From 95d41507fc78f45934ed11c47df94c1c0772fa2e Mon Sep 17 00:00:00 2001 From: "N. Taylor Mullen" Date: Thu, 17 May 2018 12:48:11 -0700 Subject: [PATCH] Add `ProjectPathProvider` abstract for document tracker creation. - First iteration of live share replaced the document tracker factory entirely; however, this will be prone to breaking changes in the future when me make changes to document tracker to not rely on a file path. To pre-emptively prevent breaking changes I added a project path provider that can be overridden in the live share case. Note that one big difference here between old and new is that instead of being a MEF service implementation for the project path resolution we're bringing that to the Workspace service level. - Added tests to validate the two flows of the default project path provider. --- .../DefaultProjectPathProvider.cs | 41 ++++++++++++++++ .../DefaultProjectPathProviderFactory.cs | 39 +++++++++++++++ ...faultVisualStudioDocumentTrackerFactory.cs | 18 +++---- ...sualStudioDocumentTrackerFactoryFactory.cs | 12 ++--- .../ProjectPathProvider.cs | 13 +++++ .../DefaultProjectPathProviderTest.cs | 48 +++++++++++++++++++ 6 files changed, 151 insertions(+), 20 deletions(-) create mode 100644 src/Microsoft.VisualStudio.Editor.Razor/DefaultProjectPathProvider.cs create mode 100644 src/Microsoft.VisualStudio.Editor.Razor/DefaultProjectPathProviderFactory.cs create mode 100644 src/Microsoft.VisualStudio.Editor.Razor/ProjectPathProvider.cs create mode 100644 test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultProjectPathProviderTest.cs diff --git a/src/Microsoft.VisualStudio.Editor.Razor/DefaultProjectPathProvider.cs b/src/Microsoft.VisualStudio.Editor.Razor/DefaultProjectPathProvider.cs new file mode 100644 index 0000000000..95f01de201 --- /dev/null +++ b/src/Microsoft.VisualStudio.Editor.Razor/DefaultProjectPathProvider.cs @@ -0,0 +1,41 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.VisualStudio.Text; + +namespace Microsoft.VisualStudio.Editor.Razor +{ + internal class DefaultProjectPathProvider : ProjectPathProvider + { + private readonly TextBufferProjectService _projectService; + + public DefaultProjectPathProvider(TextBufferProjectService projectService) + { + if (projectService == null) + { + throw new ArgumentNullException(nameof(projectService)); + } + + _projectService = projectService; + } + + public override bool TryGetProjectPath(ITextBuffer textBuffer, out string filePath) + { + if (textBuffer == null) + { + throw new ArgumentNullException(nameof(textBuffer)); + } + + var project = _projectService.GetHostProject(textBuffer); + if (project == null) + { + filePath = null; + return false; + } + + filePath = _projectService.GetProjectPath(project); + return true; + } + } +} diff --git a/src/Microsoft.VisualStudio.Editor.Razor/DefaultProjectPathProviderFactory.cs b/src/Microsoft.VisualStudio.Editor.Razor/DefaultProjectPathProviderFactory.cs new file mode 100644 index 0000000000..bcd67304c1 --- /dev/null +++ b/src/Microsoft.VisualStudio.Editor.Razor/DefaultProjectPathProviderFactory.cs @@ -0,0 +1,39 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.ComponentModel.Composition; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Host.Mef; +using Microsoft.CodeAnalysis.Razor; + +namespace Microsoft.VisualStudio.Editor.Razor +{ + [System.Composition.Shared] + [ExportWorkspaceService(typeof(ProjectPathProvider), ServiceLayer.Default)] + internal class DefaultProjectPathProviderFactory : IWorkspaceServiceFactory + { + private readonly TextBufferProjectService _projectService; + + [ImportingConstructor] + public DefaultProjectPathProviderFactory(TextBufferProjectService projectService) + { + if (projectService == null) + { + throw new ArgumentNullException(nameof(projectService)); + } + + _projectService = projectService; + } + + public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices) + { + if (workspaceServices == null) + { + throw new ArgumentNullException(nameof(workspaceServices)); + } + + return new DefaultProjectPathProvider(_projectService); + } + } +} diff --git a/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioDocumentTrackerFactory.cs b/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioDocumentTrackerFactory.cs index 7005ce059e..5d04b9d479 100644 --- a/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioDocumentTrackerFactory.cs +++ b/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioDocumentTrackerFactory.cs @@ -13,8 +13,8 @@ namespace Microsoft.VisualStudio.Editor.Razor { internal class DefaultVisualStudioDocumentTrackerFactory : VisualStudioDocumentTrackerFactory { - private readonly TextBufferProjectService _projectService; private readonly ITextDocumentFactoryService _textDocumentFactory; + private readonly ProjectPathProvider _projectPathProvider; private readonly Workspace _workspace; private readonly ImportDocumentManager _importDocumentManager; private readonly ForegroundDispatcher _foregroundDispatcher; @@ -25,7 +25,7 @@ namespace Microsoft.VisualStudio.Editor.Razor ForegroundDispatcher foregroundDispatcher, ProjectSnapshotManager projectManager, WorkspaceEditorSettings workspaceEditorSettings, - TextBufferProjectService projectService, + ProjectPathProvider projectPathProvider, ITextDocumentFactoryService textDocumentFactory, ImportDocumentManager importDocumentManager, Workspace workspace) @@ -45,9 +45,9 @@ namespace Microsoft.VisualStudio.Editor.Razor throw new ArgumentNullException(nameof(workspaceEditorSettings)); } - if (projectService == null) + if (projectPathProvider == null) { - throw new ArgumentNullException(nameof(projectService)); + throw new ArgumentNullException(nameof(projectPathProvider)); } if (textDocumentFactory == null) @@ -68,7 +68,7 @@ namespace Microsoft.VisualStudio.Editor.Razor _foregroundDispatcher = foregroundDispatcher; _projectManager = projectManager; _workspaceEditorSettings = workspaceEditorSettings; - _projectService = projectService; + _projectPathProvider = projectPathProvider; _textDocumentFactory = textDocumentFactory; _importDocumentManager = importDocumentManager; _workspace = workspace; @@ -87,16 +87,12 @@ namespace Microsoft.VisualStudio.Editor.Razor return null; } - var filePath = textDocument.FilePath; - var project = _projectService.GetHostProject(textBuffer); - if (project == null) + if (!_projectPathProvider.TryGetProjectPath(textBuffer, out var projectPath)) { - Debug.Fail("Text buffer should belong to a project."); return null; } - var projectPath = _projectService.GetProjectPath(project); - + var filePath = textDocument.FilePath; var tracker = new DefaultVisualStudioDocumentTracker(_foregroundDispatcher, filePath, projectPath, _projectManager, _workspaceEditorSettings, _workspace, textBuffer, _importDocumentManager); return tracker; diff --git a/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioDocumentTrackerFactoryFactory.cs b/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioDocumentTrackerFactoryFactory.cs index a2af5b8d17..eed3ca436e 100644 --- a/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioDocumentTrackerFactoryFactory.cs +++ b/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioDocumentTrackerFactoryFactory.cs @@ -17,13 +17,11 @@ namespace Microsoft.VisualStudio.Editor.Razor internal class DefaultVisualStudioDocumentTrackerFactoryFactory : ILanguageServiceFactory { private readonly ForegroundDispatcher _foregroundDispatcher; - private readonly TextBufferProjectService _projectService; private readonly ITextDocumentFactoryService _textDocumentFactory; [ImportingConstructor] public DefaultVisualStudioDocumentTrackerFactoryFactory( ForegroundDispatcher foregroundDispatcher, - TextBufferProjectService projectService, ITextDocumentFactoryService textDocumentFactory) { if (foregroundDispatcher == null) @@ -31,18 +29,12 @@ namespace Microsoft.VisualStudio.Editor.Razor throw new ArgumentNullException(nameof(foregroundDispatcher)); } - if (projectService == null) - { - throw new ArgumentNullException(nameof(projectService)); - } - if (textDocumentFactory == null) { throw new ArgumentNullException(nameof(textDocumentFactory)); } _foregroundDispatcher = foregroundDispatcher; - _projectService = projectService; _textDocumentFactory = textDocumentFactory; } @@ -57,11 +49,13 @@ namespace Microsoft.VisualStudio.Editor.Razor var workspaceEditorSettings = languageServices.GetRequiredService(); var importDocumentManager = languageServices.GetRequiredService(); + var projectPathProvider = languageServices.WorkspaceServices.GetRequiredService(); + return new DefaultVisualStudioDocumentTrackerFactory( _foregroundDispatcher, projectManager, workspaceEditorSettings, - _projectService, + projectPathProvider, _textDocumentFactory, importDocumentManager, languageServices.WorkspaceServices.Workspace); diff --git a/src/Microsoft.VisualStudio.Editor.Razor/ProjectPathProvider.cs b/src/Microsoft.VisualStudio.Editor.Razor/ProjectPathProvider.cs new file mode 100644 index 0000000000..396e470ff6 --- /dev/null +++ b/src/Microsoft.VisualStudio.Editor.Razor/ProjectPathProvider.cs @@ -0,0 +1,13 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis.Host; +using Microsoft.VisualStudio.Text; + +namespace Microsoft.VisualStudio.Editor.Razor +{ + internal abstract class ProjectPathProvider : IWorkspaceService + { + public abstract bool TryGetProjectPath(ITextBuffer textBuffer, out string filePath); + } +} diff --git a/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultProjectPathProviderTest.cs b/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultProjectPathProviderTest.cs new file mode 100644 index 0000000000..1633b6bda3 --- /dev/null +++ b/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultProjectPathProviderTest.cs @@ -0,0 +1,48 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.VisualStudio.Text; +using Moq; +using Xunit; + +namespace Microsoft.VisualStudio.Editor.Razor +{ + public class DefaultProjectPathProviderTest + { + [Fact] + public void TryGetProjectPath_ReturnsFalseIfNoProject() + { + // Arrange + var projectPathProvider = new DefaultProjectPathProvider(Mock.Of()); + var textBuffer = Mock.Of(); + + // Act + var result = projectPathProvider.TryGetProjectPath(textBuffer, out var filePath); + + // Assert + Assert.False(result); + Assert.Null(filePath); + } + + [Fact] + public void TryGetProjectPath_ReturnsTrueIfProject() + { + // Arrange + var expectedProjectPath = "/my/project/path.csproj"; + var projectService = new Mock(); + projectService.Setup(service => service.GetHostProject(It.IsAny())) + .Returns(new object()); + projectService.Setup(service => service.GetProjectPath(It.IsAny())) + .Returns(expectedProjectPath); + var projectPathProvider = new DefaultProjectPathProvider(projectService.Object); + var textBuffer = Mock.Of(); + + // Act + var result = projectPathProvider.TryGetProjectPath(textBuffer, out var filePath); + + // Assert + Assert.True(result); + Assert.Equal(expectedProjectPath, filePath); + } + } +}