diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/RazorFilePathToContentTypeProviderBase.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/RazorFilePathToContentTypeProviderBase.cs index 73d5e333a60..3cddb7ea5bf 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/RazorFilePathToContentTypeProviderBase.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/RazorFilePathToContentTypeProviderBase.cs @@ -41,8 +41,7 @@ private bool UseLSPEditor(string filePath) } // Otherwise, we just check for the lack of feature flag feature or project capability. - if (_lspEditorFeatureDetector.IsLspEditorEnabled() && - _lspEditorFeatureDetector.IsLspEditorSupported(filePath)) + if (_lspEditorFeatureDetector.IsLspEditorSupported(filePath)) { return true; } diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/RazorLanguageServerClient.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/RazorLanguageServerClient.cs deleted file mode 100644 index 9741a1ff1c3..00000000000 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LanguageClient/RazorLanguageServerClient.cs +++ /dev/null @@ -1,245 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using System.ComponentModel.Composition; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Razor.LanguageServer.Hosting; -using Microsoft.CodeAnalysis.Host; -using Microsoft.CodeAnalysis.Razor.Logging; -using Microsoft.CodeAnalysis.Razor.ProjectSystem; -using Microsoft.CodeAnalysis.Razor.Telemetry; -using Microsoft.CodeAnalysis.Razor.Workspaces; -using Microsoft.CodeAnalysis.Razor.Workspaces.Settings; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.VisualStudio.LanguageServer.Client; -using Microsoft.VisualStudio.Razor.LanguageClient.Endpoints; -using Microsoft.VisualStudio.Razor.LanguageClient.ProjectSystem; -using Microsoft.VisualStudio.Razor.Logging; -using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Threading; -using Microsoft.VisualStudio.Utilities; -using Nerdbank.Streams; -using StreamJsonRpc; - -namespace Microsoft.VisualStudio.Razor.LanguageClient; - -[Export(typeof(ILanguageClient))] -[ContentType(RazorConstants.RazorLSPContentTypeName)] -[method: ImportingConstructor] -internal class RazorLanguageServerClient( - RazorCustomMessageTarget customTarget, - ProjectSnapshotManager projectManager, - ILoggerFactory loggerFactory, - RazorLogHubTraceProvider traceProvider, - LanguageServerFeatureOptions languageServerFeatureOptions, - ILanguageClientBroker languageClientBroker, - ILanguageServiceBroker2 languageServiceBroker, - ITelemetryReporter telemetryReporter, - IClientSettingsManager clientSettingsManager, - ILspServerActivationTracker lspServerActivationTracker, - VisualStudioHostServicesProvider vsHostServicesProvider) - : ILanguageClient, ILanguageClientCustomMessage2, ILanguageClientPriority, IPropertyOwner -{ - private readonly ILanguageClientBroker _languageClientBroker = languageClientBroker; - private readonly ILanguageServiceBroker2 _languageServiceBroker = languageServiceBroker; - private readonly ITelemetryReporter _telemetryReporter = telemetryReporter; - private readonly IClientSettingsManager _clientSettingsManager = clientSettingsManager; - private readonly ILspServerActivationTracker _lspServerActivationTracker = lspServerActivationTracker; - private readonly RazorCustomMessageTarget _customMessageTarget = customTarget; - private readonly ProjectSnapshotManager _projectManager = projectManager; - private readonly LanguageServerFeatureOptions _languageServerFeatureOptions = languageServerFeatureOptions; - private readonly VisualStudioHostServicesProvider _vsHostServicesProvider = vsHostServicesProvider; - private readonly ILoggerFactory _loggerFactory = loggerFactory; - private readonly RazorLogHubTraceProvider _traceProvider = traceProvider; - - private RazorLanguageServerHost? _host; - - public event AsyncEventHandler? StartAsync; - public event AsyncEventHandler? StopAsync - { - add { } - remove { } - } - - public string Name => RazorLSPConstants.RazorLanguageServerName; - - public IEnumerable? ConfigurationSections => null; - - public object? InitializationOptions => null; - - public IEnumerable? FilesToWatch => null; - - public object? MiddleLayer => null; - - public object CustomMessageTarget => _customMessageTarget; - - public bool IsOverriding => false; - - // We set a priority to ensure that our Razor language server is always chosen if there's a conflict for which language server to prefer. - public int Priority => 10; - - public bool ShowNotificationOnInitializeFailed => true; - - public PropertyCollection Properties { get; } = CreateStjPropertyCollection(); - - private static PropertyCollection CreateStjPropertyCollection() - { - // Opt in to System.Text.Json serialization on the client - var collection = new PropertyCollection(); - collection.AddProperty("lsp-serialization", "stj"); - return collection; - } - - public async Task ActivateAsync(CancellationToken token) - { - // Swap to background thread, nothing below needs to be done on the UI thread. - await TaskScheduler.Default; - - var (clientStream, serverStream) = FullDuplexStream.CreatePair(); - - await EnsureCleanedUpServerAsync().ConfigureAwait(false); - - _traceProvider.TryGetTraceSource(out var traceSource); - - var lspOptions = RazorLSPOptions.From(_clientSettingsManager.GetClientSettings()); - - _host = RazorLanguageServerHost.Create( - serverStream, - serverStream, - _loggerFactory, - _telemetryReporter, - ConfigureServices, - _languageServerFeatureOptions, - lspOptions, - _lspServerActivationTracker, - traceSource); - - // This must not happen on an RPC endpoint due to UIThread concerns, so ActivateAsync was chosen. - await EnsureContainedLanguageServersInitializedAsync(); - - return new Connection(clientStream, clientStream); - - void ConfigureServices(IServiceCollection services) - { - services.AddSingleton(new HostServicesProviderAdapter(_vsHostServicesProvider)); - - var projectInfoDriver = new RazorProjectInfoDriver(_projectManager, _loggerFactory); - services.AddSingleton(projectInfoDriver); - } - } - - private async Task EnsureCleanedUpServerAsync() - { - if (_host is null) - { - // Server was already cleaned up - return; - } - - if (_host is not null) - { - // Server still hasn't shutdown, wait for it to shutdown - await _host.WaitForExitAsync().ConfigureAwait(false); - - _host.Dispose(); - _host = null; - } - } - - internal static IEnumerable> GetRelevantContainedLanguageClientsAndMetadata(ILanguageServiceBroker2 languageServiceBroker) - { - var relevantClientAndMetadata = new List>(); - -#pragma warning disable CS0618 // Type or member is obsolete - foreach (var languageClientAndMetadata in languageServiceBroker.LanguageClients) -#pragma warning restore CS0618 // Type or member is obsolete - { - if (languageClientAndMetadata.Metadata is not ILanguageClientMetadata metadata) - { - continue; - } - - if (metadata is IIsUserExperienceDisabledMetadata userExperienceDisabledMetadata && - userExperienceDisabledMetadata.IsUserExperienceDisabled) - { - continue; - } - - if (IsCSharpApplicable(metadata) || - metadata.ContentTypes.Contains(RazorLSPConstants.HtmlLSPDelegationContentTypeName)) - { - relevantClientAndMetadata.Add(languageClientAndMetadata); - } - } - - return relevantClientAndMetadata; - - static bool IsCSharpApplicable(ILanguageClientMetadata metadata) - { - return metadata.ContentTypes.Contains(RazorLSPConstants.CSharpContentTypeName) && - metadata.ClientName == CSharpVirtualDocumentFactory.CSharpClientName; - } - } - - private async Task EnsureContainedLanguageServersInitializedAsync() - { - var relevantClientsAndMetadata = GetRelevantContainedLanguageClientsAndMetadata(_languageServiceBroker); - - var clientLoadTasks = new List(); - - foreach (var languageClientAndMetadata in relevantClientsAndMetadata) - { - if (languageClientAndMetadata.Metadata is not ILanguageClientMetadata metadata) - { - continue; - } - - var loadAsyncTask = _languageClientBroker.LoadAsync(metadata, languageClientAndMetadata.Value); - clientLoadTasks.Add(loadAsyncTask); - } - - await Task.WhenAll(clientLoadTasks).ConfigureAwait(false); - - // We only want to mark the server as activated after the delegated language servers have been initialized. - _lspServerActivationTracker.Activated(); - } - - public Task AttachForCustomMessageAsync(JsonRpc rpc) => Task.CompletedTask; - - public Task OnServerInitializeFailedAsync(ILanguageClientInitializationInfo initializationState) - { - _lspServerActivationTracker.Deactivated(); - - var initializationFailureContext = new InitializationFailureContext - { - FailureMessage = string.Format(SR.LanguageServer_Initialization_Failed, - Name, initializationState.StatusMessage, initializationState.InitializationException?.ToString()) - }; - return Task.FromResult(initializationFailureContext); - } - - public Task OnLoadedAsync() - { - // If the user has turned on the Cohost server, then disable the Razor server - if (_languageServerFeatureOptions.UseRazorCohostServer) - { - return Task.CompletedTask; - } - - return StartAsync.InvokeAsync(this, EventArgs.Empty); - } - - public Task OnServerInitializedAsync() - => Task.CompletedTask; - - private sealed class HostServicesProviderAdapter(VisualStudioHostServicesProvider vsHostServicesProvider) : IHostServicesProvider - { - private readonly VisualStudioHostServicesProvider _vsHostServicesProvider = vsHostServicesProvider; - - public HostServices GetServices() => _vsHostServicesProvider.GetServices(); - } -} diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LspEditorFeatureDetector.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LspEditorFeatureDetector.cs index 4a72eb1bd6e..ab70099f3c3 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LspEditorFeatureDetector.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/LspEditorFeatureDetector.cs @@ -4,44 +4,21 @@ using System; using System.ComponentModel.Composition; using System.Threading; -using System.Threading.Tasks; -using Microsoft.Internal.VisualStudio.Shell.Interop; -using Microsoft.VisualStudio.Razor.Extensions; using Microsoft.VisualStudio.Razor.Logging; -using Microsoft.VisualStudio.Settings; -using Microsoft.VisualStudio.Threading; namespace Microsoft.VisualStudio.Razor; [Export(typeof(ILspEditorFeatureDetector))] -internal sealed class LspEditorFeatureDetector : ILspEditorFeatureDetector, IDisposable +[method: ImportingConstructor] +internal sealed class LspEditorFeatureDetector( + IUIContextService uiContextService, + IProjectCapabilityResolver projectCapabilityResolver, + RazorActivityLog activityLog) : ILspEditorFeatureDetector, IDisposable { - private readonly IUIContextService _uiContextService; - private readonly IProjectCapabilityResolver _projectCapabilityResolver; - private readonly JoinableTaskFactory _jtf; - private readonly RazorActivityLog _activityLog; - private readonly CancellationTokenSource _disposeTokenSource; - private readonly AsyncLazy _lazyLegacyEditorEnabled; - - [ImportingConstructor] - public LspEditorFeatureDetector( - IVsService vsSettingsManagerService, - IUIContextService uiContextService, - IProjectCapabilityResolver projectCapabilityResolver, - JoinableTaskContext joinableTaskContext, - RazorActivityLog activityLog) - { - _uiContextService = uiContextService; - _projectCapabilityResolver = projectCapabilityResolver; - _jtf = joinableTaskContext.Factory; - _activityLog = activityLog; - - _disposeTokenSource = new(); - - _lazyLegacyEditorEnabled = new(() => - ComputeUseLegacyEditorAsync(vsSettingsManagerService, activityLog, _disposeTokenSource.Token), - _jtf); - } + private readonly IUIContextService _uiContextService = uiContextService; + private readonly IProjectCapabilityResolver _projectCapabilityResolver = projectCapabilityResolver; + private readonly RazorActivityLog _activityLog = activityLog; + private readonly CancellationTokenSource _disposeTokenSource = new(); public void Dispose() { @@ -54,53 +31,14 @@ public void Dispose() _disposeTokenSource.Dispose(); } - private static async Task ComputeUseLegacyEditorAsync( - IVsService vsSettingsManagerService, - RazorActivityLog activityLog, - CancellationToken cancellationToken) - { - var settingsManager = await vsSettingsManagerService.GetValueAsync(cancellationToken).ConfigureAwaitRunInline(); - var useLegacyEditorSetting = settingsManager.GetValueOrDefault(WellKnownSettingNames.UseLegacyASPNETCoreEditor); - - if (useLegacyEditorSetting) - { - activityLog.LogInfo($"Using legacy editor because the '{WellKnownSettingNames.UseLegacyASPNETCoreEditor}' setting is set to true."); - return true; - } - - activityLog.LogInfo($"Using LSP editor."); - return false; - } - public bool IsLspEditorEnabled() { - // This method is first called by our IFilePathToContentTypeProvider.TryGetContentTypeForFilePath(...) implementations. - // We call AsyncLazy.GetValue() below to get the value. If the work hasn't yet completed, we guard against a hidden - // JTF.Run(...) on a background thread by asserting the UI thread. - - if (!_lazyLegacyEditorEnabled.IsValueFactoryCompleted) - { -#pragma warning disable VSTHRD108 // Assert thread affinity unconditionally - _jtf.AssertUIThread(); -#pragma warning restore VSTHRD108 // Assert thread affinity unconditionally - } - - return !_lazyLegacyEditorEnabled.GetValue(_disposeTokenSource.Token); + return true; } public bool IsLspEditorSupported(string documentFilePath) { - // Regardless of whether the LSP is enabled via tools/options, the document's project - // might not support it. For example, .NET Framework projects don't support the LSP Razor editor. - - var useLegacyEditor = _projectCapabilityResolver.CheckCapability(WellKnownProjectCapabilities.LegacyRazorEditor, documentFilePath); - - if (useLegacyEditor.HasCapability) - { - _activityLog.LogInfo($"'{documentFilePath}' does not support the LSP editor because it is associated with the '{WellKnownProjectCapabilities.LegacyRazorEditor}' capability."); - return false; - } - + // .NET Framework projects don't support the LSP Razor editor. if (!IsDotNetCoreProject(documentFilePath).HasCapability) { _activityLog.LogInfo($"'{documentFilePath}' does not support the LSP editor because it is not associated with the '{WellKnownProjectCapabilities.DotNetCoreCSharp}' capability."); diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/DefaultWindowsRazorProjectHost.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/DefaultWindowsRazorProjectHost.cs deleted file mode 100644 index f8485415753..00000000000 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/DefaultWindowsRazorProjectHost.cs +++ /dev/null @@ -1,396 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.ComponentModel.Composition; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Razor.Language; -using Microsoft.AspNetCore.Razor.PooledObjects; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Razor.ProjectSystem; -using Microsoft.CodeAnalysis.Razor.Workspaces; -using Microsoft.VisualStudio.ProjectSystem; -using Microsoft.VisualStudio.ProjectSystem.Properties; -using Microsoft.VisualStudio.Shell; -using Item = System.Collections.Generic.KeyValuePair>; -using Rules = Microsoft.CodeAnalysis.Razor.ProjectSystem.Rules; - -namespace Microsoft.VisualStudio.Razor.ProjectSystem; - -// This class is responsible for initializing the Razor ProjectSnapshotManager for cases where -// MSBuild provides configuration support (>= 2.1). -[AppliesTo("DotNetCoreRazor & DotNetCoreRazorConfiguration")] -[Export(ExportContractNames.Scopes.UnconfiguredProject, typeof(IProjectDynamicLoadComponent))] -[method: ImportingConstructor] -internal class DefaultWindowsRazorProjectHost( - IUnconfiguredProjectCommonServices commonServices, - [Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider, - ProjectSnapshotManager projectManager, - LanguageServerFeatureOptions languageServerFeatureOptions) - : WindowsRazorProjectHostBase(commonServices, serviceProvider, projectManager, languageServerFeatureOptions) -{ - private const string RootNamespaceProperty = "RootNamespace"; - private static readonly ImmutableHashSet s_ruleNames = ImmutableHashSet.CreateRange(new string[] - { - Rules.RazorGeneral.SchemaName, - Rules.RazorConfiguration.SchemaName, - Rules.RazorExtension.SchemaName, - Rules.RazorComponentWithTargetPath.SchemaName, - Rules.RazorGenerateWithTargetPath.SchemaName, - ConfigurationGeneralSchemaName, - }); - - protected override ImmutableHashSet GetRuleNames() => s_ruleNames; - - protected override async Task HandleProjectChangeAsync(string sliceDimensions, IProjectVersionedValue update) - { - if (TryGetConfiguration(update.Value.CurrentState, out var configuration) && - TryGetIntermediateOutputPath(update.Value.CurrentState, out var intermediatePath)) - { - TryGetRootNamespace(update.Value.CurrentState, out var rootNamespace); - - if (TryGetBeforeIntermediateOutputPath(update.Value.ProjectChanges, out var beforeIntermediateOutputPath) && - beforeIntermediateOutputPath != intermediatePath) - { - // If the intermediate output path is in the ProjectChanges, then we know that it has changed, so we want to ensure we remove the old one, - // otherwise this would be seen as an Add, and we'd end up with two active projects - await UpdateAsync( - updater => - { - var beforeProjectKey = new ProjectKey(beforeIntermediateOutputPath); - updater.RemoveProject(beforeProjectKey); - }, - CancellationToken.None) - .ConfigureAwait(false); - } - - // We need to deal with the case where the project was uninitialized, but now - // is valid for Razor. In that case we might have previously seen all of the documents - // but ignored them because the project wasn't active. - // - // So what we do to deal with this, is that we 'remove' all changed and removed items - // and then we 'add' all current items. This allows minimal churn to the PSM, but still - // makes us up to date. - var documents = GetCurrentDocuments(update.Value); - var changedDocuments = GetChangedAndRemovedDocuments(update.Value); - - await UpdateAsync( - updater => - { - var projectFileName = Path.GetFileNameWithoutExtension(CommonServices.UnconfiguredProject.FullPath); - var displayName = sliceDimensions is { Length: > 0 } - ? $"{projectFileName} ({sliceDimensions})" - : projectFileName; - - var hostProject = new HostProject(CommonServices.UnconfiguredProject.FullPath, intermediatePath, configuration, rootNamespace, displayName); - - UpdateProject(updater, hostProject); - - for (var i = 0; i < changedDocuments.Length; i++) - { - updater.RemoveDocument(hostProject.Key, changedDocuments[i].FilePath); - } - - for (var i = 0; i < documents.Length; i++) - { - var document = documents[i]; - updater.AddDocument(hostProject.Key, document, new FileTextLoader(document.FilePath, null)); - } - }, - CancellationToken.None) - .ConfigureAwait(false); - } - else - { - // Ok we can't find a configuration. Let's assume this project isn't using Razor then. - await UpdateAsync( - updater => - { - var projectKeys = GetProjectKeysWithFilePath(CommonServices.UnconfiguredProject.FullPath); - foreach (var projectKey in projectKeys) - { - RemoveProject(updater, projectKey); - } - }, - CancellationToken.None) - .ConfigureAwait(false); - } - } - - #region Configuration Helpers - // Internal for testing - internal static bool TryGetConfiguration( - IImmutableDictionary state, - [NotNullWhen(returnValue: true)] out RazorConfiguration? configuration) - { - if (!TryGetDefaultConfiguration(state, out var defaultConfiguration)) - { - configuration = null; - return false; - } - - if (!TryGetLanguageVersion(state, out var languageVersion)) - { - configuration = null; - return false; - } - - if (!TryGetConfigurationItem(defaultConfiguration, state, out var configurationItem)) - { - configuration = null; - return false; - } - - var extensionNames = GetExtensionNames(configurationItem); - if (!TryGetExtensions(extensionNames, state, out var extensions)) - { - configuration = null; - return false; - } - - configuration = new( - languageVersion, - configurationItem.Key, - extensions); - - return true; - } - - // Internal for testing - internal static bool TryGetDefaultConfiguration( - IImmutableDictionary state, - [NotNullWhen(returnValue: true)] out string? defaultConfiguration) - { - if (!state.TryGetValue(Rules.RazorGeneral.SchemaName, out var rule)) - { - defaultConfiguration = null; - return false; - } - - if (!rule.Properties.TryGetValue(Rules.RazorGeneral.RazorDefaultConfigurationProperty, out defaultConfiguration)) - { - defaultConfiguration = null; - return false; - } - - if (string.IsNullOrEmpty(defaultConfiguration)) - { - defaultConfiguration = null; - return false; - } - - return true; - } - - // Internal for testing - internal static bool TryGetLanguageVersion( - IImmutableDictionary state, - [NotNullWhen(returnValue: true)] out RazorLanguageVersion? languageVersion) - { - if (!state.TryGetValue(Rules.RazorGeneral.SchemaName, out var rule)) - { - languageVersion = null; - return false; - } - - if (!rule.Properties.TryGetValue(Rules.RazorGeneral.RazorLangVersionProperty, out var languageVersionValue)) - { - languageVersion = null; - return false; - } - - if (string.IsNullOrEmpty(languageVersionValue)) - { - languageVersion = null; - return false; - } - - if (!RazorLanguageVersion.TryParse(languageVersionValue, out languageVersion)) - { - languageVersion = RazorLanguageVersion.Latest; - } - - return true; - } - - // Internal for testing - internal static bool TryGetConfigurationItem( - string configuration, - IImmutableDictionary state, - out Item configurationItem) - { - if (!state.TryGetValue(Rules.RazorConfiguration.PrimaryDataSourceItemType, out var configurationState)) - { - configurationItem = default; - return false; - } - - var items = configurationState.Items; - foreach (var item in items) - { - if (item.Key == configuration) - { - configurationItem = item; - return true; - } - } - - configurationItem = default; - return false; - } - - // Internal for testing - internal static string[] GetExtensionNames(Item configurationItem) - { - // The list of extension names might not be present, because the configuration may not have any. - configurationItem.Value.TryGetValue(Rules.RazorConfiguration.ExtensionsProperty, out var extensionNames); - if (string.IsNullOrEmpty(extensionNames)) - { - return Array.Empty(); - } - - return extensionNames.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries); - } - - // Internal for testing - internal static bool TryGetExtensions( - string[] extensionNames, - IImmutableDictionary state, - out ImmutableArray extensions) - { - // The list of extensions might not be present, because the configuration may not have any. - state.TryGetValue(Rules.RazorExtension.PrimaryDataSourceItemType, out var rule); - - var items = rule?.Items ?? ImmutableDictionary>.Empty; - - using var builder = new PooledArrayBuilder(); - foreach (var item in items) - { - var extensionName = item.Key; - if (extensionNames.Contains(extensionName)) - { - builder.Add(new(extensionName)); - } - } - - extensions = builder.ToImmutableAndClear(); - return true; - } - - internal static bool TryGetRootNamespace( - IImmutableDictionary state, - [NotNullWhen(returnValue: true)] out string? rootNamespace) - { - if (!state.TryGetValue(ConfigurationGeneralSchemaName, out var rule)) - { - rootNamespace = null; - return false; - } - - if (!rule.Properties.TryGetValue(RootNamespaceProperty, out var rootNamespaceValue)) - { - rootNamespace = null; - return false; - } - - if (string.IsNullOrEmpty(rootNamespaceValue)) - { - rootNamespace = null; - return false; - } - - rootNamespace = rootNamespaceValue; - return true; - } - #endregion Configuration Helpers - - private HostDocument[] GetCurrentDocuments(IProjectSubscriptionUpdate update) - { - var documents = new List(); - if (update.CurrentState.TryGetValue(Rules.RazorComponentWithTargetPath.SchemaName, out var rule)) - { - foreach (var kvp in rule.Items) - { - if (kvp.Value.TryGetValue(Rules.RazorComponentWithTargetPath.TargetPathProperty, out var targetPath) && - !string.IsNullOrWhiteSpace(kvp.Key) && - !string.IsNullOrWhiteSpace(targetPath)) - { - var filePath = CommonServices.UnconfiguredProject.MakeRooted(kvp.Key); - - var fileKind = FileKinds.TryGetFileKindFromPath(filePath, out var kind) && kind != RazorFileKind.Legacy - ? kind - : RazorFileKind.Component; - - documents.Add(new HostDocument(filePath, targetPath, fileKind)); - } - } - } - - if (update.CurrentState.TryGetValue(Rules.RazorGenerateWithTargetPath.SchemaName, out rule)) - { - foreach (var kvp in rule.Items) - { - if (kvp.Value.TryGetValue(Rules.RazorGenerateWithTargetPath.TargetPathProperty, out var targetPath) && - !string.IsNullOrWhiteSpace(kvp.Key) && - !string.IsNullOrWhiteSpace(targetPath)) - { - var filePath = CommonServices.UnconfiguredProject.MakeRooted(kvp.Key); - documents.Add(new HostDocument(filePath, targetPath, RazorFileKind.Legacy)); - } - } - } - - return documents.ToArray(); - } - - private HostDocument[] GetChangedAndRemovedDocuments(IProjectSubscriptionUpdate update) - { - var documents = new List(); - if (update.ProjectChanges.TryGetValue(Rules.RazorComponentWithTargetPath.SchemaName, out var rule)) - { - foreach (var key in rule.Difference.RemovedItems.Concat(rule.Difference.ChangedItems)) - { - if (rule.Before.Items.TryGetValue(key, out var value)) - { - if (value.TryGetValue(Rules.RazorComponentWithTargetPath.TargetPathProperty, out var targetPath) && - !string.IsNullOrWhiteSpace(key) && - !string.IsNullOrWhiteSpace(targetPath)) - { - var filePath = CommonServices.UnconfiguredProject.MakeRooted(key); - - var fileKind = FileKinds.TryGetFileKindFromPath(filePath, out var kind) && kind != RazorFileKind.Legacy - ? kind - : RazorFileKind.Component; - - documents.Add(new HostDocument(filePath, targetPath, fileKind)); - } - } - } - } - - if (update.ProjectChanges.TryGetValue(Rules.RazorGenerateWithTargetPath.SchemaName, out rule)) - { - foreach (var key in rule.Difference.RemovedItems.Concat(rule.Difference.ChangedItems)) - { - if (rule.Before.Items.TryGetValue(key, out var value)) - { - if (value.TryGetValue(Rules.RazorGenerateWithTargetPath.TargetPathProperty, out var targetPath) && - !string.IsNullOrWhiteSpace(key) && - !string.IsNullOrWhiteSpace(targetPath)) - { - var filePath = CommonServices.UnconfiguredProject.MakeRooted(key); - documents.Add(new HostDocument(filePath, targetPath, RazorFileKind.Legacy)); - } - } - } - } - - return documents.ToArray(); - } -} diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/FallbackWindowsRazorProjectHost.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/FallbackWindowsRazorProjectHost.cs deleted file mode 100644 index 79d91f9471d..00000000000 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/FallbackWindowsRazorProjectHost.cs +++ /dev/null @@ -1,276 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.ComponentModel.Composition; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Linq; -using System.Reflection.Metadata; -using System.Reflection.PortableExecutable; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Razor.Language; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Razor.ProjectSystem; -using Microsoft.CodeAnalysis.Razor.Workspaces; -using Microsoft.VisualStudio.ProjectSystem; -using Microsoft.VisualStudio.Shell; -using ContentItem = Microsoft.VisualStudio.Razor.ProjectSystem.ManagedProjectSystemSchema.ContentItem; -using ItemReference = Microsoft.VisualStudio.Razor.ProjectSystem.ManagedProjectSystemSchema.ItemReference; -using NoneItem = Microsoft.VisualStudio.Razor.ProjectSystem.ManagedProjectSystemSchema.NoneItem; -using ResolvedCompilationReference = Microsoft.VisualStudio.Razor.ProjectSystem.ManagedProjectSystemSchema.ResolvedCompilationReference; - -namespace Microsoft.VisualStudio.Razor.ProjectSystem; - -// This class is responsible for initializing the Razor ProjectSnapshotManager for cases where -// MSBuild does not provides configuration support (SDK < 2.1). -[AppliesTo("(DotNetCoreRazor | DotNetCoreWeb) & !DotNetCoreRazorConfiguration")] -[Export(ExportContractNames.Scopes.UnconfiguredProject, typeof(IProjectDynamicLoadComponent))] -internal class FallbackWindowsRazorProjectHost : WindowsRazorProjectHostBase -{ - private const string MvcAssemblyFileName = "Microsoft.AspNetCore.Mvc.Razor.dll"; - private static readonly ImmutableHashSet s_ruleNames = ImmutableHashSet.CreateRange(new string[] - { - ResolvedCompilationReference.SchemaName, - ContentItem.SchemaName, - NoneItem.SchemaName, - ConfigurationGeneralSchemaName, - }); - - [ImportingConstructor] - public FallbackWindowsRazorProjectHost( - IUnconfiguredProjectCommonServices commonServices, - [Import(typeof(SVsServiceProvider))] IServiceProvider serviceProvider, - ProjectSnapshotManager projectManager, - LanguageServerFeatureOptions languageServerFeatureOptions) - : base(commonServices, serviceProvider, projectManager, languageServerFeatureOptions) - { - } - - protected override ImmutableHashSet GetRuleNames() => s_ruleNames; - - protected override async Task HandleProjectChangeAsync(string sliceDimensions, IProjectVersionedValue update) - { - string? mvcReferenceFullPath = null; - if (update.Value.CurrentState.ContainsKey(ResolvedCompilationReference.SchemaName)) - { - var references = update.Value.CurrentState[ResolvedCompilationReference.SchemaName].Items; - foreach (var reference in references) - { - if (reference.Key.EndsWith(MvcAssemblyFileName, StringComparison.OrdinalIgnoreCase)) - { - mvcReferenceFullPath = reference.Key; - break; - } - } - } - - if (mvcReferenceFullPath is null) - { - // Ok we can't find an MVC version. Let's assume this project isn't using Razor then. - await UpdateAsync( - updater => - { - var projectKeys = GetProjectKeysWithFilePath(CommonServices.UnconfiguredProject.FullPath); - foreach (var projectKey in projectKeys) - { - RemoveProject(updater, projectKey); - } - }, - CancellationToken.None) - .ConfigureAwait(false); - return; - } - - var version = GetAssemblyVersion(mvcReferenceFullPath); - if (version is null) - { - // Ok we can't find an MVC version. Let's assume this project isn't using Razor then. - await UpdateAsync( - updater => - { - var projectKeys = GetProjectKeysWithFilePath(CommonServices.UnconfiguredProject.FullPath); - foreach (var projectKey in projectKeys) - { - RemoveProject(updater, projectKey); - } - }, - CancellationToken.None) - .ConfigureAwait(false); - return; - } - - if (!TryGetIntermediateOutputPath(update.Value.CurrentState, out var intermediatePath)) - { - // Can't find an IntermediateOutputPath, so don't know what to do with this project - return; - } - - if (TryGetBeforeIntermediateOutputPath(update.Value.ProjectChanges, out var beforeIntermediateOutputPath) && - beforeIntermediateOutputPath != intermediatePath) - { - // If the intermediate output path is in the ProjectChanges, then we know that it has changed, so we want to ensure we remove the old one, - // otherwise this would be seen as an Add, and we'd end up with two active projects - await UpdateAsync( - updater => - { - var beforeProjectKey = new ProjectKey(beforeIntermediateOutputPath); - RemoveProject(updater, beforeProjectKey); - }, - CancellationToken.None) - .ConfigureAwait(false); - } - - // We need to deal with the case where the project was uninitialized, but now - // is valid for Razor. In that case we might have previously seen all of the documents - // but ignored them because the project wasn't active. - // - // So what we do to deal with this, is that we 'remove' all changed and removed items - // and then we 'add' all current items. This allows minimal churn to the PSM, but still - // makes us up-to-date. - var documents = GetCurrentDocuments(update.Value); - var changedDocuments = GetChangedAndRemovedDocuments(update.Value); - - await UpdateAsync(updater => - { - var configuration = FallbackRazorConfiguration.SelectConfiguration(version); - var projectFileName = Path.GetFileNameWithoutExtension(CommonServices.UnconfiguredProject.FullPath); - var displayName = sliceDimensions is { Length: > 0 } - ? $"{projectFileName} ({sliceDimensions})" - : projectFileName; - - var hostProject = new HostProject(CommonServices.UnconfiguredProject.FullPath, intermediatePath, configuration, rootNamespace: null, displayName); - - UpdateProject(updater, hostProject); - - for (var i = 0; i < changedDocuments.Length; i++) - { - updater.RemoveDocument(hostProject.Key, changedDocuments[i].FilePath); - } - - for (var i = 0; i < documents.Length; i++) - { - var document = documents[i]; - updater.AddDocument(hostProject.Key, document, new FileTextLoader(document.FilePath, null)); - } - }, CancellationToken.None).ConfigureAwait(false); - } - - // virtual for overriding in tests - protected virtual Version? GetAssemblyVersion(string filePath) - { - return ReadAssemblyVersion(filePath); - } - - // Internal for testing - internal HostDocument[] GetCurrentDocuments(IProjectSubscriptionUpdate update) - { - var documents = new List(); - - // Content Razor files - if (update.CurrentState.TryGetValue(ContentItem.SchemaName, out var rule)) - { - foreach (var kvp in rule.Items) - { - if (TryGetRazorDocument(kvp.Value, out var document)) - { - documents.Add(document); - } - } - } - - // None Razor files, these are typically included when a user links a file in Visual Studio. - if (update.CurrentState.TryGetValue(NoneItem.SchemaName, out var nonRule)) - { - foreach (var kvp in nonRule.Items) - { - if (TryGetRazorDocument(kvp.Value, out var document)) - { - documents.Add(document); - } - } - } - - return documents.ToArray(); - } - - // Internal for testing - internal HostDocument[] GetChangedAndRemovedDocuments(IProjectSubscriptionUpdate update) - { - var documents = new List(); - - // Content Razor files - if (update.ProjectChanges.TryGetValue(ContentItem.SchemaName, out var rule)) - { - foreach (var key in rule.Difference.RemovedItems.Concat(rule.Difference.ChangedItems)) - { - if (rule.Before.Items.TryGetValue(key, out var value) && - TryGetRazorDocument(value, out var document)) - { - documents.Add(document); - } - } - } - - // None Razor files, these are typically included when a user links a file in Visual Studio. - if (update.ProjectChanges.TryGetValue(NoneItem.SchemaName, out var nonRule)) - { - foreach (var key in nonRule.Difference.RemovedItems.Concat(nonRule.Difference.ChangedItems)) - { - if (nonRule.Before.Items.TryGetValue(key, out var value) && - TryGetRazorDocument(value, out var document)) - { - documents.Add(document); - } - } - } - - return documents.ToArray(); - } - - // Internal for testing - internal bool TryGetRazorDocument(IImmutableDictionary itemState, [NotNullWhen(returnValue: true)] out HostDocument? razorDocument) - { - if (itemState.TryGetValue(ItemReference.FullPathPropertyName, out var filePath)) - { - // If there's no target path then we normalize the target path to the file path. In the end, all we care about - // is that the file being included in the primary project ends in .cshtml. - itemState.TryGetValue(ItemReference.LinkPropertyName, out var targetPath); - if (string.IsNullOrEmpty(targetPath)) - { - targetPath = filePath; - } - - if (targetPath.EndsWith(".cshtml", StringComparison.OrdinalIgnoreCase)) - { - targetPath = CommonServices.UnconfiguredProject.MakeRooted(targetPath); - razorDocument = new HostDocument(filePath, targetPath, RazorFileKind.Legacy); - return true; - } - } - - razorDocument = null; - return false; - } - - private static Version? ReadAssemblyVersion(string filePath) - { - try - { - using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete); - using var reader = new PEReader(stream); - var metadataReader = reader.GetMetadataReader(); - - var assemblyDefinition = metadataReader.GetAssemblyDefinition(); - return assemblyDefinition.Version; - } - catch - { - // We're purposely silencing any kinds of I/O exceptions here, just in case something wacky is going on. - return null; - } - } -} diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/WindowsRazorProjectHostBase.TestAccessor.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/WindowsRazorProjectHostBase.TestAccessor.cs deleted file mode 100644 index 679c7895232..00000000000 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/WindowsRazorProjectHostBase.TestAccessor.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Immutable; -using System.Threading.Tasks; -using Microsoft.VisualStudio.ProjectSystem; -using Microsoft.VisualStudio.ProjectSystem.Properties; - -namespace Microsoft.VisualStudio.Razor.ProjectSystem; - -internal partial class WindowsRazorProjectHostBase -{ - internal TestAccessor GetTestAccessor() => new(this); - - internal sealed class TestAccessor(WindowsRazorProjectHostBase @this) - { - private readonly WindowsRazorProjectHostBase _this = @this; - - internal bool GetIntermediateOutputPathFromProjectChange(IImmutableDictionary state, out string? result) - { - _this._skipDirectoryExistCheck_TestOnly = true; - return _this.TryGetIntermediateOutputPath(state, out result); - } - - internal Task InitializeAsync() - => _this.InitializeAsync(); - - internal Task OnProjectChangedAsync(string sliceDimensions, IProjectVersionedValue update) - => _this.OnProjectChangedAsync(sliceDimensions, update); - - internal Task OnProjectRenamingAsync(string oldProjectFilePath, string newProjectFilePath) - => _this.OnProjectRenamingAsync(oldProjectFilePath, newProjectFilePath); - } -} diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/WindowsRazorProjectHostBase.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/WindowsRazorProjectHostBase.cs deleted file mode 100644 index cc39be8906f..00000000000 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/WindowsRazorProjectHostBase.cs +++ /dev/null @@ -1,399 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; -using System.IO; -using System.Threading; -using System.Threading.Tasks; -using System.Threading.Tasks.Dataflow; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Razor.ProjectSystem; -using Microsoft.CodeAnalysis.Razor.Workspaces; -using Microsoft.VisualStudio.ProjectSystem; -using Microsoft.VisualStudio.ProjectSystem.Properties; -using Microsoft.VisualStudio.Threading; - -namespace Microsoft.VisualStudio.Razor.ProjectSystem; - -internal abstract partial class WindowsRazorProjectHostBase : OnceInitializedOnceDisposedAsync, IProjectDynamicLoadComponent -{ - // AsyncSemaphore is banned. See https://github.com/dotnet/razor/issues/10390 for more info. -#pragma warning disable RS0030 // Do not use banned APIs - - private static readonly DataflowLinkOptions s_dataflowLinkOptions = new DataflowLinkOptions() { PropagateCompletion = true }; - - private readonly IServiceProvider _serviceProvider; - private readonly ProjectSnapshotManager _projectManager; - private readonly LanguageServerFeatureOptions _languageServerFeatureOptions; - private readonly AsyncSemaphore _lock; - - private readonly Dictionary _projectSubscriptions = new(); - private readonly List _disposables = new(); - - internal const string BaseIntermediateOutputPathPropertyName = "BaseIntermediateOutputPath"; - internal const string IntermediateOutputPathPropertyName = "IntermediateOutputPath"; - internal const string MSBuildProjectDirectoryPropertyName = "MSBuildProjectDirectory"; - - internal const string ConfigurationGeneralSchemaName = "ConfigurationGeneral"; - - private bool _skipDirectoryExistCheck_TestOnly; - - protected WindowsRazorProjectHostBase( - IUnconfiguredProjectCommonServices commonServices, - IServiceProvider serviceProvider, - ProjectSnapshotManager projectManager, - LanguageServerFeatureOptions languageServerFeatureOptions) - : base(commonServices.ThreadingService.JoinableTaskContext) - { - CommonServices = commonServices; - _serviceProvider = serviceProvider; - _projectManager = projectManager; - _languageServerFeatureOptions = languageServerFeatureOptions; - - _lock = new AsyncSemaphore(initialCount: 1); - } - - protected abstract ImmutableHashSet GetRuleNames(); - - protected abstract Task HandleProjectChangeAsync(string sliceDimensions, IProjectVersionedValue update); - - protected IUnconfiguredProjectCommonServices CommonServices { get; } - - protected sealed override Task InitializeCoreAsync(CancellationToken cancellationToken) - { - Debug.Assert(!_languageServerFeatureOptions.UseRazorCohostServer, "When cohosting is on this should never be initialized."); - - CommonServices.UnconfiguredProject.ProjectRenaming += UnconfiguredProject_ProjectRenamingAsync; - - // CPS represents the various target frameworks that a project has in configuration groups, which are called "slices". Each - // slice represents a variation of a project configuration. So for example, a given multi-targeted project would have: - // - // Configuration | Platform | Configuration Groups (slices) - // ----------------------------------------------------- - // Debug | Any CPU | net6.0, net7.0 - // Release | Any CPU | net6.0, net7.0 - // - // This subscription hooks to the ActiveConfigurationGroupSubscriptionService which will feed us data whenever a - // "slice" is added or removed, for the current active Configuration/Platform combination. This is a nice mix between - // not having too many things loaded (ie, we don't get 4 projects representing the full matrix of configurations) but - // still having distinct projects per target framework. If the user changes configuration from Debug to Release, we will - // get updates for the slices indicating that change, but within a specific configuration, both target frameworks will - // get updates for project changes, which means our data won't be stale if the user changes the active context. - // - // CPS also manages the slices themselves, and we get updates as they change. eg the first event we get has one target - // framework, then we get an update containing both. If the user adds one, we get another event. Either way - // the event also gives us a datasource we can subscribe to in order to receive updates about that project. It is - // important that we maintain our list of subscriptions because if a slice is removed, we are responsible for cleaning - // up our resources. - // - // It's worth noting the events also give us a key, which is a list of "dimensions". We only care about the target framework - // but they could be strictly anything, and could change at any time. If they do, we'll get new events, so the easiest - // thing to do is just treat the key as an opaque object. CPS implements IEquatable on it, expressly for this purpose. - // We should not have any logic that depends on the contents of the key. - // - // Somewhat similar to https://github.com/dotnet/project-system/blob/bf4f33ec1843551eb775f73cff515a939aa2f629/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/Tree/Dependencies/Subscriptions/DependenciesSnapshotProvider.cs - // but a lot simpler. - _disposables.Add(CommonServices.ActiveConfigurationGroupSubscriptionService.SourceBlock.LinkTo( - DataflowBlockSlim.CreateActionBlock>(SlicesChanged, nameFormat: "Slice {1}"), - new DataflowLinkOptions() { PropagateCompletion = true })); - - // Join, in the JTF sense, the ActiveConfigurationGroupSubscriptionService, to help avoid hangs in our OnProjectChangedAsync method - _disposables.Add(ProjectDataSources.JoinUpstreamDataSources(CommonServices.ThreadingService.JoinableTaskFactory, CommonServices.FaultHandlerService, CommonServices.ActiveConfigurationGroupSubscriptionService)); - - return Task.CompletedTask; - } - - private void SlicesChanged(IProjectVersionedValue value) - { - // Create a new dictionary representing the subscriptions we know about at the start of the update. Data flow ensures - // this method will not be called in parallel. - var current = new Dictionary(_projectSubscriptions); - - foreach (var (slice, source) in value.Value) - { - if (!_projectSubscriptions.TryGetValue(slice, out var dataSource)) - { - Assumes.False(current.ContainsKey(slice)); - - var dimensions = string.Join(";", slice.Dimensions.Values); - - // This is a new slice that we didn't previously know about, either because its a new target framework, or how dimensions - // are calculated has changed. We simply subscribe to updates for it, and let our action block code handle whether the - // distinction is important. To put it another way, we may end up having multiple subscriptions and events that would be - // affect about the same project.razor.bin file, but our event handling code ensures we don't handle them more than - // necessary. - var subscription = source.JointRuleSource.SourceBlock.LinkTo( - DataflowBlockSlim.CreateActionBlock>(v => OnProjectChangedAsync(dimensions, v), nameFormat: "OnProjectChanged {1}"), - initialDataAsNew: true, - suppressVersionOnlyUpdates: true, - ruleNames: GetRuleNames(), - linkOptions: s_dataflowLinkOptions); - - _projectSubscriptions.Add(slice, subscription); - } - else - { - // We already know about this slice, so remove it from our "current" list, as we have nothing to do for it - Assumes.True(current.Remove(slice)); - } - } - - // Anything left in the current list must have been removed, so we dispose it - foreach (var (slice, subscription) in current) - { - Assumes.True(_projectSubscriptions.Remove(slice)); - - subscription.Dispose(); - } - } - - private Task OnProjectChangedAsync(string sliceDimensions, IProjectVersionedValue update) - { - if (IsDisposing || IsDisposed) - { - return Task.CompletedTask; - } - - return CommonServices.TasksService.LoadedProjectAsync(() => ExecuteWithLockAsync(() => - { - return HandleProjectChangeAsync(sliceDimensions, update); - }), registerFaultHandler: true).Task; - } - - protected override async Task DisposeCoreAsync(bool initialized) - { - if (_languageServerFeatureOptions.UseRazorCohostServer) - { - return; - } - - if (initialized) - { - CommonServices.UnconfiguredProject.ProjectRenaming -= UnconfiguredProject_ProjectRenamingAsync; - - // If we haven't been initialized, lets not start now - if (_projectManager is not null) - { - await ExecuteWithLockAsync( - () => UpdateAsync(updater => - { - // The Projects property creates a copy, so its okay to iterate through this - var projects = updater.GetProjects(); - foreach (var project in projects) - { - RemoveProject(updater, project.Key); - } - }, - CancellationToken.None)) - .ConfigureAwait(false); - } - - foreach (var (slice, subscription) in _projectSubscriptions) - { - subscription.Dispose(); - } - - foreach (var disposable in _disposables) - { - disposable.Dispose(); - } - } - } - - private Task OnProjectRenamingAsync(string oldProjectFilePath, string newProjectFilePath) - { - // When a project gets renamed we expect any rules watched by the derived class to fire. - // - // However, the project snapshot manager uses the project Fullpath as the key. We want to just - // reinitialize the HostProject with the same configuration and settings here, but the updated - // FilePath. - return ExecuteWithLockAsync(() => UpdateAsync(updater => - { - var projectKeys = updater.GetProjectKeysWithFilePath(oldProjectFilePath); - foreach (var projectKey in projectKeys) - { - if (updater.TryGetProject(projectKey, out var project)) - { - RemoveProject(updater, projectKey); - - var hostProject = new HostProject(newProjectFilePath, project.IntermediateOutputPath, project.Configuration, project.RootNamespace); - UpdateProject(updater, hostProject); - - // This should no-op in the common case, just putting it here for insurance. - foreach (var documentFilePath in project.DocumentFilePaths) - { - var documentSnapshot = project.GetRequiredDocument(documentFilePath); - - var hostDocument = new HostDocument( - documentSnapshot.FilePath, - documentSnapshot.TargetPath, - documentSnapshot.FileKind); - updater.AddDocument(projectKey, hostDocument, new FileTextLoader(hostDocument.FilePath, null)); - } - } - } - }, CancellationToken.None)); - } - - protected ImmutableArray GetProjectKeysWithFilePath(string projectFilePath) - => _projectManager.GetProjectKeysWithFilePath(projectFilePath); - - protected Task UpdateAsync(Action action, CancellationToken cancellationToken) - { - return _projectManager.UpdateAsync( - static (updater, state) => - { - var (action, serviceProvider) = state; - - // This is a potential entry point for Razor start up when a project is opened with no open editors. - // We need to ensure that any Razor start up services are initialized before the project manager is updated. - RazorStartupInitializer.Initialize(serviceProvider); - - action(updater); - }, - state: (action, _serviceProvider), - cancellationToken); - } - - protected static void UpdateProject(ProjectSnapshotManager.Updater updater, HostProject project) - { - if (!updater.ContainsProject(project.Key)) - { - // Just in case we somehow got in a state where VS didn't tell us that solution close was finished, lets just - // ensure we're going to actually do something with the new project that we've just been told about. - // If VS did tell us, then this is a no-op. - updater.SolutionOpened(); - updater.AddProject(project); - } - else - { - updater.UpdateProjectConfiguration(project); - } - } - - protected void RemoveProject(ProjectSnapshotManager.Updater updater, ProjectKey projectKey) - { - updater.RemoveProject(projectKey); - } - - private async Task ExecuteWithLockAsync(Func func) - { - using (JoinableCollection.Join()) - { - using (await _lock.EnterAsync().ConfigureAwait(false)) - { - var task = JoinableFactory.RunAsync(func); - await task.Task.ConfigureAwait(false); - } - } - } - - Task IProjectDynamicLoadComponent.LoadAsync() - { - if (_languageServerFeatureOptions.UseRazorCohostServer) - { - return Task.CompletedTask; - } - - return InitializeAsync(); - } - - Task IProjectDynamicLoadComponent.UnloadAsync() - { - return DisposeAsync(); - } - - private Task UnconfiguredProject_ProjectRenamingAsync(object? sender, ProjectRenamedEventArgs args) - => OnProjectRenamingAsync(args.OldFullPath, args.NewFullPath); - - protected bool TryGetBeforeIntermediateOutputPath(IImmutableDictionary state, - [NotNullWhen(returnValue: true)] out string? path) - { - if (!state.TryGetValue(ConfigurationGeneralSchemaName, out var rule)) - { - path = null; - return false; - } - - var beforeValues = rule.Before; - - return TryGetIntermediateOutputPathFromProjectRuleSnapshot(beforeValues, out path); - } - - protected virtual bool TryGetIntermediateOutputPath( - IImmutableDictionary state, - [NotNullWhen(returnValue: true)] out string? path) - { - if (!state.TryGetValue(ConfigurationGeneralSchemaName, out var rule)) - { - path = null; - return false; - } - - return TryGetIntermediateOutputPathFromProjectRuleSnapshot(rule, out path); - } - - private bool TryGetIntermediateOutputPathFromProjectRuleSnapshot(IProjectRuleSnapshot rule, out string? path) - { - if (!rule.Properties.TryGetValue(BaseIntermediateOutputPathPropertyName, out var baseIntermediateOutputPathValue)) - { - path = null; - return false; - } - - if (!rule.Properties.TryGetValue(IntermediateOutputPathPropertyName, out var intermediateOutputPathValue)) - { - path = null; - return false; - } - - if (string.IsNullOrEmpty(intermediateOutputPathValue) || string.IsNullOrEmpty(baseIntermediateOutputPathValue)) - { - path = null; - return false; - } - - var basePath = new DirectoryInfo(baseIntermediateOutputPathValue).Parent; - var joinedPath = Path.Combine(basePath.FullName, intermediateOutputPathValue); - - if (!Path.IsPathRooted(baseIntermediateOutputPathValue)) - { - // For Razor class libraries, the base intermediate path is relative. Meaning instead of C:/project/obj it returns /obj. - // The `new DirectoryInfo(...).Parent` call above is forgiving so if the path passed to it isn't absolute (Razor class library scenario) it utilizes Directory.GetCurrentDirectory, which - // could be the C:/Windows/System path, or the solution path, or anything really. - // - // To workaround these inconsistencies with Razor class libraries we fall back to the MSBuildProjectDirectory and build what we think is the intermediate output path. - joinedPath = ResolveFallbackIntermediateOutputPath(rule, intermediateOutputPathValue); - if (joinedPath is null) - { - // Still couldn't resolve a valid directory. - path = null; - return false; - } - } - - path = Path.GetFullPath(joinedPath); - return true; - } - - private string? ResolveFallbackIntermediateOutputPath(IProjectRuleSnapshot rule, string intermediateOutputPathValue) - { - if (!rule.Properties.TryGetValue(MSBuildProjectDirectoryPropertyName, out var projectDirectory)) - { - // Can't resolve the project, bail. - return null; - } - - var joinedPath = Path.Combine(projectDirectory, intermediateOutputPathValue); - if (!_skipDirectoryExistCheck_TestOnly && !Directory.Exists(joinedPath)) - { - return null; - } - - return joinedPath; - } -} diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/VisualStudioLanguageServerFeatureOptions.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/VisualStudioLanguageServerFeatureOptions.cs index 2df10beab23..8e014f9b776 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/VisualStudioLanguageServerFeatureOptions.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/VisualStudioLanguageServerFeatureOptions.cs @@ -14,7 +14,6 @@ internal class VisualStudioLanguageServerFeatureOptions : LanguageServerFeatureO { private readonly ILspEditorFeatureDetector _lspEditorFeatureDetector; private readonly Lazy _showAllCSharpCodeActions; - private readonly Lazy _useRazorCohostServer; [ImportingConstructor] public VisualStudioLanguageServerFeatureOptions(ILspEditorFeatureDetector lspEditorFeatureDetector) @@ -27,13 +26,6 @@ public VisualStudioLanguageServerFeatureOptions(ILspEditorFeatureDetector lspEdi var showAllCSharpCodeActions = featureFlags.IsFeatureEnabled(WellKnownFeatureFlagNames.ShowAllCSharpCodeActions, defaultValue: false); return showAllCSharpCodeActions; }); - - _useRazorCohostServer = new Lazy(() => - { - var featureFlags = (IVsFeatureFlags)Package.GetGlobalService(typeof(SVsFeatureFlags)); - var useRazorCohostServer = featureFlags.IsFeatureEnabled(WellKnownFeatureFlagNames.UseRazorCohostServer, defaultValue: true); - return useRazorCohostServer; - }); } // We don't currently support file creation operations on VS Codespaces or VS Liveshare @@ -45,5 +37,5 @@ public VisualStudioLanguageServerFeatureOptions(ILspEditorFeatureDetector lspEdi public override bool ShowAllCSharpCodeActions => _showAllCSharpCodeActions.Value; - public override bool UseRazorCohostServer => _useRazorCohostServer.Value; + public override bool UseRazorCohostServer => true; } diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/WellKnownProjectCapabilities.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/WellKnownProjectCapabilities.cs index dec71a62841..3ce39d87610 100644 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/WellKnownProjectCapabilities.cs +++ b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/WellKnownProjectCapabilities.cs @@ -6,5 +6,4 @@ namespace Microsoft.VisualStudio.Razor; internal static class WellKnownProjectCapabilities { public const string DotNetCoreCSharp = "CSharp&CPS"; - public const string LegacyRazorEditor = "LegacyRazorEditor"; } diff --git a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/WellKnownSettingNames.cs b/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/WellKnownSettingNames.cs deleted file mode 100644 index 1fa6b2537ea..00000000000 --- a/src/Razor/src/Microsoft.VisualStudio.LanguageServices.Razor/WellKnownSettingNames.cs +++ /dev/null @@ -1,9 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.VisualStudio.Razor; - -internal static class WellKnownSettingNames -{ - public const string UseLegacyASPNETCoreEditor = "TextEditor.HTML.Specific.UseLegacyASPNETCoreRazorEditor"; -} diff --git a/src/Razor/src/Microsoft.VisualStudio.RazorExtension/Microsoft.VisualStudio.RazorExtension.Custom.pkgdef b/src/Razor/src/Microsoft.VisualStudio.RazorExtension/Microsoft.VisualStudio.RazorExtension.Custom.pkgdef index 0868efcb927..ace30532b2c 100644 --- a/src/Razor/src/Microsoft.VisualStudio.RazorExtension/Microsoft.VisualStudio.RazorExtension.Custom.pkgdef +++ b/src/Razor/src/Microsoft.VisualStudio.RazorExtension/Microsoft.VisualStudio.RazorExtension.Custom.pkgdef @@ -44,12 +44,6 @@ "Title"="Show all C# code actions in Razor files (requires restart)" "PreviewPaneChannels"="IntPreview,int.main" -[$RootKey$\FeatureFlags\Razor\LSP\UseRazorCohostServer] -"Description"="Uses the Razor language server that is cohosted in Roslyn to provide some Razor tooling functionality." -"Value"=dword:00000001 -"Title"="Use Roslyn Cohost server for Razor (requires restart)" -"PreviewPaneChannels"="*" - // CacheTag value should be changed when registration file changes // See https://devdiv.visualstudio.com/DevDiv/_wiki/wikis/DevDiv.wiki/39345/Manifest-Build-Deployment-and-Setup-Authoring-In-Depth?anchor=example-pkgdef-key for more infomation [$RootKey$\SettingsManifests\{13b72f58-279e-49e0-a56d-296be02f0805}] diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/LspEditorFeatureDetectorTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/LspEditorFeatureDetectorTest.cs index 025d0b52f0f..6dfac332a85 100644 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/LspEditorFeatureDetectorTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/LspEditorFeatureDetectorTest.cs @@ -5,9 +5,7 @@ using Microsoft.AspNetCore.Razor.Test.Common; using Microsoft.AspNetCore.Razor.Test.Common.VisualStudio; using Microsoft.CodeAnalysis.Razor.Logging; -using Microsoft.Internal.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Razor.Logging; -using Microsoft.VisualStudio.Settings; using Microsoft.VisualStudio.Shell.Interop; using Moq; using Xunit; @@ -17,46 +15,34 @@ namespace Microsoft.VisualStudio.Razor; public class LspEditorFeatureDetectorTest(ITestOutputHelper testOutput) : ToolingTestBase(testOutput) { - public static TheoryData IsLspEditorEnabledTestData { get; } = new() - { - // legacyEditorSetting, expectedResult - { false, true }, - { true, false }, - }; - - [UITheory] - [MemberData(nameof(IsLspEditorEnabledTestData))] - public void IsLspEditorEnabled(bool legacyEditorSetting, bool expectedResult) + [Fact] + public void IsLspEditorEnabled() { // Arrange - var featureDetector = CreateLspEditorFeatureDetector(legacyEditorSetting); + var featureDetector = CreateLspEditorFeatureDetector(); // Act var result = featureDetector.IsLspEditorEnabled(); // Assert - Assert.Equal(expectedResult, result); + Assert.True(result); } - public static TheoryData IsLspEditorEnabledAndSupportedTestData { get; } = new() + public static TheoryData IsLspEditorEnabledAndSupportedTestData { get; } = new() { - // legacyEditorSetting, hasLegacyRazorEditorCapability, hasDotNetCoreCSharpCapability, expectedResult - { false, true, false, false }, // .Net Framework project - always non-LSP - { false, false, true, true }, // .Net Core project - { false, true, true, false }, // .Net Core project opts-in into legacy razor editor (exists in reality?) - { true, false, true, false }, // .Net Core project but legacy editor via editor option + // hasDotNetCoreCSharpCapability, expectedResult + { false, false }, // .Net Framework project - always non-LSP + { true, true }, // .Net Core project }; [UITheory] [MemberData(nameof(IsLspEditorEnabledAndSupportedTestData))] public void IsLspEditorEnabledAndSupported( - bool legacyEditorSetting, - bool hasLegacyRazorEditorCapability, bool hasDotNetCoreCSharpCapability, bool expectedResult) { // Arrange - var featureDetector = CreateLspEditorFeatureDetector(legacyEditorSetting, hasLegacyRazorEditorCapability, hasDotNetCoreCSharpCapability); + var featureDetector = CreateLspEditorFeatureDetector(hasDotNetCoreCSharpCapability); // Act var result = featureDetector.IsLspEditorEnabled() && @@ -119,29 +105,23 @@ public void IsLiveShareHost(bool liveShareHostActive, bool liveShareGuestActive, } private ILspEditorFeatureDetector CreateLspEditorFeatureDetector(IUIContextService uiContextService) - => CreateLspEditorFeatureDetector(legacyEditorSetting: false, uiContextService, hasLegacyRazorEditorCapability: false, hasDotNetCoreCSharpCapability: true); + => CreateLspEditorFeatureDetector(uiContextService, hasDotNetCoreCSharpCapability: true); private ILspEditorFeatureDetector CreateLspEditorFeatureDetector( - bool legacyEditorSetting = false, - bool hasLegacyRazorEditorCapability = false, bool hasDotNetCoreCSharpCapability = true) { - return CreateLspEditorFeatureDetector(legacyEditorSetting, CreateUIContextService(), hasLegacyRazorEditorCapability, hasDotNetCoreCSharpCapability); + return CreateLspEditorFeatureDetector(CreateUIContextService(), hasDotNetCoreCSharpCapability); } private ILspEditorFeatureDetector CreateLspEditorFeatureDetector( - bool legacyEditorSetting, IUIContextService uiContextService, - bool hasLegacyRazorEditorCapability, bool hasDotNetCoreCSharpCapability) { uiContextService ??= CreateUIContextService(); var featureDetector = new LspEditorFeatureDetector( - CreateVsSettingsManagerService(legacyEditorSetting), uiContextService, - CreateProjectCapabilityResolver(hasLegacyRazorEditorCapability, hasDotNetCoreCSharpCapability), - JoinableTaskContext, + CreateProjectCapabilityResolver(hasDotNetCoreCSharpCapability), CreateRazorActivityLog()); AddDisposable(featureDetector); @@ -149,16 +129,6 @@ private ILspEditorFeatureDetector CreateLspEditorFeatureDetector( return featureDetector; } - private static IVsService CreateVsSettingsManagerService(bool useLegacyEditor) - { - var vsSettingsManagerMock = new StrictMock(); - vsSettingsManagerMock - .Setup(x => x.GetValueOrDefault(WellKnownSettingNames.UseLegacyASPNETCoreEditor, It.IsAny())) - .Returns(useLegacyEditor); - - return VsMocks.CreateVsService(vsSettingsManagerMock); - } - private static IUIContextService CreateUIContextService( bool liveShareHostActive = false, bool liveShareGuestActive = false, @@ -178,13 +148,10 @@ private static IUIContextService CreateUIContextService( return mock.Object; } - private static IProjectCapabilityResolver CreateProjectCapabilityResolver(bool hasLegacyRazorEditorCapability, bool hasDotNetCoreCSharpCapability) + private static IProjectCapabilityResolver CreateProjectCapabilityResolver(bool hasDotNetCoreCSharpCapability) { var projectCapabilityResolverMock = new StrictMock(); - projectCapabilityResolverMock - .Setup(x => x.CheckCapability(WellKnownProjectCapabilities.LegacyRazorEditor, It.IsAny())) - .Returns(new CapabilityCheckResult(IsInProject: true, HasCapability: hasLegacyRazorEditorCapability)); projectCapabilityResolverMock .Setup(x => x.CheckCapability(WellKnownProjectCapabilities.DotNetCoreCSharp, It.IsAny())) .Returns(new CapabilityCheckResult(IsInProject: true, HasCapability: hasDotNetCoreCSharpCapability)); diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/DefaultWindowsRazorProjectHostTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/DefaultWindowsRazorProjectHostTest.cs deleted file mode 100644 index 0d713a740ff..00000000000 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/DefaultWindowsRazorProjectHostTest.cs +++ /dev/null @@ -1,1422 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#nullable disable - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Razor.Language; -using Microsoft.AspNetCore.Razor.Test.Common; -using Microsoft.AspNetCore.Razor.Test.Common.ProjectSystem; -using Microsoft.AspNetCore.Razor.Test.Common.VisualStudio; -using Microsoft.AspNetCore.Razor.Test.Common.Workspaces; -using Microsoft.CodeAnalysis.Razor.ProjectSystem; -using Microsoft.VisualStudio.ProjectSystem.Properties; -using Xunit; -using Xunit.Abstractions; -using Rules = Microsoft.CodeAnalysis.Razor.ProjectSystem.Rules; - -namespace Microsoft.VisualStudio.Razor.ProjectSystem; - -public class DefaultWindowsRazorProjectHostTest : VisualStudioWorkspaceTestBase -{ - private readonly IServiceProvider _serviceProvider; - private readonly ItemCollection _configurationItems; - private readonly ItemCollection _extensionItems; - private readonly ItemCollection _razorComponentWithTargetPathItems; - private readonly ItemCollection _razorGenerateWithTargetPathItems; - private readonly PropertyCollection _razorGeneralProperties; - private readonly PropertyCollection _configurationGeneral; - private readonly TestProjectSnapshotManager _projectManager; - - public DefaultWindowsRazorProjectHostTest(ITestOutputHelper testOutput) - : base(testOutput) - { - _serviceProvider = VsMocks.CreateServiceProvider(static b => - b.AddComponentModel(static b => - { - var startupInitializer = new RazorStartupInitializer(TestLanguageServerFeatureOptions.Instance, []); - b.AddExport(startupInitializer); - })); - - _projectManager = CreateProjectSnapshotManager(); - - _configurationItems = new ItemCollection(Rules.RazorConfiguration.SchemaName); - _extensionItems = new ItemCollection(Rules.RazorExtension.SchemaName); - _razorComponentWithTargetPathItems = new ItemCollection(Rules.RazorComponentWithTargetPath.SchemaName); - _razorGenerateWithTargetPathItems = new ItemCollection(Rules.RazorGenerateWithTargetPath.SchemaName); - _razorGeneralProperties = new PropertyCollection(Rules.RazorGeneral.SchemaName); - - _configurationGeneral = new PropertyCollection(WindowsRazorProjectHostBase.ConfigurationGeneralSchemaName); - } - - [Fact] - public void TryGetDefaultConfiguration_FailsIfNoRule() - { - // Arrange - var projectState = new Dictionary().ToImmutableDictionary(); - - // Act - var result = DefaultWindowsRazorProjectHost.TryGetDefaultConfiguration(projectState, out var defaultConfiguration); - - // Assert - Assert.False(result); - Assert.Null(defaultConfiguration); - } - - [Fact] - public void TryGetDefaultConfiguration_FailsIfNoConfiguration() - { - // Arrange - var projectState = new Dictionary() - { - [Rules.RazorGeneral.SchemaName] = TestProjectRuleSnapshot.CreateProperties(Rules.RazorGeneral.SchemaName, new Dictionary()) - }.ToImmutableDictionary(); - - // Act - var result = DefaultWindowsRazorProjectHost.TryGetDefaultConfiguration(projectState, out var defaultConfiguration); - - // Assert - Assert.False(result); - Assert.Null(defaultConfiguration); - } - - [Fact] - public void TryGetDefaultConfiguration_FailsIfEmptyConfiguration() - { - // Arrange - var projectState = new Dictionary() - { - [Rules.RazorGeneral.SchemaName] = TestProjectRuleSnapshot.CreateProperties( - Rules.RazorGeneral.SchemaName, - new Dictionary() - { - [Rules.RazorGeneral.RazorDefaultConfigurationProperty] = string.Empty - }) - }.ToImmutableDictionary(); - - // Act - var result = DefaultWindowsRazorProjectHost.TryGetDefaultConfiguration(projectState, out var defaultConfiguration); - - // Assert - Assert.False(result); - Assert.Null(defaultConfiguration); - } - - [Fact] - public void TryGetDefaultConfiguration_SucceedsWithValidConfiguration() - { - // Arrange - var expectedConfiguration = "Razor-13.37"; - var projectState = new Dictionary() - { - [Rules.RazorGeneral.SchemaName] = TestProjectRuleSnapshot.CreateProperties( - Rules.RazorGeneral.SchemaName, - new Dictionary() - { - [Rules.RazorGeneral.RazorDefaultConfigurationProperty] = expectedConfiguration - }) - }.ToImmutableDictionary(); - - // Act - var result = DefaultWindowsRazorProjectHost.TryGetDefaultConfiguration(projectState, out var defaultConfiguration); - - // Assert - Assert.True(result); - Assert.Equal(expectedConfiguration, defaultConfiguration); - } - - [Fact] - public void TryGetLanguageVersion_FailsIfNoRule() - { - // Arrange - var projectState = new Dictionary().ToImmutableDictionary(); - - // Act - var result = DefaultWindowsRazorProjectHost.TryGetLanguageVersion(projectState, out var languageVersion); - - // Assert - Assert.False(result); - Assert.Null(languageVersion); - } - - [Fact] - public void TryGetLanguageVersion_FailsIfNoLanguageVersion() - { - // Arrange - var projectState = new Dictionary() - { - [Rules.RazorGeneral.SchemaName] = TestProjectRuleSnapshot.CreateProperties(Rules.RazorGeneral.SchemaName, new Dictionary()) - }.ToImmutableDictionary(); - - // Act - var result = DefaultWindowsRazorProjectHost.TryGetLanguageVersion(projectState, out var languageVersion); - - // Assert - Assert.False(result); - Assert.Null(languageVersion); - } - - [Fact] - public void TryGetLanguageVersion_FailsIfEmptyLanguageVersion() - { - // Arrange - var projectState = new Dictionary() - { - [Rules.RazorGeneral.SchemaName] = TestProjectRuleSnapshot.CreateProperties( - Rules.RazorGeneral.SchemaName, - new Dictionary() - { - [Rules.RazorGeneral.RazorLangVersionProperty] = string.Empty - }) - }.ToImmutableDictionary(); - - // Act - var result = DefaultWindowsRazorProjectHost.TryGetLanguageVersion(projectState, out var languageVersion); - - // Assert - Assert.False(result); - Assert.Null(languageVersion); - } - - [Fact] - public void TryGetLanguageVersion_SucceedsWithValidLanguageVersion() - { - // Arrange - var projectState = new Dictionary() - { - [Rules.RazorGeneral.SchemaName] = TestProjectRuleSnapshot.CreateProperties( - Rules.RazorGeneral.SchemaName, - new Dictionary() - { - [Rules.RazorGeneral.RazorLangVersionProperty] = "1.0" - }) - }.ToImmutableDictionary(); - - // Act - var result = DefaultWindowsRazorProjectHost.TryGetLanguageVersion(projectState, out var languageVersion); - - // Assert - Assert.True(result); - Assert.Same(RazorLanguageVersion.Version_1_0, languageVersion); - } - - [Fact] - public void TryGetLanguageVersion_SucceedsWithUnknownLanguageVersion_DefaultsToLatest() - { - // Arrange - var projectState = new Dictionary() - { - [Rules.RazorGeneral.SchemaName] = TestProjectRuleSnapshot.CreateProperties( - Rules.RazorGeneral.SchemaName, - new Dictionary() - { - [Rules.RazorGeneral.RazorLangVersionProperty] = "13.37" - }) - }.ToImmutableDictionary(); - - // Act - var result = DefaultWindowsRazorProjectHost.TryGetLanguageVersion(projectState, out var languageVersion); - - // Assert - Assert.True(result); - Assert.Same(RazorLanguageVersion.Latest, languageVersion); - } - - [Fact] - public void TryGetConfigurationItem_FailsNoRazorConfigurationRule() - { - // Arrange - var projectState = new Dictionary().ToImmutableDictionary(); - - // Act - var result = DefaultWindowsRazorProjectHost.TryGetConfigurationItem("Razor-13.37", projectState, out _); - - // Assert - Assert.False(result); - } - - [Fact] - public void TryGetConfigurationItem_FailsNoRazorConfigurationItems() - { - // Arrange - var projectState = new Dictionary() - { - [Rules.RazorConfiguration.SchemaName] = TestProjectRuleSnapshot.CreateItems( - Rules.RazorConfiguration.SchemaName, - new Dictionary>()) - }.ToImmutableDictionary(); - - // Act - var result = DefaultWindowsRazorProjectHost.TryGetConfigurationItem("Razor-13.37", projectState, out _); - - // Assert - Assert.False(result); - } - - [Fact] - public void TryGetConfigurationItem_FailsNoMatchingRazorConfigurationItems() - { - // Arrange - var projectState = new Dictionary() - { - [Rules.RazorConfiguration.SchemaName] = TestProjectRuleSnapshot.CreateItems( - Rules.RazorConfiguration.SchemaName, - new Dictionary>() - { - ["Razor-10.0"] = new Dictionary(), - }) - }.ToImmutableDictionary(); - - // Act - var result = DefaultWindowsRazorProjectHost.TryGetConfigurationItem("Razor-13.37", projectState, out _); - - // Assert - Assert.False(result); - } - - [Fact] - public void TryGetConfigurationItem_SucceedsForMatchingConfigurationItem() - { - // Arrange - var expectedConfiguration = "Razor-13.37"; - var expectedConfigurationValue = new Dictionary() - { - [Rules.RazorConfiguration.ExtensionsProperty] = "SomeExtension" - }; - var projectState = new Dictionary() - { - [Rules.RazorConfiguration.SchemaName] = TestProjectRuleSnapshot.CreateItems( - Rules.RazorConfiguration.SchemaName, - new Dictionary>() - { - [expectedConfiguration] = expectedConfigurationValue - }) - }.ToImmutableDictionary(); - - // Act - var result = DefaultWindowsRazorProjectHost.TryGetConfigurationItem(expectedConfiguration, projectState, out var configurationItem); - - // Assert - Assert.True(result); - Assert.Equal(expectedConfiguration, configurationItem.Key); - Assert.True(expectedConfigurationValue.SequenceEqual(configurationItem.Value)); - } - - [Fact] - public void GetExtensionNames_SucceedsWithNoExtensions() - { - // Arrange - var items = new ItemCollection(Rules.RazorConfiguration.SchemaName); - items.Item("Test"); - - var item = items.ToSnapshot().Items.Single(); - - // Act - var extensionNames = DefaultWindowsRazorProjectHost.GetExtensionNames(item); - - // Assert - Assert.Empty(extensionNames); - } - - [Fact] - public void GetExtensionNames_SucceedsWithEmptyExtensions() - { - // Arrange - var items = new ItemCollection(Rules.RazorConfiguration.SchemaName); - items.Item("Test"); - items.Property("Test", Rules.RazorConfiguration.ExtensionsProperty, string.Empty); - - var item = items.ToSnapshot().Items.Single(); - - // Act - var extensionNames = DefaultWindowsRazorProjectHost.GetExtensionNames(item); - - // Assert - Assert.Empty(extensionNames); - } - - [Fact] - public void GetExtensionNames_SucceedsIfSingleExtension() - { - // Arrange - var expectedExtensionName = "SomeExtensionName"; - - var items = new ItemCollection(Rules.RazorConfiguration.SchemaName); - items.Item("Test"); - items.Property("Test", Rules.RazorConfiguration.ExtensionsProperty, "SomeExtensionName"); - - var item = items.ToSnapshot().Items.Single(); - - // Act - var extensionNames = DefaultWindowsRazorProjectHost.GetExtensionNames(item); - - // Assert - var extensionName = Assert.Single(extensionNames); - Assert.Equal(expectedExtensionName, extensionName); - } - - [Fact] - public void GetExtensionNames_SucceedsIfMultipleExtensions() - { - // Arrange - var items = new ItemCollection(Rules.RazorConfiguration.SchemaName); - items.Item("Test"); - items.Property("Test", Rules.RazorConfiguration.ExtensionsProperty, "SomeExtensionName;SomeOtherExtensionName"); - - var item = items.ToSnapshot().Items.Single(); - - // Act - var extensionNames = DefaultWindowsRazorProjectHost.GetExtensionNames(item); - - // Assert - Assert.Collection( - extensionNames, - name => Assert.Equal("SomeExtensionName", name), - name => Assert.Equal("SomeOtherExtensionName", name)); - } - - [Fact] - public void TryGetExtensions_SucceedsWhenExtensionsNotFound() - { - // Arrange - var projectState = new Dictionary().ToImmutableDictionary(); - - // Act - var result = DefaultWindowsRazorProjectHost.TryGetExtensions(new[] { "Extension1", "Extension2" }, projectState, out var extensions); - - // Assert - Assert.True(result); - Assert.Empty(extensions); - } - - [Fact] - public void TryGetExtensions_SucceedsWithUnConfiguredExtensionTypes() - { - // Arrange - var projectState = new Dictionary() - { - [Rules.RazorExtension.PrimaryDataSourceItemType] = TestProjectRuleSnapshot.CreateItems( - Rules.RazorExtension.PrimaryDataSourceItemType, - new Dictionary>() - { - ["UnconfiguredExtensionName"] = new Dictionary() - }) - }.ToImmutableDictionary(); - - // Act - var result = DefaultWindowsRazorProjectHost.TryGetExtensions(new[] { "Extension1", "Extension2" }, projectState, out var extensions); - - // Assert - Assert.True(result); - Assert.Empty(extensions); - } - - [Fact] - public void TryGetExtensions_SucceedsWithSomeConfiguredExtensions() - { - // Arrange - var expectedExtension1Name = "Extension1"; - var expectedExtension2Name = "Extension2"; - var projectState = new Dictionary() - { - [Rules.RazorExtension.PrimaryDataSourceItemType] = TestProjectRuleSnapshot.CreateItems( - Rules.RazorExtension.PrimaryDataSourceItemType, - new Dictionary>() - { - ["UnconfiguredExtensionName"] = new Dictionary(), - [expectedExtension1Name] = new Dictionary(), - [expectedExtension2Name] = new Dictionary(), - }) - }.ToImmutableDictionary(); - - // Act - var result = DefaultWindowsRazorProjectHost.TryGetExtensions(new[] { expectedExtension1Name, expectedExtension2Name }, projectState, out var extensions); - - // Assert - Assert.True(result); - Assert.Collection( - extensions, - extension => Assert.Equal(expectedExtension2Name, extension.ExtensionName), - extension => Assert.Equal(expectedExtension1Name, extension.ExtensionName)); - } - - [Fact] - public void TryGetConfiguration_FailsIfNoDefaultConfiguration() - { - // Arrange - var projectState = new Dictionary().ToImmutableDictionary(); - - // Act - var result = DefaultWindowsRazorProjectHost.TryGetConfiguration(projectState, out var configuration); - - // Assert - Assert.False(result); - Assert.Null(configuration); - } - - [Fact] - public void TryGetConfiguration_FailsIfNoLanguageVersion() - { - // Arrange - var projectState = new Dictionary() - { - [Rules.RazorGeneral.SchemaName] = TestProjectRuleSnapshot.CreateProperties( - Rules.RazorGeneral.SchemaName, - new Dictionary() - { - [Rules.RazorGeneral.RazorDefaultConfigurationProperty] = "13.37" - }) - }.ToImmutableDictionary(); - - // Act - var result = DefaultWindowsRazorProjectHost.TryGetConfiguration(projectState, out var configuration); - - // Assert - Assert.False(result); - Assert.Null(configuration); - } - - [Fact] - public void TryGetConfiguration_FailsIfNoConfigurationItems() - { - // Arrange - var projectState = new Dictionary() - { - [Rules.RazorGeneral.SchemaName] = TestProjectRuleSnapshot.CreateProperties( - Rules.RazorGeneral.SchemaName, - new Dictionary() - { - [Rules.RazorGeneral.RazorDefaultConfigurationProperty] = "13.37", - [Rules.RazorGeneral.RazorLangVersionProperty] = "1.0", - }) - }.ToImmutableDictionary(); - - // Act - var result = DefaultWindowsRazorProjectHost.TryGetConfiguration(projectState, out var configuration); - - // Assert - Assert.False(result); - Assert.Null(configuration); - } - - [Fact] - public void TryGetConfiguration_SucceedsWithNoConfiguredExtensionNames() - { - // Arrange - var projectState = new Dictionary() - { - [Rules.RazorGeneral.SchemaName] = TestProjectRuleSnapshot.CreateProperties( - Rules.RazorGeneral.SchemaName, - new Dictionary() - { - [Rules.RazorGeneral.RazorDefaultConfigurationProperty] = "Razor-13.37", - [Rules.RazorGeneral.RazorLangVersionProperty] = "1.0", - }), - [Rules.RazorConfiguration.SchemaName] = TestProjectRuleSnapshot.CreateItems( - Rules.RazorConfiguration.SchemaName, - new Dictionary>() - { - ["Razor-13.37"] = new Dictionary() - }) - }.ToImmutableDictionary(); - - // Act - var result = DefaultWindowsRazorProjectHost.TryGetConfiguration(projectState, out var configuration); - - // Assert - Assert.True(result); - Assert.Equal(RazorLanguageVersion.Version_1_0, configuration.LanguageVersion); - Assert.Equal("Razor-13.37", configuration.ConfigurationName); - Assert.Empty(configuration.Extensions); - } - - [Fact] - public void TryGetConfiguration_IgnoresMissingExtension() - { - // Arrange - var projectState = new Dictionary() - { - [Rules.RazorGeneral.SchemaName] = TestProjectRuleSnapshot.CreateProperties( - Rules.RazorGeneral.SchemaName, - new Dictionary() - { - [Rules.RazorGeneral.RazorDefaultConfigurationProperty] = "13.37", - [Rules.RazorGeneral.RazorLangVersionProperty] = "1.0", - }), - [Rules.RazorConfiguration.SchemaName] = TestProjectRuleSnapshot.CreateItems( - Rules.RazorConfiguration.SchemaName, - new Dictionary>() - { - ["13.37"] = new Dictionary() - { - ["Extensions"] = "Razor-13.37" - } - }) - }.ToImmutableDictionary(); - - // Act - var result = DefaultWindowsRazorProjectHost.TryGetConfiguration(projectState, out var configuration); - - // Assert - Assert.True(result); - Assert.Empty(configuration.Extensions); - } - - // This is more of an integration test but is here to test the overall flow/functionality - [Fact] - public void TryGetConfiguration_SucceedsWithAllPreRequisites() - { - // Arrange - var expectedLanguageVersion = RazorLanguageVersion.Version_1_0; - var expectedConfigurationName = "Razor-Test"; - var expectedExtension1Name = "Extension1"; - var expectedExtension2Name = "Extension2"; - var projectState = new Dictionary() - { - [Rules.RazorGeneral.SchemaName] = TestProjectRuleSnapshot.CreateProperties( - Rules.RazorGeneral.SchemaName, - new Dictionary() - { - [Rules.RazorGeneral.RazorDefaultConfigurationProperty] = expectedConfigurationName, - [Rules.RazorGeneral.RazorLangVersionProperty] = "1.0", - }), - [Rules.RazorConfiguration.SchemaName] = TestProjectRuleSnapshot.CreateItems( - Rules.RazorConfiguration.SchemaName, - new Dictionary>() - { - ["UnconfiguredRazorConfiguration"] = new Dictionary() - { - ["Extensions"] = "Razor-9.0" - }, - [expectedConfigurationName] = new Dictionary() - { - ["Extensions"] = expectedExtension1Name + ";" + expectedExtension2Name - } - }), - [Rules.RazorExtension.PrimaryDataSourceItemType] = TestProjectRuleSnapshot.CreateItems( - Rules.RazorExtension.PrimaryDataSourceItemType, - new Dictionary>() - { - [expectedExtension1Name] = new Dictionary(), - [expectedExtension2Name] = new Dictionary(), - }) - }.ToImmutableDictionary(); - - // Act - var result = DefaultWindowsRazorProjectHost.TryGetConfiguration(projectState, out var configuration); - - // Assert - Assert.True(result); - Assert.Equal(expectedLanguageVersion, configuration.LanguageVersion); - Assert.Equal(expectedConfigurationName, configuration.ConfigurationName); - Assert.Collection( - configuration.Extensions.OrderBy(e => e.ExtensionName), - extension => Assert.Equal(expectedExtension1Name, extension.ExtensionName), - extension => Assert.Equal(expectedExtension2Name, extension.ExtensionName)); - } - - [UIFact] - public async Task DefaultRazorProjectHost_UIThread_CreateAndDispose_Succeeds() - { - // Arrange - var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath); - var host = new DefaultWindowsRazorProjectHost(services, _serviceProvider, _projectManager, TestLanguageServerFeatureOptions.Instance); - - // Act & Assert - await host.GetTestAccessor().InitializeAsync(); - Assert.Empty(_projectManager.GetProjects()); - - await host.DisposeAsync(); - Assert.Empty(_projectManager.GetProjects()); - } - - [UIFact] - public async Task DefaultRazorProjectHost_BackgroundThread_CreateAndDispose_Succeeds() - { - // Arrange - var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath); - var host = new DefaultWindowsRazorProjectHost(services, _serviceProvider, _projectManager, TestLanguageServerFeatureOptions.Instance); - - // Act & Assert - await Task.Run(async () => await host.GetTestAccessor().InitializeAsync()); - Assert.Empty(_projectManager.GetProjects()); - - await Task.Run(async () => await host.DisposeAsync()); - Assert.Empty(_projectManager.GetProjects()); - } - - [UIFact] // This can happen if the .xaml files aren't included correctly. - public async Task DefaultRazorProjectHost_OnProjectChanged_NoRulesDefined() - { - // Arrange - var changes = new TestProjectChangeDescription[] - { - }; - - var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath); - var host = new DefaultWindowsRazorProjectHost(services, _serviceProvider, _projectManager, TestLanguageServerFeatureOptions.Instance); - - var testAccessor = host.GetTestAccessor(); - - // Act & Assert - await Task.Run(async () => await testAccessor.InitializeAsync()); - Assert.Empty(_projectManager.GetProjects()); - - await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); - Assert.Empty(_projectManager.GetProjects()); - } - - [UIFact] - public async Task OnProjectChanged_ReadsProperties_InitializesProject() - { - // Arrange - _razorGeneralProperties.Property(Rules.RazorGeneral.RazorLangVersionProperty, "2.1"); - _razorGeneralProperties.Property(Rules.RazorGeneral.RazorDefaultConfigurationProperty, "MVC-2.1"); - - _configurationItems.Item("MVC-2.1"); - _configurationItems.Property("MVC-2.1", Rules.RazorConfiguration.ExtensionsProperty, "MVC-2.1;Another-Thing"); - - _extensionItems.Item("MVC-2.1"); - _extensionItems.Item("Another-Thing"); - - _razorComponentWithTargetPathItems.Item(Path.GetFileName(TestProjectData.SomeProjectComponentFile1.FilePath)); - _razorComponentWithTargetPathItems.Property(Path.GetFileName(TestProjectData.SomeProjectComponentFile1.FilePath), Rules.RazorGenerateWithTargetPath.TargetPathProperty, TestProjectData.SomeProjectComponentFile1.TargetPath); - - _razorGenerateWithTargetPathItems.Item(Path.GetFileName(TestProjectData.SomeProjectFile1.FilePath)); - _razorGenerateWithTargetPathItems.Property(Path.GetFileName(TestProjectData.SomeProjectFile1.FilePath), Rules.RazorGenerateWithTargetPath.TargetPathProperty, TestProjectData.SomeProjectFile1.TargetPath); - - _configurationGeneral.Property(WindowsRazorProjectHostBase.BaseIntermediateOutputPathPropertyName, TestProjectData.SomeProject.IntermediateOutputPath); - _configurationGeneral.Property(WindowsRazorProjectHostBase.IntermediateOutputPathPropertyName, "obj"); - - var changes = new TestProjectChangeDescription[] - { - _razorGeneralProperties.ToChange(), - _configurationItems.ToChange(), - _extensionItems.ToChange(), - _razorComponentWithTargetPathItems.ToChange(), - _razorGenerateWithTargetPathItems.ToChange(), - _configurationGeneral.ToChange(), - }; - - var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath); - - var host = new DefaultWindowsRazorProjectHost(services, _serviceProvider, _projectManager, TestLanguageServerFeatureOptions.Instance); - - var testAccessor = host.GetTestAccessor(); - - await Task.Run(async () => await testAccessor.InitializeAsync()); - Assert.Empty(_projectManager.GetProjects()); - - // Act - await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); - - // Assert - var project = Assert.Single(_projectManager.GetProjects()); - Assert.Equal(TestProjectData.SomeProject.FilePath, project.FilePath); - - Assert.Equal(RazorLanguageVersion.Version_2_1, project.Configuration.LanguageVersion); - Assert.Equal("MVC-2.1", project.Configuration.ConfigurationName); - Assert.Collection( - project.Configuration.Extensions.OrderBy(e => e.ExtensionName), - e => Assert.Equal("Another-Thing", e.ExtensionName), - e => Assert.Equal("MVC-2.1", e.ExtensionName)); - - Assert.Collection( - project.DocumentFilePaths.OrderBy(d => d), - d => - { - var document = project.GetRequiredDocument(d); - Assert.Equal(TestProjectData.SomeProjectFile1.FilePath, document.FilePath); - Assert.Equal(TestProjectData.SomeProjectFile1.TargetPath, document.TargetPath); - Assert.Equal(RazorFileKind.Legacy, document.FileKind); - }, - d => - { - var document = project.GetRequiredDocument(d); - Assert.Equal(TestProjectData.SomeProjectComponentFile1.FilePath, document.FilePath); - Assert.Equal(TestProjectData.SomeProjectComponentFile1.TargetPath, document.TargetPath); - Assert.Equal(RazorFileKind.Component, document.FileKind); - }); - - await Task.Run(async () => await host.DisposeAsync()); - Assert.Empty(_projectManager.GetProjects()); - } - - [UITheory] - // Standard setup. BaseIntermediateOutputPath ends in obj and IntermediateOutputPath starts with obj - [InlineData(@"C:\my repo root\solution folder\projectFolder\obj\", @"obj\Debug\net8.0", @"C:\my repo root\solution folder\projectFolder\obj\Debug\net8.0")] - // ArtifactsPath in use as ../artifacts - [InlineData(@"C:\my repo root\solution folder\projectFolder\../artifacts\obj\projectName\", @"C:\my repo root\solution folder\projectFolder\../artifacts\obj\projectName\debug", @"C:\my repo root\solution folder\artifacts\obj\projectName\debug")] - // .... and ArtifactsPivot is $(ArtifactsPivot)\_MyCustomPivot - [InlineData(@"C:\my repo root\solution folder\projectFolder\../artifacts\obj\projectName\", @"C:\my repo root\solution folder\projectFolder\../artifacts\obj\projectName\_MyCustomPivot", @"C:\my repo root\solution folder\artifacts\obj\projectName\_MyCustomPivot")] - // Set BIOP to ..\..\artifacts\obj\$(MSBuildProjectFolder), pre-ArtifactsPath existing - [InlineData(@"C:\my repo root\solution folder\projectFolder\..\..\artifacts\obj\projectName", @"..\..\artifacts\obj\projectName\Debug\net8.0", @"C:\my repo root\artifacts\obj\projectName\Debug\net8.0")] - public void IntermediateOutputPathCalculationHandlesRelativePaths(string baseIntermediateOutputPath, string intermediateOutputPath, string expectedCombinedIOP) - { - var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath); - var host = new DefaultWindowsRazorProjectHost(services, _serviceProvider, _projectManager, TestLanguageServerFeatureOptions.Instance); - - var state = TestProjectRuleSnapshot.CreateProperties( - WindowsRazorProjectHostBase.ConfigurationGeneralSchemaName, - new Dictionary() - { - [WindowsRazorProjectHostBase.IntermediateOutputPathPropertyName] = intermediateOutputPath, - [WindowsRazorProjectHostBase.BaseIntermediateOutputPathPropertyName] = baseIntermediateOutputPath, - }); - - var dict = ImmutableDictionary.Empty; - dict = dict.Add(WindowsRazorProjectHostBase.ConfigurationGeneralSchemaName, state); - - var result = host.GetTestAccessor().GetIntermediateOutputPathFromProjectChange(dict, - out var combinedIntermediateOutputPath); - - Assert.True(result); - Assert.Equal(expectedCombinedIOP, combinedIntermediateOutputPath); - } - - [UITheory] - // This is what we see for Razor class libraries - [InlineData("obj/", @"C:\my repo root\solution folder\projectFolder\", @"obj\Debug\net8.0", @"C:\my repo root\solution folder\projectFolder\obj\Debug\net8.0")] - [InlineData("../obj/", @"C:\my repo root\solution folder\projectFolder\", @"obj\Debug\net8.0", @"C:\my repo root\solution folder\projectFolder\obj\Debug\net8.0")] - [InlineData("../obj", @"C:\my repo root\solution folder\projectFolder\", @"obj\Debug\net8.0", @"C:\my repo root\solution folder\projectFolder\obj\Debug\net8.0")] - public void IntermediateOutputPathCalculationHandlesRelativePaths_BaseIntermediateOutputPath(string baseIntermediateOutputPath, string msbuildProjectDirectoryPropertyName, string intermediateOutputPath, string expectedCombinedIOP) - { - var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath); - var host = new DefaultWindowsRazorProjectHost(services, _serviceProvider, _projectManager, TestLanguageServerFeatureOptions.Instance); - - var state = TestProjectRuleSnapshot.CreateProperties( - WindowsRazorProjectHostBase.ConfigurationGeneralSchemaName, - new Dictionary() - { - [WindowsRazorProjectHostBase.IntermediateOutputPathPropertyName] = intermediateOutputPath, - [WindowsRazorProjectHostBase.BaseIntermediateOutputPathPropertyName] = baseIntermediateOutputPath, - [WindowsRazorProjectHostBase.MSBuildProjectDirectoryPropertyName] = msbuildProjectDirectoryPropertyName, - }); - - var dict = ImmutableDictionary.Empty; - dict = dict.Add(WindowsRazorProjectHostBase.ConfigurationGeneralSchemaName, state); - - var result = host.GetTestAccessor().GetIntermediateOutputPathFromProjectChange(dict, - out var combinedIntermediateOutputPath); - - Assert.True(result); - Assert.Equal(expectedCombinedIOP, combinedIntermediateOutputPath); - } - - [UIFact] - public async Task OnProjectChanged_NoVersionFound_DoesNotInitializeProject() - { - // Arrange - _razorGeneralProperties.Property(Rules.RazorGeneral.RazorLangVersionProperty, ""); - _razorGeneralProperties.Property(Rules.RazorGeneral.RazorDefaultConfigurationProperty, ""); - - _configurationItems.Item("TestConfiguration"); - - _extensionItems.Item("TestExtension"); - - _razorGenerateWithTargetPathItems.Item(Path.GetFileName(TestProjectData.SomeProjectFile1.FilePath)); - - var changes = new TestProjectChangeDescription[] - { - _razorGeneralProperties.ToChange(), - _configurationItems.ToChange(), - _extensionItems.ToChange(), - _razorComponentWithTargetPathItems.ToChange(), - _razorGenerateWithTargetPathItems.ToChange(), - }; - - var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath); - var host = new DefaultWindowsRazorProjectHost(services, _serviceProvider, _projectManager, TestLanguageServerFeatureOptions.Instance); - - var testAccessor = host.GetTestAccessor(); - - await Task.Run(async () => await testAccessor.InitializeAsync()); - Assert.Empty(_projectManager.GetProjects()); - - // Act - await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); - - // Assert - Assert.Empty(_projectManager.GetProjects()); - - await Task.Run(async () => await host.DisposeAsync()); - Assert.Empty(_projectManager.GetProjects()); - } - - [UIFact] - public async Task OnProjectChanged_UpdateProject_MarksSolutionOpen() - { - // Arrange - _razorGeneralProperties.Property(Rules.RazorGeneral.RazorLangVersionProperty, "2.1"); - _razorGeneralProperties.Property(Rules.RazorGeneral.RazorDefaultConfigurationProperty, "MVC-2.1"); - - _configurationItems.Item("MVC-2.1"); - _configurationItems.Property("MVC-2.1", Rules.RazorConfiguration.ExtensionsProperty, "MVC-2.1;Another-Thing"); - - _extensionItems.Item("MVC-2.1"); - _extensionItems.Item("Another-Thing"); - - _razorComponentWithTargetPathItems.Item(Path.GetFileName(TestProjectData.SomeProjectComponentFile1.FilePath)); - _razorComponentWithTargetPathItems.Property(Path.GetFileName(TestProjectData.SomeProjectComponentFile1.FilePath), Rules.RazorComponentWithTargetPath.TargetPathProperty, TestProjectData.SomeProjectComponentFile1.TargetPath); - - _razorComponentWithTargetPathItems.Item(Path.GetFileName(TestProjectData.SomeProjectComponentImportFile1.FilePath)); - _razorComponentWithTargetPathItems.Property(Path.GetFileName(TestProjectData.SomeProjectComponentImportFile1.FilePath), Rules.RazorComponentWithTargetPath.TargetPathProperty, TestProjectData.SomeProjectComponentImportFile1.TargetPath); - - _razorGenerateWithTargetPathItems.Item(Path.GetFileName(TestProjectData.SomeProjectFile1.FilePath)); - _razorGenerateWithTargetPathItems.Property(Path.GetFileName(TestProjectData.SomeProjectFile1.FilePath), Rules.RazorGenerateWithTargetPath.TargetPathProperty, TestProjectData.SomeProjectFile1.TargetPath); - - _configurationGeneral.Property(WindowsRazorProjectHostBase.BaseIntermediateOutputPathPropertyName, TestProjectData.SomeProject.IntermediateOutputPath); - _configurationGeneral.Property(WindowsRazorProjectHostBase.IntermediateOutputPathPropertyName, "obj"); - - var changes = new TestProjectChangeDescription[] - { - _razorGeneralProperties.ToChange(), - _configurationItems.ToChange(), - _extensionItems.ToChange(), - _razorComponentWithTargetPathItems.ToChange(), - _razorGenerateWithTargetPathItems.ToChange(), - _configurationGeneral.ToChange(), - }; - - var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath); - var host = new DefaultWindowsRazorProjectHost(services, _serviceProvider, _projectManager, TestLanguageServerFeatureOptions.Instance); - - var testAccessor = host.GetTestAccessor(); - - await Task.Run(async () => await testAccessor.InitializeAsync()); - Assert.Empty(_projectManager.GetProjects()); - - // Act - 1 - await _projectManager.UpdateAsync(updater => - { - updater.SolutionClosed(); - }); - - await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); - - // Assert - 1 - Assert.False(_projectManager.IsSolutionClosing); - } - - [UIFact] - public async Task OnProjectChanged_UpdateProject_Succeeds() - { - // Arrange - _razorGeneralProperties.Property(Rules.RazorGeneral.RazorLangVersionProperty, "2.1"); - _razorGeneralProperties.Property(Rules.RazorGeneral.RazorDefaultConfigurationProperty, "MVC-2.1"); - - _configurationItems.Item("MVC-2.1"); - _configurationItems.Property("MVC-2.1", Rules.RazorConfiguration.ExtensionsProperty, "MVC-2.1;Another-Thing"); - - _extensionItems.Item("MVC-2.1"); - _extensionItems.Item("Another-Thing"); - - _razorComponentWithTargetPathItems.Item(Path.GetFileName(TestProjectData.SomeProjectComponentFile1.FilePath)); - _razorComponentWithTargetPathItems.Property(Path.GetFileName(TestProjectData.SomeProjectComponentFile1.FilePath), Rules.RazorComponentWithTargetPath.TargetPathProperty, TestProjectData.SomeProjectComponentFile1.TargetPath); - - _razorComponentWithTargetPathItems.Item(Path.GetFileName(TestProjectData.SomeProjectComponentImportFile1.FilePath)); - _razorComponentWithTargetPathItems.Property(Path.GetFileName(TestProjectData.SomeProjectComponentImportFile1.FilePath), Rules.RazorComponentWithTargetPath.TargetPathProperty, TestProjectData.SomeProjectComponentImportFile1.TargetPath); - - _razorGenerateWithTargetPathItems.Item(Path.GetFileName(TestProjectData.SomeProjectFile1.FilePath)); - _razorGenerateWithTargetPathItems.Property(Path.GetFileName(TestProjectData.SomeProjectFile1.FilePath), Rules.RazorGenerateWithTargetPath.TargetPathProperty, TestProjectData.SomeProjectFile1.TargetPath); - - _configurationGeneral.Property(WindowsRazorProjectHostBase.BaseIntermediateOutputPathPropertyName, TestProjectData.SomeProject.IntermediateOutputPath); - _configurationGeneral.Property(WindowsRazorProjectHostBase.IntermediateOutputPathPropertyName, "obj"); - - var changes = new TestProjectChangeDescription[] - { - _razorGeneralProperties.ToChange(), - _configurationItems.ToChange(), - _extensionItems.ToChange(), - _razorComponentWithTargetPathItems.ToChange(), - _razorGenerateWithTargetPathItems.ToChange(), - _configurationGeneral.ToChange(), - }; - - var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath); - var host = new DefaultWindowsRazorProjectHost(services, _serviceProvider, _projectManager, TestLanguageServerFeatureOptions.Instance); - - var testAccessor = host.GetTestAccessor(); - - await Task.Run(async () => await testAccessor.InitializeAsync()); - Assert.Empty(_projectManager.GetProjects()); - - // Act - 1 - await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); - - // Assert - 1 - var project = Assert.Single(_projectManager.GetProjects()); - Assert.Equal(TestProjectData.SomeProject.FilePath, project.FilePath); - - Assert.Equal(RazorLanguageVersion.Version_2_1, project.Configuration.LanguageVersion); - Assert.Equal("MVC-2.1", project.Configuration.ConfigurationName); - Assert.Collection( - project.Configuration.Extensions.OrderBy(e => e.ExtensionName), - e => Assert.Equal("Another-Thing", e.ExtensionName), - e => Assert.Equal("MVC-2.1", e.ExtensionName)); - - Assert.Collection( - project.DocumentFilePaths.OrderBy(d => d), - d => - { - var document = project.GetRequiredDocument(d); - Assert.Equal(TestProjectData.SomeProjectComponentImportFile1.FilePath, document.FilePath); - Assert.Equal(TestProjectData.SomeProjectComponentImportFile1.TargetPath, document.TargetPath); - Assert.Equal(RazorFileKind.ComponentImport, document.FileKind); - }, - d => - { - var document = project.GetRequiredDocument(d); - Assert.Equal(TestProjectData.SomeProjectFile1.FilePath, document.FilePath); - Assert.Equal(TestProjectData.SomeProjectFile1.TargetPath, document.TargetPath); - }, - d => - { - var document = project.GetRequiredDocument(d); - Assert.Equal(TestProjectData.SomeProjectComponentFile1.FilePath, document.FilePath); - Assert.Equal(TestProjectData.SomeProjectComponentFile1.TargetPath, document.TargetPath); - Assert.Equal(RazorFileKind.Component, document.FileKind); - }); - - // Act - 2 - _razorGeneralProperties.Property(Rules.RazorGeneral.RazorLangVersionProperty, "2.0"); - _razorGeneralProperties.Property(Rules.RazorGeneral.RazorDefaultConfigurationProperty, "MVC-2.0"); - _configurationItems.RemoveItem("MVC-2.1"); - _configurationItems.Item("MVC-2.0", new Dictionary() { { "Extensions", "MVC-2.0;Another-Thing" }, }); - _extensionItems.Item("MVC-2.0"); - _razorComponentWithTargetPathItems.Item(TestProjectData.AnotherProjectNestedComponentFile3.FilePath, new Dictionary() - { - { Rules.RazorGenerateWithTargetPath.TargetPathProperty, TestProjectData.AnotherProjectNestedComponentFile3.TargetPath }, - }); - _razorGenerateWithTargetPathItems.Item(TestProjectData.AnotherProjectNestedFile3.FilePath, new Dictionary() - { - { Rules.RazorGenerateWithTargetPath.TargetPathProperty, TestProjectData.AnotherProjectNestedFile3.TargetPath }, - }); - - changes = new TestProjectChangeDescription[] - { - _razorGeneralProperties.ToChange(changes[0].After), - _configurationItems.ToChange(changes[1].After), - _extensionItems.ToChange(changes[2].After), - _razorComponentWithTargetPathItems.ToChange(changes[3].After), - _razorGenerateWithTargetPathItems.ToChange(changes[4].After), - _configurationGeneral.ToChange(changes[5].After), - }; - - await Task.Run(async () => await host.GetTestAccessor().OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); - - // Assert - 2 - project = Assert.Single(_projectManager.GetProjects()); - Assert.Equal(TestProjectData.SomeProject.FilePath, project.FilePath); - - Assert.Equal(RazorLanguageVersion.Version_2_0, project.Configuration.LanguageVersion); - Assert.Equal("MVC-2.0", project.Configuration.ConfigurationName); - Assert.Collection( - project.Configuration.Extensions.OrderBy(e => e.ExtensionName), - e => Assert.Equal("Another-Thing", e.ExtensionName), - e => Assert.Equal("MVC-2.0", e.ExtensionName)); - - Assert.Collection( - project.DocumentFilePaths.OrderBy(d => d), - d => - { - var document = project.GetRequiredDocument(d); - Assert.Equal(TestProjectData.AnotherProjectNestedFile3.FilePath, document.FilePath); - Assert.Equal(TestProjectData.AnotherProjectNestedFile3.TargetPath, document.TargetPath); - Assert.Equal(RazorFileKind.Legacy, document.FileKind); - }, - d => - { - var document = project.GetRequiredDocument(d); - Assert.Equal(TestProjectData.AnotherProjectNestedComponentFile3.FilePath, document.FilePath); - Assert.Equal(TestProjectData.AnotherProjectNestedComponentFile3.TargetPath, document.TargetPath); - Assert.Equal(RazorFileKind.Component, document.FileKind); - }, - d => - { - var document = project.GetRequiredDocument(d); - Assert.Equal(TestProjectData.SomeProjectComponentImportFile1.FilePath, document.FilePath); - Assert.Equal(TestProjectData.SomeProjectComponentImportFile1.TargetPath, document.TargetPath); - Assert.Equal(RazorFileKind.ComponentImport, document.FileKind); - }, - d => - { - var document = project.GetRequiredDocument(d); - Assert.Equal(TestProjectData.SomeProjectFile1.FilePath, document.FilePath); - Assert.Equal(TestProjectData.SomeProjectFile1.TargetPath, document.TargetPath); - Assert.Equal(RazorFileKind.Legacy, document.FileKind); - }, - d => - { - var document = project.GetRequiredDocument(d); - Assert.Equal(TestProjectData.SomeProjectComponentFile1.FilePath, document.FilePath); - Assert.Equal(TestProjectData.SomeProjectComponentFile1.TargetPath, document.TargetPath); - Assert.Equal(RazorFileKind.Component, document.FileKind); - }); - - await Task.Run(async () => await host.DisposeAsync()); - Assert.Empty(_projectManager.GetProjects()); - } - - [UIFact] - public async Task OnProjectChanged_VersionRemoved_DeInitializesProject() - { - // Arrange - _razorGeneralProperties.Property(Rules.RazorGeneral.RazorLangVersionProperty, "2.1"); - _razorGeneralProperties.Property(Rules.RazorGeneral.RazorDefaultConfigurationProperty, "MVC-2.1"); - - _configurationItems.Item("MVC-2.1"); - _configurationItems.Property("MVC-2.1", Rules.RazorConfiguration.ExtensionsProperty, "MVC-2.1;Another-Thing"); - - _extensionItems.Item("MVC-2.1"); - _extensionItems.Item("Another-Thing"); - - _razorComponentWithTargetPathItems.Item(Path.GetFileName(TestProjectData.SomeProjectComponentFile1.FilePath)); - _razorComponentWithTargetPathItems.Property(Path.GetFileName(TestProjectData.SomeProjectComponentFile1.FilePath), Rules.RazorGenerateWithTargetPath.TargetPathProperty, TestProjectData.SomeProjectComponentFile1.TargetPath); - - _razorGenerateWithTargetPathItems.Item(Path.GetFileName(TestProjectData.SomeProjectFile1.FilePath)); - _razorGenerateWithTargetPathItems.Property(Path.GetFileName(TestProjectData.SomeProjectFile1.FilePath), Rules.RazorGenerateWithTargetPath.TargetPathProperty, TestProjectData.SomeProjectFile1.TargetPath); - - _configurationGeneral.Property(WindowsRazorProjectHostBase.BaseIntermediateOutputPathPropertyName, TestProjectData.SomeProject.IntermediateOutputPath); - _configurationGeneral.Property(WindowsRazorProjectHostBase.IntermediateOutputPathPropertyName, "obj"); - - var changes = new TestProjectChangeDescription[] - { - _razorGeneralProperties.ToChange(), - _configurationItems.ToChange(), - _extensionItems.ToChange(), - _razorComponentWithTargetPathItems.ToChange(), - _razorGenerateWithTargetPathItems.ToChange(), - _configurationGeneral.ToChange(), - }; - - var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath); - var host = new DefaultWindowsRazorProjectHost(services, _serviceProvider, _projectManager, TestLanguageServerFeatureOptions.Instance); - - var testAccessor = host.GetTestAccessor(); - - await Task.Run(async () => await testAccessor.InitializeAsync()); - Assert.Empty(_projectManager.GetProjects()); - - // Act - 1 - await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); - - // Assert - 1 - var snapshot = Assert.Single(_projectManager.GetProjects()); - Assert.Equal(TestProjectData.SomeProject.FilePath, snapshot.FilePath); - - Assert.Equal(RazorLanguageVersion.Version_2_1, snapshot.Configuration.LanguageVersion); - Assert.Equal("MVC-2.1", snapshot.Configuration.ConfigurationName); - Assert.Collection( - snapshot.Configuration.Extensions.OrderBy(e => e.ExtensionName), - e => Assert.Equal("Another-Thing", e.ExtensionName), - e => Assert.Equal("MVC-2.1", e.ExtensionName)); - - // Act - 2 - _razorGeneralProperties.Property(Rules.RazorGeneral.RazorLangVersionProperty, ""); - _razorGeneralProperties.Property(Rules.RazorGeneral.RazorDefaultConfigurationProperty, ""); - - changes = new TestProjectChangeDescription[] - { - _razorGeneralProperties.ToChange(changes[0].After), - _configurationItems.ToChange(changes[1].After), - _extensionItems.ToChange(changes[2].After), - _razorComponentWithTargetPathItems.ToChange(changes[3].After), - _razorGenerateWithTargetPathItems.ToChange(changes[4].After), - }; - - await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); - - // Assert - 2 - Assert.Empty(_projectManager.GetProjects()); - - await Task.Run(async () => await host.DisposeAsync()); - Assert.Empty(_projectManager.GetProjects()); - } - - [UIFact] - public async Task OnProjectChanged_AfterDispose_IgnoresUpdate() - { - // Arrange - _razorGeneralProperties.Property(Rules.RazorGeneral.RazorLangVersionProperty, "2.1"); - _razorGeneralProperties.Property(Rules.RazorGeneral.RazorDefaultConfigurationProperty, "MVC-2.1"); - - _configurationItems.Item("MVC-2.1"); - _configurationItems.Property("MVC-2.1", Rules.RazorConfiguration.ExtensionsProperty, "MVC-2.1;Another-Thing"); - - _extensionItems.Item("MVC-2.1"); - _extensionItems.Item("Another-Thing"); - - _razorComponentWithTargetPathItems.Item(Path.GetFileName(TestProjectData.SomeProjectComponentFile1.FilePath)); - _razorComponentWithTargetPathItems.Property(Path.GetFileName(TestProjectData.SomeProjectComponentFile1.FilePath), Rules.RazorGenerateWithTargetPath.TargetPathProperty, TestProjectData.SomeProjectComponentFile1.TargetPath); - - _razorGenerateWithTargetPathItems.Item(Path.GetFileName(TestProjectData.SomeProjectFile1.FilePath)); - _razorGenerateWithTargetPathItems.Property(Path.GetFileName(TestProjectData.SomeProjectFile1.FilePath), Rules.RazorGenerateWithTargetPath.TargetPathProperty, TestProjectData.SomeProjectFile1.TargetPath); - - _configurationGeneral.Property(WindowsRazorProjectHostBase.BaseIntermediateOutputPathPropertyName, TestProjectData.SomeProject.IntermediateOutputPath); - _configurationGeneral.Property(WindowsRazorProjectHostBase.IntermediateOutputPathPropertyName, "obj"); - - var changes = new TestProjectChangeDescription[] - { - _razorGeneralProperties.ToChange(), - _configurationItems.ToChange(), - _extensionItems.ToChange(), - _razorComponentWithTargetPathItems.ToChange(), - _razorGenerateWithTargetPathItems.ToChange(), - _configurationGeneral.ToChange(), - }; - - var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath); - var host = new DefaultWindowsRazorProjectHost(services, _serviceProvider, _projectManager, TestLanguageServerFeatureOptions.Instance); - - var testAccessor = host.GetTestAccessor(); - - await Task.Run(async () => await testAccessor.InitializeAsync()); - Assert.Empty(_projectManager.GetProjects()); - - // Act - 1 - await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); - - // Assert - 1 - var snapshot = Assert.Single(_projectManager.GetProjects()); - Assert.Equal(TestProjectData.SomeProject.FilePath, snapshot.FilePath); - - Assert.Equal(RazorLanguageVersion.Version_2_1, snapshot.Configuration.LanguageVersion); - Assert.Equal("MVC-2.1", snapshot.Configuration.ConfigurationName); - Assert.Collection( - snapshot.Configuration.Extensions.OrderBy(e => e.ExtensionName), - e => Assert.Equal("Another-Thing", e.ExtensionName), - e => Assert.Equal("MVC-2.1", e.ExtensionName)); - - // Act - 2 - await Task.Run(async () => await host.DisposeAsync()); - - // Assert - 2 - Assert.Empty(_projectManager.GetProjects()); - - // Act - 3 - _razorGeneralProperties.Property(Rules.RazorGeneral.RazorLangVersionProperty, "2.0"); - _razorGeneralProperties.Property(Rules.RazorGeneral.RazorDefaultConfigurationProperty, "MVC-2.0"); - _configurationItems.Item("MVC-2.0", new Dictionary() { { "Extensions", "MVC-2.0;Another-Thing" }, }); - - changes = new TestProjectChangeDescription[] - { - _razorGeneralProperties.ToChange(changes[0].After), - _configurationItems.ToChange(changes[1].After), - _extensionItems.ToChange(changes[2].After), - _razorComponentWithTargetPathItems.ToChange(changes[3].After), - _razorGenerateWithTargetPathItems.ToChange(changes[4].After), - }; - - await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); - - // Assert - 3 - Assert.Empty(_projectManager.GetProjects()); - } - - [UIFact] - public async Task OnProjectRenamed_RemovesHostProject_CopiesConfiguration() - { - // Arrange - _razorGeneralProperties.Property(Rules.RazorGeneral.RazorLangVersionProperty, "2.1"); - _razorGeneralProperties.Property(Rules.RazorGeneral.RazorDefaultConfigurationProperty, "MVC-2.1"); - - _configurationItems.Item("MVC-2.1"); - _configurationItems.Property("MVC-2.1", Rules.RazorConfiguration.ExtensionsProperty, "MVC-2.1;Another-Thing"); - - _extensionItems.Item("MVC-2.1"); - _extensionItems.Item("Another-Thing"); - - _razorComponentWithTargetPathItems.Item(Path.GetFileName(TestProjectData.SomeProjectComponentFile1.FilePath)); - _razorComponentWithTargetPathItems.Property(Path.GetFileName(TestProjectData.SomeProjectComponentFile1.FilePath), Rules.RazorGenerateWithTargetPath.TargetPathProperty, TestProjectData.SomeProjectComponentFile1.TargetPath); - - _razorGenerateWithTargetPathItems.Item(Path.GetFileName(TestProjectData.SomeProjectFile1.FilePath)); - _razorGenerateWithTargetPathItems.Property(Path.GetFileName(TestProjectData.SomeProjectFile1.FilePath), Rules.RazorGenerateWithTargetPath.TargetPathProperty, TestProjectData.SomeProjectFile1.TargetPath); - - _configurationGeneral.Property(WindowsRazorProjectHostBase.BaseIntermediateOutputPathPropertyName, TestProjectData.SomeProject.IntermediateOutputPath); - _configurationGeneral.Property(WindowsRazorProjectHostBase.IntermediateOutputPathPropertyName, "obj"); - - var changes = new TestProjectChangeDescription[] - { - _razorGeneralProperties.ToChange(), - _configurationItems.ToChange(), - _extensionItems.ToChange(), - _razorComponentWithTargetPathItems.ToChange(), - _razorGenerateWithTargetPathItems.ToChange(), - _configurationGeneral.ToChange(), - }; - - var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath); - - var host = new DefaultWindowsRazorProjectHost(services, _serviceProvider, _projectManager, TestLanguageServerFeatureOptions.Instance); - - var testAccessor = host.GetTestAccessor(); - - await Task.Run(async () => await testAccessor.InitializeAsync()); - Assert.Empty(_projectManager.GetProjects()); - - // Act - 1 - await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); - - // Assert - 1 - var project = Assert.Single(_projectManager.GetProjects()); - Assert.Equal(TestProjectData.SomeProject.FilePath, project.FilePath); - Assert.Same("MVC-2.1", project.Configuration.ConfigurationName); - - Assert.Collection( - project.DocumentFilePaths.OrderBy(d => d), - d => - { - var document = project.GetRequiredDocument(d); - Assert.Equal(TestProjectData.SomeProjectFile1.FilePath, document.FilePath); - Assert.Equal(TestProjectData.SomeProjectFile1.TargetPath, document.TargetPath); - Assert.Equal(RazorFileKind.Legacy, document.FileKind); - }, - d => - { - var document = project.GetRequiredDocument(d); - Assert.Equal(TestProjectData.SomeProjectComponentFile1.FilePath, document.FilePath); - Assert.Equal(TestProjectData.SomeProjectComponentFile1.TargetPath, document.TargetPath); - Assert.Equal(RazorFileKind.Component, document.FileKind); - }); - - // Act - 2 - services.UnconfiguredProject.FullPath = TestProjectData.AnotherProject.FilePath; - await Task.Run(async () => await testAccessor.OnProjectRenamingAsync(TestProjectData.SomeProject.FilePath, TestProjectData.AnotherProject.FilePath)); - - // Assert - 1 - project = Assert.Single(_projectManager.GetProjects()); - Assert.Equal(TestProjectData.AnotherProject.FilePath, project.FilePath); - Assert.Same("MVC-2.1", project.Configuration.ConfigurationName); - - Assert.Collection( - project.DocumentFilePaths.OrderBy(d => d), - d => - { - var document = project.GetRequiredDocument(d); - Assert.Equal(TestProjectData.SomeProjectFile1.FilePath, document.FilePath); - Assert.Equal(TestProjectData.SomeProjectFile1.TargetPath, document.TargetPath); - Assert.Equal(RazorFileKind.Legacy, document.FileKind); - }, - d => - { - var document = project.GetRequiredDocument(d); - Assert.Equal(TestProjectData.SomeProjectComponentFile1.FilePath, document.FilePath); - Assert.Equal(TestProjectData.SomeProjectComponentFile1.TargetPath, document.TargetPath); - Assert.Equal(RazorFileKind.Component, document.FileKind); - }); - - await Task.Run(async () => await host.DisposeAsync()); - Assert.Empty(_projectManager.GetProjects()); - } - - [UIFact] - public async Task OnProjectChanged_ChangeIntermediateOutputPath_RemovesAndAddsProject() - { - // Arrange - _razorGeneralProperties.Property(Rules.RazorGeneral.RazorLangVersionProperty, "2.1"); - _razorGeneralProperties.Property(Rules.RazorGeneral.RazorDefaultConfigurationProperty, "MVC-2.1"); - - _configurationItems.Item("MVC-2.1"); - _configurationItems.Property("MVC-2.1", Rules.RazorConfiguration.ExtensionsProperty, "MVC-2.1;Another-Thing"); - - _extensionItems.Item("MVC-2.1"); - _extensionItems.Item("Another-Thing"); - - _razorComponentWithTargetPathItems.Item(Path.GetFileName(TestProjectData.SomeProjectComponentFile1.FilePath)); - _razorComponentWithTargetPathItems.Property(Path.GetFileName(TestProjectData.SomeProjectComponentFile1.FilePath), Rules.RazorComponentWithTargetPath.TargetPathProperty, TestProjectData.SomeProjectComponentFile1.TargetPath); - - _razorComponentWithTargetPathItems.Item(Path.GetFileName(TestProjectData.SomeProjectComponentImportFile1.FilePath)); - _razorComponentWithTargetPathItems.Property(Path.GetFileName(TestProjectData.SomeProjectComponentImportFile1.FilePath), Rules.RazorComponentWithTargetPath.TargetPathProperty, TestProjectData.SomeProjectComponentImportFile1.TargetPath); - - _razorGenerateWithTargetPathItems.Item(Path.GetFileName(TestProjectData.SomeProjectFile1.FilePath)); - _razorGenerateWithTargetPathItems.Property(Path.GetFileName(TestProjectData.SomeProjectFile1.FilePath), Rules.RazorGenerateWithTargetPath.TargetPathProperty, TestProjectData.SomeProjectFile1.TargetPath); - - _configurationGeneral.Property(WindowsRazorProjectHostBase.BaseIntermediateOutputPathPropertyName, TestProjectData.SomeProject.IntermediateOutputPath); - _configurationGeneral.Property(WindowsRazorProjectHostBase.IntermediateOutputPathPropertyName, "obj"); - - var changes = new TestProjectChangeDescription[] - { - _razorGeneralProperties.ToChange(), - _configurationItems.ToChange(), - _extensionItems.ToChange(), - _razorComponentWithTargetPathItems.ToChange(), - _razorGenerateWithTargetPathItems.ToChange(), - _configurationGeneral.ToChange(), - }; - - var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath); - var host = new DefaultWindowsRazorProjectHost(services, _serviceProvider, _projectManager, TestLanguageServerFeatureOptions.Instance); - - var testAccessor = host.GetTestAccessor(); - - await Task.Run(async () => await testAccessor.InitializeAsync()); - Assert.Empty(_projectManager.GetProjects()); - - // Act - 1 - await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); - - // Assert - 1 - var project = Assert.Single(_projectManager.GetProjects()); - Assert.Equal(TestProjectData.SomeProject.FilePath, project.FilePath); - - Assert.Equal(RazorLanguageVersion.Version_2_1, project.Configuration.LanguageVersion); - Assert.Equal("MVC-2.1", project.Configuration.ConfigurationName); - Assert.Collection( - project.Configuration.Extensions.OrderBy(e => e.ExtensionName), - e => Assert.Equal("Another-Thing", e.ExtensionName), - e => Assert.Equal("MVC-2.1", e.ExtensionName)); - - Assert.Collection( - project.DocumentFilePaths.OrderBy(d => d), - d => - { - var document = project.GetRequiredDocument(d); - Assert.Equal(TestProjectData.SomeProjectComponentImportFile1.FilePath, document.FilePath); - Assert.Equal(TestProjectData.SomeProjectComponentImportFile1.TargetPath, document.TargetPath); - Assert.Equal(RazorFileKind.ComponentImport, document.FileKind); - }, - d => - { - var document = project.GetRequiredDocument(d); - Assert.Equal(TestProjectData.SomeProjectFile1.FilePath, document.FilePath); - Assert.Equal(TestProjectData.SomeProjectFile1.TargetPath, document.TargetPath); - }, - d => - { - var document = project.GetRequiredDocument(d); - Assert.Equal(TestProjectData.SomeProjectComponentFile1.FilePath, document.FilePath); - Assert.Equal(TestProjectData.SomeProjectComponentFile1.TargetPath, document.TargetPath); - Assert.Equal(RazorFileKind.Component, document.FileKind); - }); - - // Act - 2 - _configurationGeneral.Property(WindowsRazorProjectHostBase.IntermediateOutputPathPropertyName, "obj2"); - - changes = new TestProjectChangeDescription[] - { - _razorGeneralProperties.ToChange(changes[0].After), - _configurationItems.ToChange(changes[1].After), - _extensionItems.ToChange(changes[2].After), - _razorComponentWithTargetPathItems.ToChange(changes[3].After), - _razorGenerateWithTargetPathItems.ToChange(changes[4].After), - _configurationGeneral.ToChange(changes[5].After), - }; - - await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); - - // Assert - 2 - // Changing intermediate output path is effectively removing the old project and adding a new one. - project = Assert.Single(_projectManager.GetProjects()); - Assert.Equal(TestProjectData.SomeProject.FilePath, project.FilePath); - Assert.Equal(TestProjectData.SomeProject.IntermediateOutputPath + "2", project.IntermediateOutputPath); - - await Task.Run(async () => await host.DisposeAsync()); - Assert.Empty(_projectManager.GetProjects()); - } -} diff --git a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/FallbackWindowsRazorProjectHostTest.cs b/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/FallbackWindowsRazorProjectHostTest.cs deleted file mode 100644 index c800bf45b46..00000000000 --- a/src/Razor/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/FallbackWindowsRazorProjectHostTest.cs +++ /dev/null @@ -1,682 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -#nullable disable - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Razor.Language; -using Microsoft.AspNetCore.Razor.Test.Common.ProjectSystem; -using Microsoft.AspNetCore.Razor.Test.Common.VisualStudio; -using Microsoft.AspNetCore.Razor.Test.Common.Workspaces; -using Microsoft.CodeAnalysis.Razor.ProjectSystem; -using Microsoft.VisualStudio.ProjectSystem.Properties; -using Xunit; -using Xunit.Abstractions; -using ItemReference = Microsoft.VisualStudio.Razor.ProjectSystem.ManagedProjectSystemSchema.ItemReference; - -namespace Microsoft.VisualStudio.Razor.ProjectSystem; - -public class FallbackWindowsRazorProjectHostTest : VisualStudioWorkspaceTestBase -{ - private readonly IServiceProvider _serviceProvider; - private readonly ItemCollection _referenceItems; - private readonly TestProjectSnapshotManager _projectManager; - private readonly ItemCollection _contentItems; - private readonly ItemCollection _noneItems; - - public FallbackWindowsRazorProjectHostTest(ITestOutputHelper testOutput) - : base(testOutput) - { - _serviceProvider = VsMocks.CreateServiceProvider(static b => - b.AddComponentModel(static b => - { - var startupInitializer = new RazorStartupInitializer(TestLanguageServerFeatureOptions.Instance, []); - b.AddExport(startupInitializer); - })); - - _projectManager = CreateProjectSnapshotManager(); - - _referenceItems = new ItemCollection(ManagedProjectSystemSchema.ResolvedCompilationReference.SchemaName); - _contentItems = new ItemCollection(ManagedProjectSystemSchema.ContentItem.SchemaName); - _noneItems = new ItemCollection(ManagedProjectSystemSchema.NoneItem.SchemaName); - } - - [Fact] - public void GetChangedAndRemovedDocuments_ReturnsChangedContentAndNoneItems() - { - // Arrange - var afterChangeContentItems = new ItemCollection(ManagedProjectSystemSchema.ContentItem.SchemaName); - _contentItems.Item("Index.cshtml", new Dictionary() - { - [ItemReference.LinkPropertyName] = "NewIndex.cshtml", - [ItemReference.FullPathPropertyName] = "C:\\From\\Index.cshtml", - }); - var afterChangeNoneItems = new ItemCollection(ManagedProjectSystemSchema.NoneItem.SchemaName); - _noneItems.Item("About.cshtml", new Dictionary() - { - [ItemReference.LinkPropertyName] = "NewAbout.cshtml", - [ItemReference.FullPathPropertyName] = "C:\\From\\About.cshtml", - }); - var services = new TestProjectSystemServices("C:\\To\\Test.csproj"); - - var host = new TestFallbackRazorProjectHost(services, _serviceProvider, _projectManager); - var changes = new TestProjectChangeDescription[] - { - afterChangeContentItems.ToChange(_contentItems.ToSnapshot()), - afterChangeNoneItems.ToChange(_noneItems.ToSnapshot()), - }; - var update = services.CreateUpdate(changes).Value; - - // Act - var result = host.GetChangedAndRemovedDocuments(update); - - // Assert - Assert.Collection( - result, - document => - { - Assert.Equal("C:\\From\\Index.cshtml", document.FilePath); - Assert.Equal("C:\\To\\NewIndex.cshtml", document.TargetPath); - }, - document => - { - Assert.Equal("C:\\From\\About.cshtml", document.FilePath); - Assert.Equal("C:\\To\\NewAbout.cshtml", document.TargetPath); - }); - } - - [Fact] - public void GetCurrentDocuments_ReturnsContentAndNoneItems() - { - // Arrange - _contentItems.Item("Index.cshtml", new Dictionary() - { - [ItemReference.LinkPropertyName] = "NewIndex.cshtml", - [ItemReference.FullPathPropertyName] = "C:\\From\\Index.cshtml", - }); - _noneItems.Item("About.cshtml", new Dictionary() - { - [ItemReference.LinkPropertyName] = "NewAbout.cshtml", - [ItemReference.FullPathPropertyName] = "C:\\From\\About.cshtml", - }); - var services = new TestProjectSystemServices("C:\\To\\Test.csproj"); - - var host = new TestFallbackRazorProjectHost(services, _serviceProvider, _projectManager); - var changes = new TestProjectChangeDescription[] - { - _contentItems.ToChange(), - _noneItems.ToChange(), - }; - var update = services.CreateUpdate(changes).Value; - - // Act - var result = host.GetCurrentDocuments(update); - - // Assert - Assert.Collection( - result, - document => - { - Assert.Equal("C:\\From\\Index.cshtml", document.FilePath); - Assert.Equal("C:\\To\\NewIndex.cshtml", document.TargetPath); - }, - document => - { - Assert.Equal("C:\\From\\About.cshtml", document.FilePath); - Assert.Equal("C:\\To\\NewAbout.cshtml", document.TargetPath); - }); - } - - // This is for the legacy SDK case, we don't support components. - [Fact] - public void GetCurrentDocuments_IgnoresDotRazorFiles() - { - // Arrange - _contentItems.Item("Index.razor", new Dictionary() - { - [ItemReference.LinkPropertyName] = "NewIndex.razor", - [ItemReference.FullPathPropertyName] = "C:\\From\\Index.razor", - }); - _noneItems.Item("About.razor", new Dictionary() - { - [ItemReference.LinkPropertyName] = "NewAbout.razor", - [ItemReference.FullPathPropertyName] = "C:\\From\\About.razor", - }); - var services = new TestProjectSystemServices("C:\\To\\Test.csproj"); - - var host = new TestFallbackRazorProjectHost(services, _serviceProvider, _projectManager); - var changes = new TestProjectChangeDescription[] - { - _contentItems.ToChange(), - _noneItems.ToChange(), - }; - var update = services.CreateUpdate(changes).Value; - - // Act - var result = host.GetCurrentDocuments(update); - - // Assert - Assert.Empty(result); - } - - [Fact] - public void TryGetRazorDocument_NoFilePath_ReturnsFalse() - { - // Arrange - var services = new TestProjectSystemServices("C:\\To\\Test.csproj"); - - var host = new TestFallbackRazorProjectHost(services, _serviceProvider, _projectManager); - var itemState = new Dictionary() - { - [ItemReference.LinkPropertyName] = "Index.cshtml", - }.ToImmutableDictionary(); - - // Act - var result = host.TryGetRazorDocument(itemState, out var document); - - // Assert - Assert.False(result); - Assert.Null(document); - } - - [Fact] - public void TryGetRazorDocument_NonRazorFilePath_ReturnsFalse() - { - // Arrange - var services = new TestProjectSystemServices("C:\\Path\\Test.csproj"); - - var host = new TestFallbackRazorProjectHost(services, _serviceProvider, _projectManager); - var itemState = new Dictionary() - { - [ItemReference.FullPathPropertyName] = "C:\\Path\\site.css", - }.ToImmutableDictionary(); - - // Act - var result = host.TryGetRazorDocument(itemState, out var document); - - // Assert - Assert.False(result); - Assert.Null(document); - } - - [Fact] - public void TryGetRazorDocument_NonRazorTargetPath_ReturnsFalse() - { - // Arrange - var services = new TestProjectSystemServices("C:\\Path\\To\\Test.csproj"); - - var host = new TestFallbackRazorProjectHost(services, _serviceProvider, _projectManager); - var itemState = new Dictionary() - { - [ItemReference.LinkPropertyName] = "site.html", - [ItemReference.FullPathPropertyName] = "C:\\Path\\From\\Index.cshtml", - }.ToImmutableDictionary(); - - // Act - var result = host.TryGetRazorDocument(itemState, out var document); - - // Assert - Assert.False(result); - Assert.Null(document); - } - - [Fact] - public void TryGetRazorDocument_JustFilePath_ReturnsTrue() - { - // Arrange - var expectedPath = "C:\\Path\\Index.cshtml"; - var services = new TestProjectSystemServices("C:\\Path\\Test.csproj"); - - var host = new TestFallbackRazorProjectHost(services, _serviceProvider, _projectManager); - var itemState = new Dictionary() - { - [ItemReference.FullPathPropertyName] = expectedPath, - }.ToImmutableDictionary(); - - // Act - var result = host.TryGetRazorDocument(itemState, out var document); - - // Assert - Assert.True(result); - Assert.Equal(expectedPath, document.FilePath); - Assert.Equal(expectedPath, document.TargetPath); - } - - [Fact] - public void TryGetRazorDocument_LinkedFilepath_ReturnsTrue() - { - // Arrange - var expectedFullPath = "C:\\Path\\From\\Index.cshtml"; - var expectedTargetPath = "C:\\Path\\To\\Index.cshtml"; - var services = new TestProjectSystemServices("C:\\Path\\To\\Test.csproj"); - - var host = new TestFallbackRazorProjectHost(services, _serviceProvider, _projectManager); - var itemState = new Dictionary() - { - [ItemReference.LinkPropertyName] = "Index.cshtml", - [ItemReference.FullPathPropertyName] = expectedFullPath, - }.ToImmutableDictionary(); - - // Act - var result = host.TryGetRazorDocument(itemState, out var document); - - // Assert - Assert.True(result); - Assert.Equal(expectedFullPath, document.FilePath); - Assert.Equal(expectedTargetPath, document.TargetPath); - } - - [Fact] - public void TryGetRazorDocument_SetsLegacyFileKind() - { - // Arrange - var expectedFullPath = "C:\\Path\\From\\Index.cshtml"; - var expectedTargetPath = "C:\\Path\\To\\Index.cshtml"; - var services = new TestProjectSystemServices("C:\\Path\\To\\Test.csproj"); - - var host = new TestFallbackRazorProjectHost(services, _serviceProvider, _projectManager); - var itemState = new Dictionary() - { - [ItemReference.LinkPropertyName] = "Index.cshtml", - [ItemReference.FullPathPropertyName] = expectedFullPath, - }.ToImmutableDictionary(); - - // Act - var result = host.TryGetRazorDocument(itemState, out var document); - - // Assert - Assert.True(result); - Assert.Equal(expectedFullPath, document.FilePath); - Assert.Equal(expectedTargetPath, document.TargetPath); - Assert.Equal(RazorFileKind.Legacy, document.FileKind); - } - - [UIFact] - public async Task FallbackRazorProjectHost_UIThread_CreateAndDispose_Succeeds() - { - // Arrange - var services = new TestProjectSystemServices("C:\\To\\Test.csproj"); - - var host = new TestFallbackRazorProjectHost(services, _serviceProvider, _projectManager); - - // Act & Assert - await host.GetTestAccessor().InitializeAsync(); - Assert.Empty(_projectManager.GetProjects()); - - await host.DisposeAsync(); - Assert.Empty(_projectManager.GetProjects()); - } - - [UIFact] - public async Task FallbackRazorProjectHost_BackgroundThread_CreateAndDispose_Succeeds() - { - // Arrange - var services = new TestProjectSystemServices("Test.csproj"); - - var host = new TestFallbackRazorProjectHost(services, _serviceProvider, _projectManager); - - // Act & Assert - await Task.Run(async () => await host.GetTestAccessor().InitializeAsync()); - Assert.Empty(_projectManager.GetProjects()); - - await Task.Run(async () => await host.DisposeAsync()); - Assert.Empty(_projectManager.GetProjects()); - } - - [UIFact] // This can happen if the .xaml files aren't included correctly. - public async Task OnProjectChanged_NoRulesDefined() - { - // Arrange - var changes = new TestProjectChangeDescription[] - { - }; - - var services = new TestProjectSystemServices("Test.csproj"); - - var host = new TestFallbackRazorProjectHost(services, _serviceProvider, _projectManager) - { - AssemblyVersion = new Version(2, 0), - }; - - var testAccessor = host.GetTestAccessor(); - - // Act & Assert - await Task.Run(async () => await testAccessor.InitializeAsync()); - Assert.Empty(_projectManager.GetProjects()); - - await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); - Assert.Empty(_projectManager.GetProjects()); - } - - [UIFact] - public async Task OnProjectChanged_ReadsProperties_InitializesProject() - { - // Arrange - _referenceItems.Item("c:\\nuget\\Microsoft.AspNetCore.Mvc.razor.dll"); - _contentItems.Item("Index.cshtml", new Dictionary() - { - [ItemReference.FullPathPropertyName] = "C:\\Path\\Index.cshtml", - }); - _noneItems.Item("About.cshtml", new Dictionary() - { - [ItemReference.FullPathPropertyName] = "C:\\Path\\About.cshtml", - }); - - var changes = new TestProjectChangeDescription[] - { - _referenceItems.ToChange(), - _contentItems.ToChange(), - _noneItems.ToChange(), - }; - - var services = new TestProjectSystemServices("C:\\Path\\Test.csproj"); - - var host = new TestFallbackRazorProjectHost(services, _serviceProvider, _projectManager) - { - AssemblyVersion = new Version(2, 0), // Mock for reading the assembly's version - }; - - var testAccessor = host.GetTestAccessor(); - - await Task.Run(async () => await testAccessor.InitializeAsync()); - Assert.Empty(_projectManager.GetProjects()); - - // Act - await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); - - // Assert - var snapshot = Assert.Single(_projectManager.GetProjects()); - Assert.Equal("C:\\Path\\Test.csproj", snapshot.FilePath); - Assert.Same(FallbackRazorConfiguration.MVC_2_0, snapshot.Configuration); - - Assert.Collection( - snapshot.DocumentFilePaths.OrderBy(d => d), - filePath => Assert.Equal("C:\\Path\\About.cshtml", filePath), - filePath => Assert.Equal("C:\\Path\\Index.cshtml", filePath)); - - await Task.Run(async () => await host.DisposeAsync()); - Assert.Empty(_projectManager.GetProjects()); - } - - [UIFact] - public async Task OnProjectChanged_NoAssemblyFound_DoesNotInitializeProject() - { - // Arrange - var changes = new TestProjectChangeDescription[] - { - _referenceItems.ToChange(), - }; - var services = new TestProjectSystemServices("Test.csproj"); - - var host = new TestFallbackRazorProjectHost(services, _serviceProvider, _projectManager); - - var testAccessor = host.GetTestAccessor(); - - await Task.Run(async () => await testAccessor.InitializeAsync()); - Assert.Empty(_projectManager.GetProjects()); - - // Act - await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); - - // Assert - Assert.Empty(_projectManager.GetProjects()); - - await Task.Run(async () => await host.DisposeAsync()); - Assert.Empty(_projectManager.GetProjects()); - } - - [UIFact] - public async Task OnProjectChanged_AssemblyFoundButCannotReadVersion_DoesNotInitializeProject() - { - // Arrange - _referenceItems.Item("c:\\nuget\\Microsoft.AspNetCore.Mvc.razor.dll"); - - var changes = new TestProjectChangeDescription[] - { - _referenceItems.ToChange(), - }; - - var services = new TestProjectSystemServices("Test.csproj"); - - var host = new TestFallbackRazorProjectHost(services, _serviceProvider, _projectManager); - - var testAccessor = host.GetTestAccessor(); - - await Task.Run(async () => await testAccessor.InitializeAsync()); - Assert.Empty(_projectManager.GetProjects()); - - // Act - await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); - - // Assert - Assert.Empty(_projectManager.GetProjects()); - - await Task.Run(async () => await host.DisposeAsync()); - Assert.Empty(_projectManager.GetProjects()); - } - - [UIFact] - public async Task OnProjectChanged_UpdateProject_Succeeds() - { - // Arrange - _referenceItems.Item("c:\\nuget\\Microsoft.AspNetCore.Mvc.razor.dll"); - var afterChangeContentItems = new ItemCollection(ManagedProjectSystemSchema.ContentItem.SchemaName); - _contentItems.Item("Index.cshtml", new Dictionary() - { - [ItemReference.FullPathPropertyName] = "C:\\Path\\Index.cshtml", - }); - - var initialChanges = new TestProjectChangeDescription[] - { - _referenceItems.ToChange(), - _contentItems.ToChange(), - }; - var changes = new TestProjectChangeDescription[] - { - _referenceItems.ToChange(), - afterChangeContentItems.ToChange(_contentItems.ToSnapshot()), - }; - - var services = new TestProjectSystemServices("C:\\Path\\Test.csproj"); - - var host = new TestFallbackRazorProjectHost(services, _serviceProvider, _projectManager) - { - AssemblyVersion = new Version(2, 0), - }; - - var testAccessor = host.GetTestAccessor(); - - await Task.Run(async () => await testAccessor.InitializeAsync()); - Assert.Empty(_projectManager.GetProjects()); - - // Act - 1 - await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(initialChanges))); - - // Assert - 1 - var snapshot = Assert.Single(_projectManager.GetProjects()); - Assert.Equal("C:\\Path\\Test.csproj", snapshot.FilePath); - Assert.Same(FallbackRazorConfiguration.MVC_2_0, snapshot.Configuration); - var filePath = Assert.Single(snapshot.DocumentFilePaths); - Assert.Equal("C:\\Path\\Index.cshtml", filePath); - - // Act - 2 - host.AssemblyVersion = new Version(1, 0); - await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); - - // Assert - 2 - snapshot = Assert.Single(_projectManager.GetProjects()); - Assert.Equal("C:\\Path\\Test.csproj", snapshot.FilePath); - Assert.Same(FallbackRazorConfiguration.MVC_1_0, snapshot.Configuration); - Assert.Empty(snapshot.DocumentFilePaths); - - await Task.Run(async () => await host.DisposeAsync()); - Assert.Empty(_projectManager.GetProjects()); - } - - [UIFact] - public async Task OnProjectChanged_VersionRemoved_DeinitializesProject() - { - // Arrange - _referenceItems.Item("c:\\nuget\\Microsoft.AspNetCore.Mvc.razor.dll"); - - var changes = new TestProjectChangeDescription[] - { - _referenceItems.ToChange(), - }; - - var services = new TestProjectSystemServices("Test.csproj"); - - var host = new TestFallbackRazorProjectHost(services, _serviceProvider, _projectManager) - { - AssemblyVersion = new Version(2, 0), - }; - - var testAccessor = host.GetTestAccessor(); - - await Task.Run(async () => await testAccessor.InitializeAsync()); - Assert.Empty(_projectManager.GetProjects()); - - // Act - 1 - await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); - - // Assert - 1 - var snapshot = Assert.Single(_projectManager.GetProjects()); - Assert.Equal("Test.csproj", snapshot.FilePath); - Assert.Same(FallbackRazorConfiguration.MVC_2_0, snapshot.Configuration); - - // Act - 2 - host.AssemblyVersion = null; - await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); - - // Assert - 2 - Assert.Empty(_projectManager.GetProjects()); - - await Task.Run(async () => await host.DisposeAsync()); - Assert.Empty(_projectManager.GetProjects()); - } - - [UIFact] - public async Task OnProjectChanged_AfterDispose_IgnoresUpdate() - { - // Arrange - _referenceItems.Item("c:\\nuget\\Microsoft.AspNetCore.Mvc.razor.dll"); - _contentItems.Item("Index.cshtml", new Dictionary() - { - [ItemReference.FullPathPropertyName] = "C:\\Path\\Index.cshtml", - }); - - var changes = new TestProjectChangeDescription[] - { - _referenceItems.ToChange(), - _contentItems.ToChange(), - }; - - var services = new TestProjectSystemServices("C:\\Path\\Test.csproj"); - - var host = new TestFallbackRazorProjectHost(services, _serviceProvider, _projectManager) - { - AssemblyVersion = new Version(2, 0), - }; - - var testAccessor = host.GetTestAccessor(); - - await Task.Run(async () => await testAccessor.InitializeAsync()); - Assert.Empty(_projectManager.GetProjects()); - - // Act - 1 - await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); - - // Assert - 1 - var snapshot = Assert.Single(_projectManager.GetProjects()); - Assert.Equal("C:\\Path\\Test.csproj", snapshot.FilePath); - Assert.Same(FallbackRazorConfiguration.MVC_2_0, snapshot.Configuration); - var filePath = Assert.Single(snapshot.DocumentFilePaths); - Assert.Equal("C:\\Path\\Index.cshtml", filePath); - - // Act - 2 - await Task.Run(async () => await host.DisposeAsync()); - - // Assert - 2 - Assert.Empty(_projectManager.GetProjects()); - - // Act - 3 - host.AssemblyVersion = new Version(1, 1); - await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); - - // Assert - 3 - Assert.Empty(_projectManager.GetProjects()); - } - - [UIFact] - public async Task OnProjectRenamed_RemovesHostProject_CopiesConfiguration() - { - // Arrange - _referenceItems.Item("c:\\nuget\\Microsoft.AspNetCore.Mvc.razor.dll"); - - var changes = new TestProjectChangeDescription[] - { - _referenceItems.ToChange(), - }; - - var services = new TestProjectSystemServices("Test.csproj"); - - var host = new TestFallbackRazorProjectHost(services, _serviceProvider, _projectManager) - { - AssemblyVersion = new Version(2, 0), // Mock for reading the assembly's version - }; - - var testAccessor = host.GetTestAccessor(); - - await Task.Run(async () => await testAccessor.InitializeAsync()); - Assert.Empty(_projectManager.GetProjects()); - - // Act - 1 - await Task.Run(async () => await testAccessor.OnProjectChangedAsync(string.Empty, services.CreateUpdate(changes))); - - // Assert - 1 - var snapshot = Assert.Single(_projectManager.GetProjects()); - Assert.Equal("Test.csproj", snapshot.FilePath); - Assert.Same(FallbackRazorConfiguration.MVC_2_0, snapshot.Configuration); - - // Act - 2 - services.UnconfiguredProject.FullPath = "Test2.csproj"; - await Task.Run(async () => await testAccessor.OnProjectRenamingAsync("Test.csproj", "Test2.csproj")); - - // Assert - 1 - snapshot = Assert.Single(_projectManager.GetProjects()); - Assert.Equal("Test2.csproj", snapshot.FilePath); - Assert.Same(FallbackRazorConfiguration.MVC_2_0, snapshot.Configuration); - - await Task.Run(async () => await host.DisposeAsync()); - Assert.Empty(_projectManager.GetProjects()); - } - - private class TestFallbackRazorProjectHost : FallbackWindowsRazorProjectHost - { - internal TestFallbackRazorProjectHost( - IUnconfiguredProjectCommonServices commonServices, - IServiceProvider serviceProvider, - ProjectSnapshotManager projectManager) - : base(commonServices, serviceProvider, projectManager, TestLanguageServerFeatureOptions.Instance) - { - } - - public Version AssemblyVersion { get; set; } - - protected override Version GetAssemblyVersion(string filePath) - { - return AssemblyVersion; - } - - protected override bool TryGetIntermediateOutputPath(IImmutableDictionary state, [NotNullWhen(true)] out string path) - { - path = "obj"; - return true; - } - } -} diff --git a/src/Razor/test/Microsoft.VisualStudio.Razor.IntegrationTests/AbstractRazorEditorTest.cs b/src/Razor/test/Microsoft.VisualStudio.Razor.IntegrationTests/AbstractRazorEditorTest.cs index 8c1bf0124b7..31ca6668ecb 100644 --- a/src/Razor/test/Microsoft.VisualStudio.Razor.IntegrationTests/AbstractRazorEditorTest.cs +++ b/src/Razor/test/Microsoft.VisualStudio.Razor.IntegrationTests/AbstractRazorEditorTest.cs @@ -11,8 +11,6 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Razor; using Microsoft.CodeAnalysis.Razor.Logging; -using Microsoft.Internal.VisualStudio.Shell.Interop; -using Microsoft.VisualStudio.Settings; using Microsoft.VisualStudio.Shell; using Xunit; using Xunit.Abstractions; @@ -67,7 +65,6 @@ public override async Task InitializeAsync() // Razor extension doesn't launch until a razor file is opened, so wait for it to equalize await TestServices.Workspace.WaitForProjectSystemAsync(ControlledHangMitigatingCancellationToken); - EnsureLSPEditorEnabled(); await EnsureTextViewRolesAsync(ControlledHangMitigatingCancellationToken); await EnsureExtensionInstalledAsync(ControlledHangMitigatingCancellationToken); @@ -150,15 +147,6 @@ public override async Task DisposeAsync() await base.DisposeAsync(); } - private static void EnsureLSPEditorEnabled() - { - var settingsManager = (ISettingsManager)ServiceProvider.GlobalProvider.GetService(typeof(SVsSettingsPersistenceManager)); - Assumes.Present(settingsManager); - - var useLegacyEditor = settingsManager.GetValueOrDefault(WellKnownSettingNames.UseLegacyASPNETCoreEditor); - Assert.False(useLegacyEditor, "Expected the Legacy Razor Editor to be disabled, but it was enabled"); - } - private async Task EnsureTextViewRolesAsync(CancellationToken cancellationToken) { var textView = await TestServices.Editor.GetActiveTextViewAsync(cancellationToken);