From baa71375d08faef8e8ac5ba1f199b46ddfb7d389 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Fri, 19 Oct 2018 21:39:43 -0700 Subject: [PATCH] Add and process notifications for Imports (#2656) This builds support for tracking the effect of changes to imports on other documents, and completes our model for being able to keep generated code up to date. --- .../ProjectSnapshotManagerBenchmarkBase.cs | 16 +- .../DefaultRazorProjectFileSystem.cs | 4 + ...aultProjectSnapshotProjectEngineFactory.cs | 9 +- .../ProjectSnapshotProjectEngineFactory.cs | 31 +- .../ProjectSystem/DefaultProjectSnapshot.cs | 31 +- .../DefaultProjectSnapshotManager.cs | 122 ++--- .../ProjectSystem/DocumentState.cs | 12 + .../ProjectSystem/EphemeralProjectSnapshot.cs | 20 + .../LiveShareProjectSnapshotBase.cs | 4 + .../ProjectSystem/ProjectChangeEventArgs.cs | 26 +- .../ProjectSystem/ProjectEngineTracker.cs | 30 +- .../ProjectSystem/ProjectSnapshot.cs | 11 + .../ProjectSystem/ProjectState.cs | 167 +++++-- .../RazorServiceBase.cs | 10 + .../BackgroundDocumentGenerator.cs | 28 +- .../DefaultDocumentSnapshotTest.cs | 23 +- .../DefaultProjectSnapshotTest.cs | 131 +++-- .../ProjectSystem/DocumentStateTest.cs | 69 ++- .../ProjectSystem/ProjectStateTest.cs | 457 ++++++++++++++++-- .../Shared/TestImportProjectFeature.cs | 32 ++ .../Shared/TestProjectData.cs | 56 +++ ...TestProjectSnapshotProjectEngineFactory.cs | 6 +- .../Shared/WorkspaceTestBase.cs | 79 +++ .../SingleThreadedForegroundDispatcher.cs | 47 ++ .../Xunit/ForegroundDispatcherTestBase.cs | 40 -- ...ultImportDocumentManagerIntegrationTest.cs | 23 +- .../DefaultImportDocumentManagerTest.cs | 29 +- .../DefaultVisualStudioDocumentTrackerTest.cs | 31 +- .../DefaultVisualStudioRazorParserTest.cs | 2 +- .../EditorDocumentManagerBaseTest.cs | 20 +- .../EditorDocumentManagerListenerTest.cs | 12 +- .../Documents/EditorDocumentTest.cs | 5 +- .../ForegroundDispatcherWorkspaceTestBase.cs | 12 + .../BackgroundDocumentGeneratorTest.cs | 24 +- .../VisualStudioFileChangeTrackerTest.cs | 10 +- ...lStudio.LanguageServices.Razor.Test.csproj | 3 + .../DefaultProjectSnapshotManagerTest.cs | 48 +- .../DefaultRazorProjectHostTest.cs | 82 ++-- .../FallbackRazorProjectHostTest.cs | 5 +- ...rkspaceProjectSnapshotChangeTriggerTest.cs | 5 +- ...UpdatesProjectSnapshotChangeTriggerTest.cs | 6 +- ...dio.Mac.LanguageServices.Razor.Test.csproj | 3 + 42 files changed, 1347 insertions(+), 434 deletions(-) create mode 100644 test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/Shared/TestImportProjectFeature.cs create mode 100644 test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/Shared/TestProjectData.cs create mode 100644 test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/Shared/WorkspaceTestBase.cs create mode 100644 test/Microsoft.VisualStudio.Editor.Razor.Test.Common/SingleThreadedForegroundDispatcher.cs create mode 100644 test/Microsoft.VisualStudio.Editor.Razor.Test/Shared/ForegroundDispatcherWorkspaceTestBase.cs diff --git a/benchmarks/Microsoft.AspNetCore.Razor.Performance/ProjectSystem/ProjectSnapshotManagerBenchmarkBase.cs b/benchmarks/Microsoft.AspNetCore.Razor.Performance/ProjectSystem/ProjectSnapshotManagerBenchmarkBase.cs index 70590a6a35..580ddd1572 100644 --- a/benchmarks/Microsoft.AspNetCore.Razor.Performance/ProjectSystem/ProjectSnapshotManagerBenchmarkBase.cs +++ b/benchmarks/Microsoft.AspNetCore.Razor.Performance/ProjectSystem/ProjectSnapshotManagerBenchmarkBase.cs @@ -132,14 +132,6 @@ namespace Microsoft.AspNetCore.Razor.Performance private class StaticProjectSnapshotProjectEngineFactory : ProjectSnapshotProjectEngineFactory { - public override RazorProjectEngine Create(ProjectSnapshot project, RazorProjectFileSystem fileSystem, Action configure) - { - return RazorProjectEngine.Create(project.Configuration, fileSystem, b => - { - RazorExtensions.Register(b); - }); - } - public override IProjectEngineFactory FindFactory(ProjectSnapshot project) { throw new NotImplementedException(); @@ -149,6 +141,14 @@ namespace Microsoft.AspNetCore.Razor.Performance { throw new NotImplementedException(); } + + public override RazorProjectEngine Create(RazorConfiguration configuration, RazorProjectFileSystem fileSystem, Action configure) + { + return RazorProjectEngine.Create(configuration, fileSystem, b => + { + RazorExtensions.Register(b); + }); + } } } } diff --git a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorProjectFileSystem.cs b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorProjectFileSystem.cs index 2600ce5efe..9283088cd1 100644 --- a/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorProjectFileSystem.cs +++ b/src/Microsoft.AspNetCore.Razor.Language/DefaultRazorProjectFileSystem.cs @@ -49,6 +49,10 @@ namespace Microsoft.AspNetCore.Razor.Language var absolutePath = NormalizeAndEnsureValidPath(path); var file = new FileInfo(absolutePath); + if (!absolutePath.StartsWith(absoluteBasePath)) + { + throw new InvalidOperationException($"The file '{file.FullName}' is not a descendent of the base path '{absoluteBasePath}'."); + } var relativePhysicalPath = file.FullName.Substring(absoluteBasePath.Length + 1); // Include leading separator var filePath = "/" + relativePhysicalPath.Replace(Path.DirectorySeparatorChar, '/'); diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultProjectSnapshotProjectEngineFactory.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultProjectSnapshotProjectEngineFactory.cs index cd9ac1ee51..8651a9ced6 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultProjectSnapshotProjectEngineFactory.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultProjectSnapshotProjectEngineFactory.cs @@ -32,13 +32,8 @@ namespace Microsoft.CodeAnalysis.Razor _factories = factories; } - public override RazorProjectEngine Create(ProjectSnapshot project, RazorProjectFileSystem fileSystem, Action configure) + public override RazorProjectEngine Create(RazorConfiguration configuration, RazorProjectFileSystem fileSystem, Action configure) { - if (project == null) - { - throw new ArgumentNullException(nameof(project)); - } - if (fileSystem == null) { throw new ArgumentNullException(nameof(fileSystem)); @@ -55,7 +50,7 @@ namespace Microsoft.CodeAnalysis.Razor // // We typically want this because the language adds features over time - we don't want to a bunch of errors // to show up when a document is first opened, and then go away when the configuration loads, we'd prefer the opposite. - var configuration = project.Configuration ?? DefaultConfiguration; + configuration = configuration ?? DefaultConfiguration; // If there's no factory to handle the configuration then fall back to a very basic configuration. // diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSnapshotProjectEngineFactory.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSnapshotProjectEngineFactory.cs index cf2341878d..0f44e7c4c7 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSnapshotProjectEngineFactory.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSnapshotProjectEngineFactory.cs @@ -45,7 +45,36 @@ namespace Microsoft.CodeAnalysis.Razor return Create(project, RazorProjectFileSystem.Create(Path.GetDirectoryName(project.FilePath)), configure); } - public abstract RazorProjectEngine Create(ProjectSnapshot project, RazorProjectFileSystem fileSystem, Action configure); + public RazorProjectEngine Create(ProjectSnapshot project, RazorProjectFileSystem fileSystem, Action configure) + { + if (project == null) + { + throw new ArgumentNullException(nameof(project)); + } + if (fileSystem == null) + { + throw new ArgumentNullException(nameof(fileSystem)); + } + + return Create(project.Configuration, fileSystem, configure); + } + + public RazorProjectEngine Create(RazorConfiguration configuration, string directoryPath, Action configure) + { + if (configuration == null) + { + throw new ArgumentNullException(nameof(configuration)); + } + + if (directoryPath == null) + { + throw new ArgumentNullException(nameof(directoryPath)); + } + + return Create(configuration, RazorProjectFileSystem.Create(directoryPath), configure); + } + + public abstract RazorProjectEngine Create(RazorConfiguration configuration, RazorProjectFileSystem fileSystem, Action configure); } } diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshot.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshot.cs index aba5e7a916..3a85347d8d 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshot.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshot.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; @@ -58,9 +59,37 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem } } + public override bool IsImportDocument(DocumentSnapshot document) + { + if (document == null) + { + throw new ArgumentNullException(nameof(document)); + } + + return State.ImportsToRelatedDocuments.ContainsKey(document.TargetPath); + } + + public override IEnumerable GetRelatedDocuments(DocumentSnapshot document) + { + if (document == null) + { + throw new ArgumentNullException(nameof(document)); + } + + if (State.ImportsToRelatedDocuments.TryGetValue(document.TargetPath, out var relatedDocuments)) + { + lock (_lock) + { + return relatedDocuments.Select(GetDocument).ToArray(); + } + } + + return Array.Empty(); + } + public override RazorProjectEngine GetProjectEngine() { - return State.ProjectEngine.GetProjectEngine(this); + return State.ProjectEngine.GetProjectEngine(this.State); } public override Task> GetTagHelpersAsync() diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotManager.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotManager.cs index 5a810cc803..9953878518 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotManager.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotManager.cs @@ -90,12 +90,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem var projects = new ProjectSnapshot[_projects.Count]; foreach (var entry in _projects) { - if (entry.Value.Snapshot == null) - { - entry.Value.Snapshot = new DefaultProjectSnapshot(entry.Value.State); - } - - projects[i++] = entry.Value.Snapshot; + projects[i++] = entry.Value.GetSnapshot(); } return projects; @@ -115,12 +110,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem if (_projects.TryGetValue(filePath, out var entry)) { - if (entry.Snapshot == null) - { - entry.Snapshot = new DefaultProjectSnapshot(entry.State); - } - - return entry.Snapshot; + return entry.GetSnapshot(); } return null; @@ -175,8 +165,10 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem // Document updates can no-op. if (!object.ReferenceEquals(state, entry.State)) { - _projects[hostProject.FilePath] = new Entry(state); - NotifyListeners(new ProjectChangeEventArgs(hostProject.FilePath, document.FilePath, ProjectChangeKind.DocumentAdded)); + var oldSnapshot = entry.GetSnapshot(); + entry = new Entry(state); + _projects[hostProject.FilePath] = entry; + NotifyListeners(new ProjectChangeEventArgs(oldSnapshot, entry.GetSnapshot(), document.FilePath, ProjectChangeKind.DocumentAdded)); } } } @@ -201,8 +193,10 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem // Document updates can no-op. if (!object.ReferenceEquals(state, entry.State)) { - _projects[hostProject.FilePath] = new Entry(state); - NotifyListeners(new ProjectChangeEventArgs(hostProject.FilePath, document.FilePath, ProjectChangeKind.DocumentRemoved)); + var oldSnapshot = entry.GetSnapshot(); + entry = new Entry(state); + _projects[hostProject.FilePath] = entry; + NotifyListeners(new ProjectChangeEventArgs(oldSnapshot, entry.GetSnapshot(), document.FilePath, ProjectChangeKind.DocumentRemoved)); } } } @@ -229,12 +223,10 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem entry.State.Documents.TryGetValue(documentFilePath, out var older)) { ProjectState state; - SourceText olderText; - VersionStamp olderVersion; var currentText = sourceText; - if (older.TryGetText(out olderText) && - older.TryGetTextVersion(out olderVersion)) + if (older.TryGetText(out var olderText) && + older.TryGetTextVersion(out var olderVersion)) { var version = currentText.ContentEquals(olderText) ? olderVersion : olderVersion.GetNewerVersion(); state = entry.State.WithChangedHostDocument(older.HostDocument, currentText, version); @@ -256,8 +248,10 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem // Document updates can no-op. if (!object.ReferenceEquals(state, entry.State)) { - _projects[projectFilePath] = new Entry(state); - NotifyListeners(new ProjectChangeEventArgs(projectFilePath, documentFilePath, ProjectChangeKind.DocumentChanged)); + var oldSnapshot = entry.GetSnapshot(); + entry = new Entry(state); + _projects[projectFilePath] = entry; + NotifyListeners(new ProjectChangeEventArgs(oldSnapshot, entry.GetSnapshot(), documentFilePath, ProjectChangeKind.DocumentChanged)); } } } @@ -293,8 +287,10 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem // Document updates can no-op. if (!object.ReferenceEquals(state, entry.State)) { - _projects[projectFilePath] = new Entry(state); - NotifyListeners(new ProjectChangeEventArgs(projectFilePath, documentFilePath, ProjectChangeKind.DocumentChanged)); + var oldSnapshot = entry.GetSnapshot(); + entry = new Entry(state); + _projects[projectFilePath] = entry; + NotifyListeners(new ProjectChangeEventArgs(oldSnapshot, entry.GetSnapshot(), documentFilePath, ProjectChangeKind.DocumentChanged)); } } } @@ -321,12 +317,10 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem entry.State.Documents.TryGetValue(documentFilePath, out var older)) { ProjectState state; - SourceText olderText; - VersionStamp olderVersion; var currentText = sourceText; - if (older.TryGetText(out olderText) && - older.TryGetTextVersion(out olderVersion)) + if (older.TryGetText(out var olderText) && + older.TryGetTextVersion(out var olderVersion)) { var version = currentText.ContentEquals(olderText) ? olderVersion : olderVersion.GetNewerVersion(); state = entry.State.WithChangedHostDocument(older.HostDocument, currentText, version); @@ -346,8 +340,10 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem // Document updates can no-op. if (!object.ReferenceEquals(state, entry.State)) { - _projects[projectFilePath] = new Entry(state); - NotifyListeners(new ProjectChangeEventArgs(projectFilePath, documentFilePath, ProjectChangeKind.DocumentChanged)); + var oldSnapshot = entry.GetSnapshot(); + entry = new Entry(state); + _projects[projectFilePath] = entry; + NotifyListeners(new ProjectChangeEventArgs(oldSnapshot, entry.GetSnapshot(), documentFilePath, ProjectChangeKind.DocumentChanged)); } } } @@ -381,8 +377,10 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem // Document updates can no-op. if (!object.ReferenceEquals(state, entry.State)) { - _projects[projectFilePath] = new Entry(state); - NotifyListeners(new ProjectChangeEventArgs(projectFilePath, documentFilePath, ProjectChangeKind.DocumentChanged)); + var oldSnapshot = entry.GetSnapshot(); + entry = new Entry(state); + _projects[projectFilePath] = entry; + NotifyListeners(new ProjectChangeEventArgs(oldSnapshot, entry.GetSnapshot(), documentFilePath, ProjectChangeKind.DocumentChanged)); } } } @@ -407,10 +405,11 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem var workspaceProject = GetWorkspaceProject(hostProject.FilePath); var state = ProjectState.Create(Workspace.Services, hostProject, workspaceProject); - _projects[hostProject.FilePath] = new Entry(state); + var entry = new Entry(state); + _projects[hostProject.FilePath] = entry; // We need to notify listeners about every project add. - NotifyListeners(new ProjectChangeEventArgs(hostProject.FilePath, ProjectChangeKind.ProjectAdded)); + NotifyListeners(new ProjectChangeEventArgs(null, entry.GetSnapshot(), ProjectChangeKind.ProjectAdded)); } public override void HostProjectChanged(HostProject hostProject) @@ -429,9 +428,10 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem // HostProject updates can no-op. if (!object.ReferenceEquals(state, entry.State)) { - _projects[hostProject.FilePath] = new Entry(state); - - NotifyListeners(new ProjectChangeEventArgs(hostProject.FilePath, ProjectChangeKind.ProjectChanged)); + var oldSnapshot = entry.GetSnapshot(); + entry = new Entry(state); + _projects[hostProject.FilePath] = entry; + NotifyListeners(new ProjectChangeEventArgs(oldSnapshot, entry.GetSnapshot(), ProjectChangeKind.ProjectChanged)); } } } @@ -445,12 +445,12 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem _foregroundDispatcher.AssertForegroundThread(); - if (_projects.TryGetValue(hostProject.FilePath, out var snapshot)) + if (_projects.TryGetValue(hostProject.FilePath, out var entry)) { - _projects.Remove(hostProject.FilePath); - // We need to notify listeners about every project removal. - NotifyListeners(new ProjectChangeEventArgs(hostProject.FilePath, ProjectChangeKind.ProjectRemoved)); + var oldSnapshot = entry.GetSnapshot(); + _projects.Remove(hostProject.FilePath); + NotifyListeners(new ProjectChangeEventArgs(oldSnapshot, null, ProjectChangeKind.ProjectRemoved)); } } @@ -477,9 +477,11 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem if (entry.State.WorkspaceProject == null) { var state = entry.State.WithWorkspaceProject(workspaceProject); - _projects[workspaceProject.FilePath] = new Entry(state); - NotifyListeners(new ProjectChangeEventArgs(workspaceProject.FilePath, ProjectChangeKind.ProjectChanged)); + var oldSnapshot = entry.GetSnapshot(); + entry = new Entry(state); + _projects[workspaceProject.FilePath] = entry; + NotifyListeners(new ProjectChangeEventArgs(oldSnapshot, entry.GetSnapshot(), ProjectChangeKind.ProjectChanged)); } } } @@ -505,14 +507,15 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem (entry.State.WorkspaceProject == null || entry.State.WorkspaceProject.Version.GetNewerVersion(workspaceProject.Version) == workspaceProject.Version)) { var state = entry.State.WithWorkspaceProject(workspaceProject); - + // WorkspaceProject updates can no-op. This can be the case if a build is triggered, but we've // already seen the update. if (!object.ReferenceEquals(state, entry.State)) { - _projects[workspaceProject.FilePath] = new Entry(state); - - NotifyListeners(new ProjectChangeEventArgs(workspaceProject.FilePath, ProjectChangeKind.ProjectChanged)); + var oldSnapshot = entry.GetSnapshot(); + entry = new Entry(state); + _projects[workspaceProject.FilePath] = entry; + NotifyListeners(new ProjectChangeEventArgs(oldSnapshot, entry.GetSnapshot(), ProjectChangeKind.ProjectChanged)); } } } @@ -549,17 +552,21 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { // OK there's another WorkspaceProject, use that. state = entry.State.WithWorkspaceProject(otherWorkspaceProject); - _projects[otherWorkspaceProject.FilePath] = new Entry(state); - NotifyListeners(new ProjectChangeEventArgs(otherWorkspaceProject.FilePath, ProjectChangeKind.ProjectChanged)); + var oldSnapshot = entry.GetSnapshot(); + entry = new Entry(state); + _projects[otherWorkspaceProject.FilePath] = entry; + NotifyListeners(new ProjectChangeEventArgs(oldSnapshot, entry.GetSnapshot(), ProjectChangeKind.ProjectChanged)); } else { - state = entry.State.WithWorkspaceProject(null); - _projects[workspaceProject.FilePath] = new Entry(state); - // Notify listeners of a change because we've removed computed state. - NotifyListeners(new ProjectChangeEventArgs(workspaceProject.FilePath, ProjectChangeKind.ProjectChanged)); + state = entry.State.WithWorkspaceProject(null); + + var oldSnapshot = entry.GetSnapshot(); + entry = new Entry(state); + _projects[workspaceProject.FilePath] = entry; + NotifyListeners(new ProjectChangeEventArgs(oldSnapshot, entry.GetSnapshot(), ProjectChangeKind.ProjectChanged)); } } } @@ -601,7 +608,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { throw new ArgumentNullException(nameof(exception)); } - + _errorReporter.ReportError(exception, workspaceProject); } @@ -644,13 +651,18 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem private class Entry { - public ProjectSnapshot Snapshot; + public ProjectSnapshot SnapshotUnsafe; public readonly ProjectState State; public Entry(ProjectState state) { State = state; } + + public ProjectSnapshot GetSnapshot() + { + return SnapshotUnsafe ?? (SnapshotUnsafe = new DefaultProjectSnapshot(State)); + } } } } \ No newline at end of file diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentState.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentState.cs index 681ce5fd9a..b6ad36ddb0 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentState.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DocumentState.cs @@ -183,6 +183,18 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem return state; } + public virtual DocumentState WithImportsChange() + { + var state = new DocumentState(Services, HostDocument, _sourceText, _version, _loader); + + // The source could not have possibly changed. + state._sourceText = _sourceText; + state._version = _version; + state._loaderTask = _loaderTask; + + return state; + } + public virtual DocumentState WithWorkspaceProjectChange() { var state = new DocumentState(Services, HostDocument, _sourceText, _version, _loader); diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/EphemeralProjectSnapshot.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/EphemeralProjectSnapshot.cs index cd87c074de..82f9a93196 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/EphemeralProjectSnapshot.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/EphemeralProjectSnapshot.cs @@ -56,6 +56,26 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem return null; } + public override bool IsImportDocument(DocumentSnapshot document) + { + if (document == null) + { + throw new ArgumentNullException(nameof(document)); + } + + return false; + } + + public override IEnumerable GetRelatedDocuments(DocumentSnapshot document) + { + if (document == null) + { + throw new ArgumentNullException(nameof(document)); + } + + return Array.Empty(); + } + public override RazorProjectEngine GetProjectEngine() { return _projectEngine.Value; diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/LiveShareProjectSnapshotBase.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/LiveShareProjectSnapshotBase.cs index f03a31b0ae..ae13f49735 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/LiveShareProjectSnapshotBase.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/LiveShareProjectSnapshotBase.cs @@ -27,6 +27,10 @@ namespace Microsoft.CodeAnalysis.Razor.Workspaces.ProjectSystem public override DocumentSnapshot GetDocument(string filePath) => throw new NotImplementedException(); + public override bool IsImportDocument(DocumentSnapshot document) => throw new NotImplementedException(); + + public override IEnumerable GetRelatedDocuments(DocumentSnapshot document) => throw new NotImplementedException(); + public override RazorProjectEngine GetProjectEngine() => throw new NotImplementedException(); public override Task> GetTagHelpersAsync() => throw new NotImplementedException(); diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectChangeEventArgs.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectChangeEventArgs.cs index a163ba5f1b..3e0dd3ff68 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectChangeEventArgs.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectChangeEventArgs.cs @@ -7,29 +7,39 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { internal class ProjectChangeEventArgs : EventArgs { - public ProjectChangeEventArgs(string projectFilePath, ProjectChangeKind kind) + public ProjectChangeEventArgs(ProjectSnapshot older, ProjectSnapshot newer, ProjectChangeKind kind) { - if (projectFilePath == null) + if (older == null && newer == null) { - throw new ArgumentNullException(nameof(projectFilePath)); + throw new ArgumentException("Both projects cannot be null."); } - ProjectFilePath = projectFilePath; + Older = older; + Newer = newer; Kind = kind; + + ProjectFilePath = older?.FilePath ?? newer.FilePath; } - public ProjectChangeEventArgs(string projectFilePath, string documentFilePath, ProjectChangeKind kind) + public ProjectChangeEventArgs(ProjectSnapshot older, ProjectSnapshot newer, string documentFilePath, ProjectChangeKind kind) { - if (projectFilePath == null) + if (older == null && newer == null) { - throw new ArgumentNullException(nameof(projectFilePath)); + throw new ArgumentException("Both projects cannot be null."); } - ProjectFilePath = projectFilePath; + Older = older; + Newer = newer; DocumentFilePath = documentFilePath; Kind = kind; + + ProjectFilePath = older?.FilePath ?? newer.FilePath; } + public ProjectSnapshot Older { get; } + + public ProjectSnapshot Newer { get; } + public string ProjectFilePath { get; } public string DocumentFilePath { get; } diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectEngineTracker.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectEngineTracker.cs index 6e54daed0b..0be857f443 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectEngineTracker.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectEngineTracker.cs @@ -2,6 +2,10 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.IO; +using System.Linq; using Microsoft.AspNetCore.Razor.Language; using Microsoft.CodeAnalysis.Host; @@ -41,11 +45,11 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem return this; } - public RazorProjectEngine GetProjectEngine(ProjectSnapshot snapshot) + public RazorProjectEngine GetProjectEngine(ProjectState state) { - if (snapshot == null) + if (state == null) { - throw new ArgumentNullException(nameof(snapshot)); + throw new ArgumentNullException(nameof(state)); } if (_projectEngine == null) @@ -55,12 +59,30 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem if (_projectEngine == null) { var factory = _services.GetRequiredService(); - _projectEngine = factory.Create(snapshot); + _projectEngine = factory.Create(state.HostProject.Configuration, Path.GetDirectoryName(state.HostProject.FilePath), configure: null); } } } return _projectEngine; } + + public List GetImportDocumentTargetPaths(ProjectState state, string targetPath) + { + var projectEngine = GetProjectEngine(state); + var importFeature = projectEngine.ProjectFeatures.OfType().FirstOrDefault(); + var projectItem = projectEngine.FileSystem.GetItem(targetPath); + var importItems = importFeature?.GetImports(projectItem).Where(i => i.FilePath != null); + + // Target path looks like `Foo\\Bar.cshtml` + var targetPaths = new List(); + foreach (var importItem in importItems) + { + var itemTargetPath = importItem.FilePath.Replace('/', '\\').TrimStart('\\'); + targetPaths.Add(itemTargetPath); + } + + return targetPaths; + } } } diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshot.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshot.cs index 418d117ff2..cf091a4f19 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshot.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshot.cs @@ -25,6 +25,17 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem public abstract DocumentSnapshot GetDocument(string filePath); + public abstract bool IsImportDocument(DocumentSnapshot document); + + /// + /// If the provided document is an import document, gets the other documents in the project + /// that include directives specified by the provided document. Otherwise returns an empty + /// list. + /// + /// The document. + /// A list of related documents. + public abstract IEnumerable GetRelatedDocuments(DocumentSnapshot document); + public abstract Task> GetTagHelpersAsync(); public abstract bool TryGetTagHelpers(out IReadOnlyList result); diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectState.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectState.cs index 6670ba55a2..27e33e6f5c 100644 --- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectState.cs +++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectState.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Text; @@ -12,10 +13,10 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem // Internal tracker for DefaultProjectSnapshot internal class ProjectState { - private static readonly IReadOnlyDictionary EmptyDocuments = new Dictionary(); - + private static readonly ImmutableDictionary EmptyDocuments = ImmutableDictionary.Create(FilePathComparer.Instance); + private static readonly ImmutableDictionary> EmptyImportsToRelatedDocuments = ImmutableDictionary.Create>(FilePathComparer.Instance); private readonly object _lock; - + private ProjectEngineTracker _projectEngine; private ProjectTagHelperTracker _tagHelpers; @@ -43,6 +44,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem HostProject = hostProject; WorkspaceProject = workspaceProject; Documents = EmptyDocuments; + ImportsToRelatedDocuments = EmptyImportsToRelatedDocuments; Version = VersionStamp.Create(); _lock = new object(); @@ -53,7 +55,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem ProjectDifference difference, HostProject hostProject, Project workspaceProject, - IReadOnlyDictionary documents) + ImmutableDictionary documents, + ImmutableDictionary> importsToRelatedDocuments) { if (older == null) { @@ -70,12 +73,18 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem throw new ArgumentNullException(nameof(documents)); } + if (importsToRelatedDocuments == null) + { + throw new ArgumentNullException(nameof(importsToRelatedDocuments)); + } + Services = older.Services; Version = older.Version.GetNewerVersion(); HostProject = hostProject; WorkspaceProject = workspaceProject; Documents = documents; + ImportsToRelatedDocuments = importsToRelatedDocuments; _lock = new object(); @@ -84,7 +93,10 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem } // Internal set for testing. - public IReadOnlyDictionary Documents { get; internal set; } + public ImmutableDictionary Documents { get; internal set; } + + // Internal set for testing. + public ImmutableDictionary> ImportsToRelatedDocuments { get; internal set; } public HostProject HostProject { get; } @@ -152,17 +164,24 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { return this; } - - var documents = new Dictionary(FilePathComparer.Instance); - foreach (var kvp in Documents) - { - documents.Add(kvp.Key, kvp.Value); - } - documents.Add(hostDocument.FilePath, DocumentState.Create(Services, hostDocument, loader)); + var documents = Documents.Add(hostDocument.FilePath, DocumentState.Create(Services, hostDocument, loader)); - var difference = ProjectDifference.DocumentAdded; - var state = new ProjectState(this, difference, HostProject, WorkspaceProject, documents); + // Compute the effect on the import map + var importTargetPaths = ProjectEngine.GetImportDocumentTargetPaths(this, hostDocument.TargetPath); + var importsToRelatedDocuments = AddToImportsToRelatedDocuments(ImportsToRelatedDocuments, hostDocument, importTargetPaths); + + // Now check if the updated document is an import - it's important this this happens after + // updating the imports map. + if (importsToRelatedDocuments.TryGetValue(hostDocument.TargetPath, out var relatedDocuments)) + { + foreach (var relatedDocument in relatedDocuments) + { + documents = documents.SetItem(relatedDocument, documents[relatedDocument].WithImportsChange()); + } + } + + var state = new ProjectState(this, ProjectDifference.DocumentAdded, HostProject, WorkspaceProject, documents, importsToRelatedDocuments); return state; } @@ -177,17 +196,24 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { return this; } + + var documents = Documents.Remove(hostDocument.FilePath); - var documents = new Dictionary(FilePathComparer.Instance); - foreach (var kvp in Documents) + // First check if the updated document is an import - it's important that this happens + // before updating the imports map. + if (ImportsToRelatedDocuments.TryGetValue(hostDocument.TargetPath, out var relatedDocuments)) { - documents.Add(kvp.Key, kvp.Value); + foreach (var relatedDocument in relatedDocuments) + { + documents = documents.SetItem(relatedDocument, documents[relatedDocument].WithImportsChange()); + } } - documents.Remove(hostDocument.FilePath); + // Compute the effect on the import map + var importTargetPaths = ProjectEngine.GetImportDocumentTargetPaths(this, hostDocument.TargetPath); + var importsToRelatedDocuments = RemoveFromImportsToRelatedDocuments(ImportsToRelatedDocuments, hostDocument, importTargetPaths); - var difference = ProjectDifference.DocumentRemoved; - var state = new ProjectState(this, difference, HostProject, WorkspaceProject, documents); + var state = new ProjectState(this, ProjectDifference.DocumentRemoved, HostProject, WorkspaceProject, documents, importsToRelatedDocuments); return state; } @@ -198,23 +224,22 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem throw new ArgumentNullException(nameof(hostDocument)); } - if (!Documents.ContainsKey(hostDocument.FilePath)) + if (!Documents.TryGetValue(hostDocument.FilePath, out var document)) { return this; } - var documents = new Dictionary(FilePathComparer.Instance); - foreach (var kvp in Documents) + var documents = Documents.SetItem(hostDocument.FilePath, document.WithText(sourceText, version)); + + if (ImportsToRelatedDocuments.TryGetValue(hostDocument.TargetPath, out var relatedDocuments)) { - documents.Add(kvp.Key, kvp.Value); + foreach (var relatedDocument in relatedDocuments) + { + documents = documents.SetItem(relatedDocument, documents[relatedDocument].WithImportsChange()); + } } - if (documents.TryGetValue(hostDocument.FilePath, out var document)) - { - documents[hostDocument.FilePath] = document.WithText(sourceText, version); - } - - var state = new ProjectState(this, ProjectDifference.DocumentChanged, HostProject, WorkspaceProject, documents); + var state = new ProjectState(this, ProjectDifference.DocumentChanged, HostProject, WorkspaceProject, documents, ImportsToRelatedDocuments); return state; } @@ -225,23 +250,22 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem throw new ArgumentNullException(nameof(hostDocument)); } - if (!Documents.ContainsKey(hostDocument.FilePath)) + if (!Documents.TryGetValue(hostDocument.FilePath, out var document)) { return this; } - var documents = new Dictionary(FilePathComparer.Instance); - foreach (var kvp in Documents) + var documents = Documents.SetItem(hostDocument.FilePath, document.WithTextLoader(loader)); + + if (ImportsToRelatedDocuments.TryGetValue(hostDocument.TargetPath, out var relatedDocuments)) { - documents.Add(kvp.Key, kvp.Value); + foreach (var relatedDocument in relatedDocuments) + { + documents = documents.SetItem(relatedDocument, documents[relatedDocument].WithImportsChange()); + } } - if (documents.TryGetValue(hostDocument.FilePath, out var document)) - { - documents[hostDocument.FilePath] = document.WithTextLoader(loader); - } - - var state = new ProjectState(this, ProjectDifference.DocumentChanged, HostProject, WorkspaceProject, documents); + var state = new ProjectState(this, ProjectDifference.DocumentChanged, HostProject, WorkspaceProject, documents, ImportsToRelatedDocuments); return state; } @@ -256,15 +280,20 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { return this; } + + var documents = Documents.ToImmutableDictionary(kvp => kvp.Key, kvp => kvp.Value.WithConfigurationChange(), FilePathComparer.Instance); - var difference = ProjectDifference.ConfigurationChanged; - var documents = new Dictionary(FilePathComparer.Instance); - foreach (var kvp in Documents) + // If the host project has changed then we need to recompute the imports map + var importsToRelatedDocuments = EmptyImportsToRelatedDocuments; + + foreach (var document in documents) { - documents.Add(kvp.Key, kvp.Value.WithConfigurationChange()); + var importTargetPaths = ProjectEngine.GetImportDocumentTargetPaths(this, document.Value.HostDocument.TargetPath); + importsToRelatedDocuments = AddToImportsToRelatedDocuments(ImportsToRelatedDocuments, document.Value.HostDocument, importTargetPaths); } - var state = new ProjectState(this, difference, hostProject, WorkspaceProject, documents); + + var state = new ProjectState(this, ProjectDifference.ConfigurationChanged, hostProject, WorkspaceProject, documents, importsToRelatedDocuments); return state; } @@ -291,14 +320,52 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem return this; } - var documents = new Dictionary(FilePathComparer.Instance); - foreach (var kvp in Documents) + var documents = Documents.ToImmutableDictionary(kvp => kvp.Key, kvp => kvp.Value.WithWorkspaceProjectChange(), FilePathComparer.Instance); + var state = new ProjectState(this, difference, HostProject, workspaceProject, documents, ImportsToRelatedDocuments); + return state; + } + + private static ImmutableDictionary> AddToImportsToRelatedDocuments( + ImmutableDictionary> importsToRelatedDocuments, + HostDocument hostDocument, + List importTargetPaths) + { + foreach (var importTargetPath in importTargetPaths) { - documents.Add(kvp.Key, kvp.Value.WithWorkspaceProjectChange()); + if (!importsToRelatedDocuments.TryGetValue(importTargetPath, out var relatedDocuments)) + { + relatedDocuments = ImmutableArray.Create(); + } + + relatedDocuments = relatedDocuments.Add(hostDocument.FilePath); + importsToRelatedDocuments = importsToRelatedDocuments.SetItem(importTargetPath, relatedDocuments); } - var state = new ProjectState(this, difference, HostProject, workspaceProject, documents); - return state; + return importsToRelatedDocuments; + } + + private static ImmutableDictionary> RemoveFromImportsToRelatedDocuments( + ImmutableDictionary> importsToRelatedDocuments, + HostDocument hostDocument, + List importTargetPaths) + { + foreach (var importTargetPath in importTargetPaths) + { + if (importsToRelatedDocuments.TryGetValue(importTargetPath, out var relatedDocuments)) + { + relatedDocuments = relatedDocuments.Remove(hostDocument.FilePath); + if (relatedDocuments.Length > 0) + { + importsToRelatedDocuments = importsToRelatedDocuments.SetItem(importTargetPath, relatedDocuments); + } + else + { + importsToRelatedDocuments = importsToRelatedDocuments.Remove(importTargetPath); + } + } + } + + return importsToRelatedDocuments; } } } diff --git a/src/Microsoft.CodeAnalysis.Remote.Razor/RazorServiceBase.cs b/src/Microsoft.CodeAnalysis.Remote.Razor/RazorServiceBase.cs index 289d75cc0f..32e8e3bcb6 100644 --- a/src/Microsoft.CodeAnalysis.Remote.Razor/RazorServiceBase.cs +++ b/src/Microsoft.CodeAnalysis.Remote.Razor/RazorServiceBase.cs @@ -76,6 +76,16 @@ namespace Microsoft.CodeAnalysis.Remote.Razor return null; } + public override bool IsImportDocument(DocumentSnapshot document) + { + throw new NotImplementedException(); + } + + public override IEnumerable GetRelatedDocuments(DocumentSnapshot document) + { + throw new NotImplementedException(); + } + public override RazorProjectEngine GetProjectEngine() { throw new NotImplementedException(); diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/BackgroundDocumentGenerator.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/BackgroundDocumentGenerator.cs index fe4c9292d1..947dc1155a 100644 --- a/src/Microsoft.VisualStudio.LanguageServices.Razor/BackgroundDocumentGenerator.cs +++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/BackgroundDocumentGenerator.cs @@ -237,7 +237,7 @@ namespace Microsoft.CodeAnalysis.Razor { case ProjectChangeKind.ProjectAdded: { - var projectSnapshot = _projectManager.GetLoadedProject(e.ProjectFilePath); + var projectSnapshot = e.Newer; foreach (var documentFilePath in projectSnapshot.DocumentFilePaths) { Enqueue(projectSnapshot, projectSnapshot.GetDocument(documentFilePath)); @@ -247,7 +247,7 @@ namespace Microsoft.CodeAnalysis.Razor } case ProjectChangeKind.ProjectChanged: { - var projectSnapshot = _projectManager.GetLoadedProject(e.ProjectFilePath); + var projectSnapshot = e.Newer; foreach (var documentFilePath in projectSnapshot.DocumentFilePaths) { Enqueue(projectSnapshot, projectSnapshot.GetDocument(documentFilePath)); @@ -257,23 +257,35 @@ namespace Microsoft.CodeAnalysis.Razor } case ProjectChangeKind.DocumentAdded: + case ProjectChangeKind.DocumentChanged: { - var project = _projectManager.GetLoadedProject(e.ProjectFilePath); - Enqueue(project, project.GetDocument(e.DocumentFilePath)); + var project = e.Newer; + var document = project.GetDocument(e.DocumentFilePath); + + Enqueue(project, document); + foreach (var relatedDocument in project.GetRelatedDocuments(document)) + { + Enqueue(project, document); + } break; } - case ProjectChangeKind.DocumentChanged: + case ProjectChangeKind.DocumentRemoved: { - var project = _projectManager.GetLoadedProject(e.ProjectFilePath); - Enqueue(project, project.GetDocument(e.DocumentFilePath)); + // For removals use the old snapshot to find the removed document, so we can figure out + // what the imports were in the new snapshot. + var document = e.Older.GetDocument(e.DocumentFilePath); + + foreach (var relatedDocument in e.Newer.GetRelatedDocuments(document)) + { + Enqueue(e.Newer, document); + } break; } case ProjectChangeKind.ProjectRemoved: - case ProjectChangeKind.DocumentRemoved: { // ignore break; diff --git a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultDocumentSnapshotTest.cs b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultDocumentSnapshotTest.cs index 860334c66a..1ca2f6e5d9 100644 --- a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultDocumentSnapshotTest.cs +++ b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultDocumentSnapshotTest.cs @@ -1,36 +1,28 @@ // 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.Collections.Generic; using System.Threading.Tasks; -using Microsoft.AspNetCore.Razor.Language; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Text; using Xunit; namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { - public class DefaultDocumentSnapshotTest + public class DefaultDocumentSnapshotTest : WorkspaceTestBase { public DefaultDocumentSnapshotTest() { - var services = TestServices.Create( - new[] { new TestProjectSnapshotProjectEngineFactory() }, - new[] { new TestTagHelperResolver() }); - Workspace = TestWorkspace.Create(services); - var hostProject = new HostProject("C:/some/path/project.csproj", RazorConfiguration.Default); - var projectState = ProjectState.Create(Workspace.Services, hostProject); + var projectState = ProjectState.Create(Workspace.Services, TestProjectData.SomeProject); var project = new DefaultProjectSnapshot(projectState); - HostDocument = new HostDocument("C:/some/path/file.cshtml", "C:/some/path/file.cshtml"); - SourceText = Text.SourceText.From("

Hello World

"); + HostDocument = TestProjectData.SomeProjectFile1; + SourceText = SourceText.From("

Hello World

"); Version = VersionStamp.Default.GetNewerVersion(); var textAndVersion = TextAndVersion.Create(SourceText, Version); var documentState = DocumentState.Create(Workspace.Services, HostDocument, () => Task.FromResult(textAndVersion)); Document = new DefaultDocumentSnapshot(project, documentState); - } - private Workspace Workspace { get; } - private SourceText SourceText { get; } private VersionStamp Version { get; } @@ -39,6 +31,11 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem private DefaultDocumentSnapshot Document { get; } + protected override void ConfigureLanguageServices(List services) + { + services.Add(new TestTagHelperResolver()); + } + [Fact] public async Task GetGeneratedOutputAsync_SetsHostDocumentOutput() { diff --git a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultProjectSnapshotTest.cs b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultProjectSnapshotTest.cs index 3dd6e35b53..03800d062a 100644 --- a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultProjectSnapshotTest.cs +++ b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultProjectSnapshotTest.cs @@ -1,35 +1,23 @@ // 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.AspNetCore.Razor.Language; -using Microsoft.CodeAnalysis.Host; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Microsoft.AspNetCore.Razor.Language; +using Microsoft.CodeAnalysis.Host; using Xunit; namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { - public class DefaultProjectSnapshotTest + public class DefaultProjectSnapshotTest : WorkspaceTestBase { public DefaultProjectSnapshotTest() { TagHelperResolver = new TestTagHelperResolver(); - HostServices = TestServices.Create( - new IWorkspaceService[] - { - new TestProjectSnapshotProjectEngineFactory(), - }, - new ILanguageService[] - { - TagHelperResolver, - }); - - HostProject = new HostProject("c:\\MyProject\\Test.csproj", FallbackRazorConfiguration.MVC_2_0); - HostProjectWithConfigurationChange = new HostProject("c:\\MyProject\\Test.csproj", FallbackRazorConfiguration.MVC_1_0); - - Workspace = TestWorkspace.Create(HostServices); + HostProject = new HostProject(TestProjectData.SomeProject.FilePath, FallbackRazorConfiguration.MVC_2_0); + HostProjectWithConfigurationChange = new HostProject(TestProjectData.SomeProject.FilePath, FallbackRazorConfiguration.MVC_1_0); var projectId = ProjectId.CreateNewId("Test"); var solution = Workspace.CurrentSolution.AddProject(ProjectInfo.Create( @@ -38,19 +26,21 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem "Test", "Test", LanguageNames.CSharp, - "c:\\MyProject\\Test.csproj")); + TestProjectData.SomeProject.FilePath)); WorkspaceProject = solution.GetProject(projectId); - SomeTagHelpers = new List(); - SomeTagHelpers.Add(TagHelperDescriptorBuilder.Create("Test1", "TestAssembly").Build()); + SomeTagHelpers = new List + { + TagHelperDescriptorBuilder.Create("Test1", "TestAssembly").Build() + }; Documents = new HostDocument[] { - new HostDocument("c:\\MyProject\\File.cshtml", "File.cshtml"), - new HostDocument("c:\\MyProject\\Index.cshtml", "Index.cshtml"), + TestProjectData.SomeProjectFile1, + TestProjectData.SomeProjectFile2, // linked file - new HostDocument("c:\\SomeOtherProject\\Index.cshtml", "Pages\\Index.cshtml"), + TestProjectData.AnotherProjectNestedFile3, }; } @@ -64,12 +54,25 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem private TestTagHelperResolver TagHelperResolver { get; } - private HostServices HostServices { get; } - - private Workspace Workspace { get; } - private List SomeTagHelpers { get; } + private void Configure(RazorProjectEngineBuilder builder) + { + builder.Features.Remove(builder.Features.OfType().Single()); + builder.Features.Add(new TestImportProjectFeature()); + } + + protected override void ConfigureLanguageServices(List services) + { + services.Add(TagHelperResolver); + } + + protected override void ConfigureProjectEngine(RazorProjectEngineBuilder builder) + { + builder.Features.Remove(builder.Features.OfType().Single()); + builder.Features.Add(new TestImportProjectFeature()); + } + [Fact] public void ProjectSnapshot_CachesDocumentSnapshots() { @@ -114,5 +117,79 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem TagHelperResolver.CompletionSource.SetCanceled(); } } + + [Fact] + public void IsImportDocument_NonImportDocument_ReturnsFalse() + { + // Arrange + var state = ProjectState.Create(Workspace.Services, HostProject, WorkspaceProject) + .WithAddedHostDocument(Documents[0], DocumentState.EmptyLoader); + var snapshot = new DefaultProjectSnapshot(state); + + var document = snapshot.GetDocument(Documents[0].FilePath); + + // Act + var result = snapshot.IsImportDocument(document); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsImportDocument_ImportDocument_ReturnsTrue() + { + // Arrange + var state = ProjectState.Create(Workspace.Services, HostProject, WorkspaceProject) + .WithAddedHostDocument(Documents[0], DocumentState.EmptyLoader) + .WithAddedHostDocument(TestProjectData.SomeProjectImportFile, DocumentState.EmptyLoader); + var snapshot = new DefaultProjectSnapshot(state); + + var document = snapshot.GetDocument(TestProjectData.SomeProjectImportFile.FilePath); + + // Act + var result = snapshot.IsImportDocument(document); + + // Assert + Assert.True(result); + } + + [Fact] + public void GetRelatedDocuments_NonImportDocument_ReturnsEmpty() + { + // Arrange + var state = ProjectState.Create(Workspace.Services, HostProject, WorkspaceProject) + .WithAddedHostDocument(Documents[0], DocumentState.EmptyLoader); + var snapshot = new DefaultProjectSnapshot(state); + + var document = snapshot.GetDocument(Documents[0].FilePath); + + // Act + var documents = snapshot.GetRelatedDocuments(document); + + // Assert + Assert.Empty(documents); + } + + [Fact] + public void GetRelatedDocuments_ImportDocument_ReturnsRelated() + { + // Arrange + var state = ProjectState.Create(Workspace.Services, HostProject, WorkspaceProject) + .WithAddedHostDocument(Documents[0], DocumentState.EmptyLoader) + .WithAddedHostDocument(Documents[1], DocumentState.EmptyLoader) + .WithAddedHostDocument(TestProjectData.SomeProjectImportFile, DocumentState.EmptyLoader); + var snapshot = new DefaultProjectSnapshot(state); + + var document = snapshot.GetDocument(TestProjectData.SomeProjectImportFile.FilePath); + + // Act + var documents = snapshot.GetRelatedDocuments(document); + + // Assert + Assert.Collection( + documents.OrderBy(d => d.FilePath), + d => Assert.Equal(Documents[0].FilePath, d.FilePath), + d => Assert.Equal(Documents[1].FilePath, d.FilePath)); + } } } diff --git a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DocumentStateTest.cs b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DocumentStateTest.cs index cf4db709e8..caa1927d8b 100644 --- a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DocumentStateTest.cs +++ b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DocumentStateTest.cs @@ -3,37 +3,22 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Text; -using Moq; using Xunit; namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { - public class DocumentStateTest + public class DocumentStateTest : WorkspaceTestBase { public DocumentStateTest() { TagHelperResolver = new TestTagHelperResolver(); - - HostServices = TestServices.Create( - new IWorkspaceService[] - { - new TestProjectSnapshotProjectEngineFactory(), - }, - new ILanguageService[] - { - TagHelperResolver, - }); - - HostProject = new HostProject("c:\\MyProject\\Test.csproj", FallbackRazorConfiguration.MVC_2_0); - HostProjectWithConfigurationChange = new HostProject("c:\\MyProject\\Test.csproj", FallbackRazorConfiguration.MVC_1_0); - - Workspace = TestWorkspace.Create(HostServices); + + HostProject = new HostProject(TestProjectData.SomeProject.FilePath, FallbackRazorConfiguration.MVC_2_0); + HostProjectWithConfigurationChange = new HostProject(TestProjectData.SomeProject.FilePath, FallbackRazorConfiguration.MVC_1_0); var projectId = ProjectId.CreateNewId("Test"); var solution = Workspace.CurrentSolution.AddProject(ProjectInfo.Create( @@ -42,13 +27,13 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem "Test", "Test", LanguageNames.CSharp, - "c:\\MyProject\\Test.csproj")); + TestProjectData.SomeProject.FilePath)); WorkspaceProject = solution.GetProject(projectId); SomeTagHelpers = new List(); SomeTagHelpers.Add(TagHelperDescriptorBuilder.Create("Test1", "TestAssembly").Build()); - Document = new HostDocument("c:\\MyProject\\File.cshtml", "File.cshtml"); + Document = TestProjectData.SomeProjectFile1; Text = SourceText.From("Hello, world!"); TextLoader = () => Task.FromResult(TextAndVersion.Create(Text, VersionStamp.Create())); @@ -64,16 +49,17 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem private TestTagHelperResolver TagHelperResolver { get; } - private HostServices HostServices { get; } - - private Workspace Workspace { get; } - private List SomeTagHelpers { get; } private Func> TextLoader { get; } private SourceText Text { get; } + protected override void ConfigureLanguageServices(List services) + { + services.Add(TagHelperResolver); + } + [Fact] public async Task DocumentState_CreatedNew_HasEmptyText() { @@ -145,6 +131,38 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem Assert.True(state.TryGetTextVersion(out _)); } + [Fact] + public void DocumentState_WithImportsChange_CachesSnapshotText() + { + // Arrange + var original = DocumentState.Create(Workspace.Services, Document, DocumentState.EmptyLoader) + .WithText(Text, VersionStamp.Create()); + + // Act + var state = original.WithImportsChange(); + + // Assert + Assert.True(state.TryGetText(out _)); + Assert.True(state.TryGetTextVersion(out _)); + } + + [Fact] + public async Task DocumentState_WithImportsChange_CachesLoadedText() + { + // Arrange + var original = DocumentState.Create(Workspace.Services, Document, DocumentState.EmptyLoader) + .WithTextLoader(TextLoader); + + await original.GetTextAsync(); + + // Act + var state = original.WithImportsChange(); + + // Assert + Assert.True(state.TryGetText(out _)); + Assert.True(state.TryGetTextVersion(out _)); + } + [Fact] public void DocumentState_WithWorkspaceProjectChange_CachesSnapshotText() { @@ -160,7 +178,6 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem Assert.True(state.TryGetTextVersion(out _)); } - [Fact] public async Task DocumentState_WithWorkspaceProjectChange_CachesLoadedText() { diff --git a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/ProjectStateTest.cs b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/ProjectStateTest.cs index 585d5af27e..81f022e67f 100644 --- a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/ProjectStateTest.cs +++ b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/ProjectStateTest.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Language; @@ -12,26 +13,14 @@ using Xunit; namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { - public class ProjectStateTest + public class ProjectStateTest : WorkspaceTestBase { public ProjectStateTest() { TagHelperResolver = new TestTagHelperResolver(); - HostServices = TestServices.Create( - new IWorkspaceService[] - { - new TestProjectSnapshotProjectEngineFactory(), - }, - new ILanguageService[] - { - TagHelperResolver, - }); - - HostProject = new HostProject("c:\\MyProject\\Test.csproj", FallbackRazorConfiguration.MVC_2_0); - HostProjectWithConfigurationChange = new HostProject("c:\\MyProject\\Test.csproj", FallbackRazorConfiguration.MVC_1_0); - - Workspace = TestWorkspace.Create(HostServices); + HostProject = new HostProject(TestProjectData.SomeProject.FilePath, FallbackRazorConfiguration.MVC_2_0); + HostProjectWithConfigurationChange = new HostProject(TestProjectData.SomeProject.FilePath, FallbackRazorConfiguration.MVC_1_0); var projectId = ProjectId.CreateNewId("Test"); var solution = Workspace.CurrentSolution.AddProject(ProjectInfo.Create( @@ -40,7 +29,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem "Test", "Test", LanguageNames.CSharp, - "c:\\MyProject\\Test.csproj")); + TestProjectData.SomeProject.FilePath)); WorkspaceProject = solution.GetProject(projectId); SomeTagHelpers = new List(); @@ -48,11 +37,11 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem Documents = new HostDocument[] { - new HostDocument("c:\\MyProject\\File.cshtml", "File.cshtml"), - new HostDocument("c:\\MyProject\\Index.cshtml", "Index.cshtml"), + TestProjectData.SomeProjectFile1, + TestProjectData.SomeProjectFile2, // linked file - new HostDocument("c:\\SomeOtherProject\\Index.cshtml", "Pages\\Index.cshtml"), + TestProjectData.AnotherProjectNestedFile3, }; Text = SourceText.From("Hello, world!"); @@ -69,16 +58,23 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem private TestTagHelperResolver TagHelperResolver { get; } - private HostServices HostServices { get; } - - private Workspace Workspace { get; } - private List SomeTagHelpers { get; } private Func> TextLoader { get; } private SourceText Text { get; } + protected override void ConfigureLanguageServices(List services) + { + services.Add(TagHelperResolver); + } + + protected override void ConfigureProjectEngine(RazorProjectEngineBuilder builder) + { + builder.Features.Remove(builder.Features.OfType().Single()); + builder.Features.Add(new TestImportProjectFeature()); + } + [Fact] public void ProjectState_ConstructedNew() { @@ -139,9 +135,93 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem Assert.Collection( state.Documents.OrderBy(kvp => kvp.Key), + d => Assert.Same(Documents[2], d.Value.HostDocument), d => Assert.Same(Documents[0], d.Value.HostDocument), - d => Assert.Same(Documents[1], d.Value.HostDocument), - d => Assert.Same(Documents[2], d.Value.HostDocument)); + d => Assert.Same(Documents[1], d.Value.HostDocument)); + } + + [Fact] + public void ProjectState_AddHostDocument_TracksImports() + { + // Arrange + + // Act + var state = ProjectState.Create(Workspace.Services, HostProject, WorkspaceProject) + .WithAddedHostDocument(TestProjectData.SomeProjectFile1, DocumentState.EmptyLoader) + .WithAddedHostDocument(TestProjectData.SomeProjectFile2, DocumentState.EmptyLoader) + .WithAddedHostDocument(TestProjectData.SomeProjectNestedFile3, DocumentState.EmptyLoader) + .WithAddedHostDocument(TestProjectData.AnotherProjectNestedFile4, DocumentState.EmptyLoader); + + // Assert + Assert.Collection( + state.ImportsToRelatedDocuments.OrderBy(kvp => kvp.Key), + kvp => + { + Assert.Equal(TestProjectData.SomeProjectImportFile.TargetPath, kvp.Key); + Assert.Equal( + new string[] + { + TestProjectData.AnotherProjectNestedFile4.FilePath, + TestProjectData.SomeProjectFile1.FilePath, + TestProjectData.SomeProjectFile2.FilePath, + TestProjectData.SomeProjectNestedFile3.FilePath, + }, + kvp.Value.OrderBy(f => f)); + }, + kvp => + { + Assert.Equal(TestProjectData.SomeProjectNestedImportFile.TargetPath, kvp.Key); + Assert.Equal( + new string[] + { + TestProjectData.AnotherProjectNestedFile4.FilePath, + TestProjectData.SomeProjectNestedFile3.FilePath, + }, + kvp.Value.OrderBy(f => f)); + }); + } + + [Fact] + public void ProjectState_AddHostDocument_TracksImports_AddImportFile() + { + // Arrange + var original = ProjectState.Create(Workspace.Services, HostProject, WorkspaceProject) + .WithAddedHostDocument(TestProjectData.SomeProjectFile1, DocumentState.EmptyLoader) + .WithAddedHostDocument(TestProjectData.SomeProjectFile2, DocumentState.EmptyLoader) + .WithAddedHostDocument(TestProjectData.SomeProjectNestedFile3, DocumentState.EmptyLoader) + .WithAddedHostDocument(TestProjectData.AnotherProjectNestedFile4, DocumentState.EmptyLoader); + + // Act + var state = original + .WithAddedHostDocument(TestProjectData.AnotherProjectImportFile, DocumentState.EmptyLoader); + + // Assert + Assert.Collection( + state.ImportsToRelatedDocuments.OrderBy(kvp => kvp.Key), + kvp => + { + Assert.Equal(TestProjectData.SomeProjectImportFile.TargetPath, kvp.Key); + Assert.Equal( + new string[] + { + TestProjectData.AnotherProjectNestedFile4.FilePath, + TestProjectData.SomeProjectFile1.FilePath, + TestProjectData.SomeProjectFile2.FilePath, + TestProjectData.SomeProjectNestedFile3.FilePath, + }, + kvp.Value.OrderBy(f => f)); + }, + kvp => + { + Assert.Equal(TestProjectData.SomeProjectNestedImportFile.TargetPath, kvp.Key); + Assert.Equal( + new string[] + { + TestProjectData.AnotherProjectNestedFile4.FilePath, + TestProjectData.SomeProjectNestedFile3.FilePath, + }, + kvp.Value.OrderBy(f => f)); + }); } [Fact] @@ -311,6 +391,68 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem d => Assert.Same(Documents[2], d.Value.HostDocument)); } + [Fact] + public void ProjectState_RemoveHostDocument_TracksImports() + { + // Arrange + var original = ProjectState.Create(Workspace.Services, HostProject, WorkspaceProject) + .WithAddedHostDocument(TestProjectData.SomeProjectFile1, DocumentState.EmptyLoader) + .WithAddedHostDocument(TestProjectData.SomeProjectFile2, DocumentState.EmptyLoader) + .WithAddedHostDocument(TestProjectData.SomeProjectNestedFile3, DocumentState.EmptyLoader) + .WithAddedHostDocument(TestProjectData.AnotherProjectNestedFile4, DocumentState.EmptyLoader); + + // Act + var state = original.WithRemovedHostDocument(TestProjectData.SomeProjectNestedFile3); + + // Assert + Assert.Collection( + state.ImportsToRelatedDocuments.OrderBy(kvp => kvp.Key), + kvp => + { + Assert.Equal(TestProjectData.SomeProjectImportFile.TargetPath, kvp.Key); + Assert.Equal( + new string[] + { + TestProjectData.AnotherProjectNestedFile4.FilePath, + TestProjectData.SomeProjectFile1.FilePath, + TestProjectData.SomeProjectFile2.FilePath, + }, + kvp.Value.OrderBy(f => f)); + }, + kvp => + { + Assert.Equal(TestProjectData.SomeProjectNestedImportFile.TargetPath, kvp.Key); + Assert.Equal( + new string[] + { + TestProjectData.AnotherProjectNestedFile4.FilePath, + }, + kvp.Value.OrderBy(f => f)); + }); + } + + [Fact] + public void ProjectState_RemoveHostDocument_TracksImports_RemoveAllDocuments() + { + // Arrange + var original = ProjectState.Create(Workspace.Services, HostProject, WorkspaceProject) + .WithAddedHostDocument(TestProjectData.SomeProjectFile1, DocumentState.EmptyLoader) + .WithAddedHostDocument(TestProjectData.SomeProjectFile2, DocumentState.EmptyLoader) + .WithAddedHostDocument(TestProjectData.SomeProjectNestedFile3, DocumentState.EmptyLoader) + .WithAddedHostDocument(TestProjectData.AnotherProjectNestedFile4, DocumentState.EmptyLoader); + + // Act + var state = original + .WithRemovedHostDocument(TestProjectData.SomeProjectFile1) + .WithRemovedHostDocument(TestProjectData.SomeProjectFile2) + .WithRemovedHostDocument(TestProjectData.SomeProjectNestedFile3) + .WithRemovedHostDocument(TestProjectData.AnotherProjectNestedFile4); + + // Assert + Assert.Empty(state.Documents); + Assert.Empty(state.ImportsToRelatedDocuments); + } + [Fact] public void ProjectState_RemoveHostDocument_RetainsComputedState() { @@ -398,13 +540,13 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { // Arrange var callCount = 0; - - var documents = new Dictionary(); + + var documents = ImmutableDictionary.CreateBuilder(FilePathComparer.Instance); documents[Documents[1].FilePath] = TestDocumentState.Create(Workspace.Services, Documents[1], onConfigurationChange: () => callCount++); documents[Documents[2].FilePath] = TestDocumentState.Create(Workspace.Services, Documents[2], onConfigurationChange: () => callCount++); var original = ProjectState.Create(Workspace.Services, HostProject, WorkspaceProject); - original.Documents = documents; + original.Documents = documents.ToImmutable(); var changed = WorkspaceProject.WithAssemblyName("Test1"); @@ -503,12 +645,12 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem // Arrange var callCount = 0; - var documents = new Dictionary(); + var documents = ImmutableDictionary.CreateBuilder(FilePathComparer.Instance); documents[Documents[1].FilePath] = TestDocumentState.Create(Workspace.Services, Documents[1], onWorkspaceProjectChange: () => callCount++); documents[Documents[2].FilePath] = TestDocumentState.Create(Workspace.Services, Documents[2], onWorkspaceProjectChange: () => callCount++); var original = ProjectState.Create(Workspace.Services, HostProject, WorkspaceProject); - original.Documents = documents; + original.Documents = documents.ToImmutable(); var changed = WorkspaceProject.WithAssemblyName("Test1"); @@ -520,6 +662,231 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem Assert.Equal(2, callCount); } + [Fact] + public void ProjectState_WhenImportDocumentAdded_CallsImportsChanged() + { + // Arrange + var callCount = 0; + + var document1 = TestProjectData.SomeProjectFile1; + var document2 = TestProjectData.SomeProjectFile2; + var document3 = TestProjectData.SomeProjectNestedFile3; + var document4 = TestProjectData.AnotherProjectNestedFile4; + + var documents = ImmutableDictionary.CreateBuilder(FilePathComparer.Instance); + documents[document1.FilePath] = TestDocumentState.Create(Workspace.Services, document1, onImportsChange: () => callCount++); + documents[document2.FilePath] = TestDocumentState.Create(Workspace.Services, document2, onImportsChange: () => callCount++); + documents[document3.FilePath] = TestDocumentState.Create(Workspace.Services, document3, onImportsChange: () => callCount++); + documents[document4.FilePath] = TestDocumentState.Create(Workspace.Services, document4, onImportsChange: () => callCount++); + + var importsToRelatedDocuments = ImmutableDictionary.CreateBuilder>(FilePathComparer.Instance); + importsToRelatedDocuments.Add( + TestProjectData.SomeProjectImportFile.TargetPath, + ImmutableArray.Create( + TestProjectData.SomeProjectFile1.FilePath, + TestProjectData.SomeProjectFile2.FilePath, + TestProjectData.SomeProjectNestedFile3.FilePath, + TestProjectData.AnotherProjectNestedFile4.FilePath)); + importsToRelatedDocuments.Add( + TestProjectData.SomeProjectNestedImportFile.TargetPath, + ImmutableArray.Create( + TestProjectData.SomeProjectNestedFile3.FilePath, + TestProjectData.AnotherProjectNestedFile4.FilePath)); + + var original = ProjectState.Create(Workspace.Services, HostProject, WorkspaceProject); + original.Documents = documents.ToImmutable(); + original.ImportsToRelatedDocuments = importsToRelatedDocuments.ToImmutable(); + + // Act + var state = original.WithAddedHostDocument(TestProjectData.AnotherProjectImportFile, DocumentState.EmptyLoader); + + // Assert + Assert.NotEqual(original.Version, state.Version); + Assert.Equal(4, callCount); + } + + [Fact] + public void ProjectState_WhenImportDocumentAdded_CallsImportsChanged_Nested() + { + // Arrange + var callCount = 0; + + var document1 = TestProjectData.SomeProjectFile1; + var document2 = TestProjectData.SomeProjectFile2; + var document3 = TestProjectData.SomeProjectNestedFile3; + var document4 = TestProjectData.AnotherProjectNestedFile4; + + var documents = ImmutableDictionary.CreateBuilder(FilePathComparer.Instance); + documents[document1.FilePath] = TestDocumentState.Create(Workspace.Services, document1, onImportsChange: () => callCount++); + documents[document2.FilePath] = TestDocumentState.Create(Workspace.Services, document2, onImportsChange: () => callCount++); + documents[document3.FilePath] = TestDocumentState.Create(Workspace.Services, document3, onImportsChange: () => callCount++); + documents[document4.FilePath] = TestDocumentState.Create(Workspace.Services, document4, onImportsChange: () => callCount++); + + var importsToRelatedDocuments = ImmutableDictionary.CreateBuilder>(FilePathComparer.Instance); + importsToRelatedDocuments.Add( + TestProjectData.SomeProjectImportFile.TargetPath, + ImmutableArray.Create( + TestProjectData.SomeProjectFile1.FilePath, + TestProjectData.SomeProjectFile2.FilePath, + TestProjectData.SomeProjectNestedFile3.FilePath, + TestProjectData.AnotherProjectNestedFile4.FilePath)); + importsToRelatedDocuments.Add( + TestProjectData.SomeProjectNestedImportFile.TargetPath, + ImmutableArray.Create( + TestProjectData.SomeProjectNestedFile3.FilePath, + TestProjectData.AnotherProjectNestedFile4.FilePath)); + + var original = ProjectState.Create(Workspace.Services, HostProject, WorkspaceProject); + original.Documents = documents.ToImmutable(); + original.ImportsToRelatedDocuments = importsToRelatedDocuments.ToImmutable(); + + // Act + var state = original.WithAddedHostDocument(TestProjectData.AnotherProjectNestedImportFile, DocumentState.EmptyLoader); + + // Assert + Assert.NotEqual(original.Version, state.Version); + Assert.Equal(2, callCount); + } + + [Fact] + public void ProjectState_WhenImportDocumentChangedTextLoader_CallsImportsChanged() + { + // Arrange + var callCount = 0; + + var document1 = TestProjectData.SomeProjectFile1; + var document2 = TestProjectData.SomeProjectFile2; + var document3 = TestProjectData.SomeProjectNestedFile3; + var document4 = TestProjectData.AnotherProjectNestedFile4; + var document5 = TestProjectData.AnotherProjectNestedImportFile; + + var documents = ImmutableDictionary.CreateBuilder(FilePathComparer.Instance); + documents[document1.FilePath] = TestDocumentState.Create(Workspace.Services, document1, onImportsChange: () => callCount++); + documents[document2.FilePath] = TestDocumentState.Create(Workspace.Services, document2, onImportsChange: () => callCount++); + documents[document3.FilePath] = TestDocumentState.Create(Workspace.Services, document3, onImportsChange: () => callCount++); + documents[document4.FilePath] = TestDocumentState.Create(Workspace.Services, document4, onImportsChange: () => callCount++); + documents[document5.FilePath] = TestDocumentState.Create(Workspace.Services, document5, onImportsChange: () => callCount++); + + var importsToRelatedDocuments = ImmutableDictionary.CreateBuilder>(FilePathComparer.Instance); + importsToRelatedDocuments.Add( + TestProjectData.SomeProjectImportFile.TargetPath, + ImmutableArray.Create( + TestProjectData.SomeProjectFile1.FilePath, + TestProjectData.SomeProjectFile2.FilePath, + TestProjectData.SomeProjectNestedFile3.FilePath, + TestProjectData.AnotherProjectNestedFile4.FilePath, + TestProjectData.AnotherProjectNestedImportFile.FilePath)); + importsToRelatedDocuments.Add( + TestProjectData.SomeProjectNestedImportFile.TargetPath, + ImmutableArray.Create( + TestProjectData.SomeProjectNestedFile3.FilePath, + TestProjectData.AnotherProjectNestedFile4.FilePath)); + + var original = ProjectState.Create(Workspace.Services, HostProject, WorkspaceProject); + original.Documents = documents.ToImmutable(); + original.ImportsToRelatedDocuments = importsToRelatedDocuments.ToImmutable(); + + // Act + var state = original.WithChangedHostDocument(document5, DocumentState.EmptyLoader); + + // Assert + Assert.NotEqual(original.Version, state.Version); + Assert.Equal(2, callCount); + } + + [Fact] + public void ProjectState_WhenImportDocumentChangedSnapshot_CallsImportsChanged() + { + // Arrange + var callCount = 0; + + var document1 = TestProjectData.SomeProjectFile1; + var document2 = TestProjectData.SomeProjectFile2; + var document3 = TestProjectData.SomeProjectNestedFile3; + var document4 = TestProjectData.AnotherProjectNestedFile4; + var document5 = TestProjectData.AnotherProjectNestedImportFile; + + var documents = ImmutableDictionary.CreateBuilder(FilePathComparer.Instance); + documents[document1.FilePath] = TestDocumentState.Create(Workspace.Services, document1, onImportsChange: () => callCount++); + documents[document2.FilePath] = TestDocumentState.Create(Workspace.Services, document2, onImportsChange: () => callCount++); + documents[document3.FilePath] = TestDocumentState.Create(Workspace.Services, document3, onImportsChange: () => callCount++); + documents[document4.FilePath] = TestDocumentState.Create(Workspace.Services, document4, onImportsChange: () => callCount++); + documents[document5.FilePath] = TestDocumentState.Create(Workspace.Services, document5, onImportsChange: () => callCount++); + + var importsToRelatedDocuments = ImmutableDictionary.CreateBuilder>(FilePathComparer.Instance); + importsToRelatedDocuments.Add( + TestProjectData.SomeProjectImportFile.TargetPath, + ImmutableArray.Create( + TestProjectData.SomeProjectFile1.FilePath, + TestProjectData.SomeProjectFile2.FilePath, + TestProjectData.SomeProjectNestedFile3.FilePath, + TestProjectData.AnotherProjectNestedFile4.FilePath, + TestProjectData.AnotherProjectNestedImportFile.FilePath)); + importsToRelatedDocuments.Add( + TestProjectData.SomeProjectNestedImportFile.TargetPath, + ImmutableArray.Create( + TestProjectData.SomeProjectNestedFile3.FilePath, + TestProjectData.AnotherProjectNestedFile4.FilePath)); + + var original = ProjectState.Create(Workspace.Services, HostProject, WorkspaceProject); + original.Documents = documents.ToImmutable(); + original.ImportsToRelatedDocuments = importsToRelatedDocuments.ToImmutable(); + + // Act + var state = original.WithChangedHostDocument(document5, Text, VersionStamp.Create()); + + // Assert + Assert.NotEqual(original.Version, state.Version); + Assert.Equal(2, callCount); + } + + + [Fact] + public void ProjectState_WhenImportDocumentRemoved_CallsImportsChanged() + { + // Arrange + var callCount = 0; + + var document1 = TestProjectData.SomeProjectFile1; + var document2 = TestProjectData.SomeProjectFile2; + var document3 = TestProjectData.SomeProjectNestedFile3; + var document4 = TestProjectData.AnotherProjectNestedFile4; + var document5 = TestProjectData.AnotherProjectNestedImportFile; + + var documents = ImmutableDictionary.CreateBuilder(FilePathComparer.Instance); + documents[document1.FilePath] = TestDocumentState.Create(Workspace.Services, document1, onImportsChange: () => callCount++); + documents[document2.FilePath] = TestDocumentState.Create(Workspace.Services, document2, onImportsChange: () => callCount++); + documents[document3.FilePath] = TestDocumentState.Create(Workspace.Services, document3, onImportsChange: () => callCount++); + documents[document4.FilePath] = TestDocumentState.Create(Workspace.Services, document4, onImportsChange: () => callCount++); + documents[document5.FilePath] = TestDocumentState.Create(Workspace.Services, document5, onImportsChange: () => callCount++); + + var importsToRelatedDocuments = ImmutableDictionary.CreateBuilder>(FilePathComparer.Instance); + importsToRelatedDocuments.Add( + TestProjectData.SomeProjectImportFile.TargetPath, + ImmutableArray.Create( + TestProjectData.SomeProjectFile1.FilePath, + TestProjectData.SomeProjectFile2.FilePath, + TestProjectData.SomeProjectNestedFile3.FilePath, + TestProjectData.AnotherProjectNestedFile4.FilePath, + TestProjectData.AnotherProjectNestedImportFile.FilePath)); + importsToRelatedDocuments.Add( + TestProjectData.SomeProjectNestedImportFile.TargetPath, + ImmutableArray.Create( + TestProjectData.SomeProjectNestedFile3.FilePath, + TestProjectData.AnotherProjectNestedFile4.FilePath)); + + var original = ProjectState.Create(Workspace.Services, HostProject, WorkspaceProject); + original.Documents = documents.ToImmutable(); + original.ImportsToRelatedDocuments = importsToRelatedDocuments.ToImmutable(); + + // Act + var state = original.WithRemovedHostDocument(document5); + + // Assert + Assert.NotEqual(original.Version, state.Version); + Assert.Equal(2, callCount); + } + private class TestDocumentState : DocumentState { public static TestDocumentState Create( @@ -529,15 +896,27 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem Action onTextChange = null, Action onTextLoaderChange = null, Action onConfigurationChange = null, + Action onImportsChange = null, Action onWorkspaceProjectChange = null) { - return new TestDocumentState(services, hostDocument, null, null, loader, onTextChange, onTextLoaderChange, onConfigurationChange, onWorkspaceProjectChange); + return new TestDocumentState( + services, + hostDocument, + null, + null, + loader, + onTextChange, + onTextLoaderChange, + onConfigurationChange, + onImportsChange, + onWorkspaceProjectChange); } - Action _onTextChange; - Action _onTextLoaderChange; - Action _onConfigurationChange; - Action _onWorkspaceProjectChange; + private readonly Action _onTextChange; + private readonly Action _onTextLoaderChange; + private readonly Action _onConfigurationChange; + private readonly Action _onImportsChange; + private readonly Action _onWorkspaceProjectChange; private TestDocumentState( HostWorkspaceServices services, @@ -548,12 +927,14 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem Action onTextChange, Action onTextLoaderChange, Action onConfigurationChange, + Action onImportsChange, Action onWorkspaceProjectChange) : base(services, hostDocument, text, version, loader) { _onTextChange = onTextChange; _onTextLoaderChange = onTextLoaderChange; _onConfigurationChange = onConfigurationChange; + _onImportsChange = onImportsChange; _onWorkspaceProjectChange = onWorkspaceProjectChange; } @@ -575,6 +956,12 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem return base.WithConfigurationChange(); } + public override DocumentState WithImportsChange() + { + _onImportsChange?.Invoke(); + return base.WithImportsChange(); + } + public override DocumentState WithWorkspaceProjectChange() { _onWorkspaceProjectChange?.Invoke(); diff --git a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/Shared/TestImportProjectFeature.cs b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/Shared/TestImportProjectFeature.cs new file mode 100644 index 0000000000..3a00772223 --- /dev/null +++ b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/Shared/TestImportProjectFeature.cs @@ -0,0 +1,32 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.AspNetCore.Razor.Language +{ + public class TestImportProjectFeature : RazorProjectEngineFeatureBase, IImportProjectFeature + { + public IReadOnlyList GetImports(RazorProjectItem projectItem) + { + if (projectItem == null) + { + throw new ArgumentNullException(nameof(projectItem)); + } + + var imports = new List(); + AddHierarchicalImports(projectItem, imports); + + return imports; + } + + private void AddHierarchicalImports(RazorProjectItem projectItem, List imports) + { + // We want items in descending order. FindHierarchicalItems returns items in ascending order. + var importProjectItems = ProjectEngine.FileSystem.FindHierarchicalItems(projectItem.FilePath, "_Imports.cshtml").Reverse(); + imports.AddRange(importProjectItems); + } + } +} diff --git a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/Shared/TestProjectData.cs b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/Shared/TestProjectData.cs new file mode 100644 index 0000000000..1f7182900a --- /dev/null +++ b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/Shared/TestProjectData.cs @@ -0,0 +1,56 @@ +// 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.IO; +using System.Runtime.InteropServices; +using Microsoft.AspNetCore.Razor.Language; +using Microsoft.CodeAnalysis.Razor.ProjectSystem; + +namespace Microsoft.CodeAnalysis.Razor +{ + // Used to abstract away platform-specific file/directory path information. + // + // The System.IO.Path methods don't processes Windows paths in a Windows way + // on *nix (rightly so), so we need to use platform-specific paths. + // + // Target paths are always Windows style. + internal static class TestProjectData + { + static TestProjectData() + { + var baseDirectory = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "c:\\users\\example\\src" : "/home/example"; + + SomeProject = new HostProject(Path.Combine(baseDirectory, "SomeProject", "SomeProject.csproj"), RazorConfiguration.Default); + SomeProjectFile1 = new HostDocument(Path.Combine(baseDirectory, "SomeProject", "File1.cshtml"), "File1.cshtml"); + SomeProjectFile2 = new HostDocument(Path.Combine(baseDirectory, "SomeProject", "File2.cshtml"), "File2.cshtml"); + SomeProjectImportFile = new HostDocument(Path.Combine(baseDirectory, "SomeProject", "_Imports.cshtml"), "_Imports.cshtml"); + SomeProjectNestedFile3 = new HostDocument(Path.Combine(baseDirectory, "SomeProject", "Nested", "File3.cshtml"), "Nested\\File1.cshtml"); + SomeProjectNestedFile4 = new HostDocument(Path.Combine(baseDirectory, "SomeProject", "Nested", "File4.cshtml"), "Nested\\File2.cshtml"); + SomeProjectNestedImportFile = new HostDocument(Path.Combine(baseDirectory, "SomeProject", "Nested", "_Imports.cshtml"), "Nested\\_Imports.cshtml"); + + AnotherProject = new HostProject(Path.Combine(baseDirectory, "AnotherProject", "AnotherProject.csproj"), RazorConfiguration.Default); + AnotherProjectFile1 = new HostDocument(Path.Combine(baseDirectory, "AnotherProject", "File1.cshtml"), "File1.cshtml"); + AnotherProjectFile2 = new HostDocument(Path.Combine(baseDirectory, "AnotherProject", "File2.cshtml"), "File2.cshtml"); + AnotherProjectImportFile = new HostDocument(Path.Combine(baseDirectory, "AnotherProject", "_Imports.cshtml"), "_Imports.cshtml"); + AnotherProjectNestedFile3 = new HostDocument(Path.Combine(baseDirectory, "AnotherProject", "Nested", "File3.cshtml"), "Nested\\File1.cshtml"); + AnotherProjectNestedFile4 = new HostDocument(Path.Combine(baseDirectory, "AnotherProject", "Nested", "File4.cshtml"), "Nested\\File2.cshtml"); + AnotherProjectNestedImportFile = new HostDocument(Path.Combine(baseDirectory, "AnotherProject", "Nested", "_Imports.cshtml"), "Nested\\_Imports.cshtml"); + } + + public static readonly HostProject SomeProject; + public static readonly HostDocument SomeProjectFile1; + public static readonly HostDocument SomeProjectFile2; + public static readonly HostDocument SomeProjectImportFile; + public static readonly HostDocument SomeProjectNestedFile3; + public static readonly HostDocument SomeProjectNestedFile4; + public static readonly HostDocument SomeProjectNestedImportFile; + + public static readonly HostProject AnotherProject; + public static readonly HostDocument AnotherProjectFile1; + public static readonly HostDocument AnotherProjectFile2; + public static readonly HostDocument AnotherProjectImportFile; + public static readonly HostDocument AnotherProjectNestedFile3; + public static readonly HostDocument AnotherProjectNestedFile4; + public static readonly HostDocument AnotherProjectNestedImportFile; + } +} diff --git a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/Shared/TestProjectSnapshotProjectEngineFactory.cs b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/Shared/TestProjectSnapshotProjectEngineFactory.cs index fcbb5fbc3a..61d923062f 100644 --- a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/Shared/TestProjectSnapshotProjectEngineFactory.cs +++ b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/Shared/TestProjectSnapshotProjectEngineFactory.cs @@ -8,11 +8,13 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { internal class TestProjectSnapshotProjectEngineFactory : ProjectSnapshotProjectEngineFactory { + public Action Configure { get; set; } + public RazorProjectEngine Engine { get; set; } - public override RazorProjectEngine Create(ProjectSnapshot project, RazorProjectFileSystem fileSystem, Action configure) + public override RazorProjectEngine Create(RazorConfiguration configuration, RazorProjectFileSystem fileSystem, Action configure) { - return Engine ?? RazorProjectEngine.Create(project.Configuration, fileSystem, configure); + return Engine ?? RazorProjectEngine.Create(configuration, fileSystem, configure ?? Configure); } public override IProjectEngineFactory FindFactory(ProjectSnapshot project) diff --git a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/Shared/WorkspaceTestBase.cs b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/Shared/WorkspaceTestBase.cs new file mode 100644 index 0000000000..0608261af0 --- /dev/null +++ b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/Shared/WorkspaceTestBase.cs @@ -0,0 +1,79 @@ +// 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.Collections.Generic; +using Microsoft.AspNetCore.Razor.Language; +using Microsoft.CodeAnalysis.Host; +using Microsoft.CodeAnalysis.Razor.ProjectSystem; + +namespace Microsoft.CodeAnalysis.Razor +{ + public abstract class WorkspaceTestBase + { + private bool _initialized; + private HostServices _hostServices; + private Workspace _workspace; + + protected WorkspaceTestBase() + { + } + + protected HostServices HostServices + { + get + { + EnsureInitialized(); + return _hostServices; + } + } + + protected Workspace Workspace + { + get + { + EnsureInitialized(); + return _workspace; + } + } + + protected virtual void ConfigureWorkspaceServices(List services) + { + } + + protected virtual void ConfigureLanguageServices(List services) + { + } + + protected virtual void ConfigureWorkspace(AdhocWorkspace workspace) + { + } + + protected virtual void ConfigureProjectEngine(RazorProjectEngineBuilder builder) + { + } + + private void EnsureInitialized() + { + if (_initialized) + { + return; + } + + var workspaceServices = new List() + { + new TestProjectSnapshotProjectEngineFactory() + { + Configure = ConfigureProjectEngine, + }, + }; + ConfigureWorkspaceServices(workspaceServices); + + var languageServices = new List(); + ConfigureLanguageServices(languageServices); + + _hostServices = TestServices.Create(workspaceServices, languageServices); + _workspace = TestWorkspace.Create(_hostServices, ConfigureWorkspace); + _initialized = true; + } + } +} diff --git a/test/Microsoft.VisualStudio.Editor.Razor.Test.Common/SingleThreadedForegroundDispatcher.cs b/test/Microsoft.VisualStudio.Editor.Razor.Test.Common/SingleThreadedForegroundDispatcher.cs new file mode 100644 index 0000000000..7c76826397 --- /dev/null +++ b/test/Microsoft.VisualStudio.Editor.Razor.Test.Common/SingleThreadedForegroundDispatcher.cs @@ -0,0 +1,47 @@ +// 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.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.CodeAnalysis.Razor +{ + internal class SingleThreadedForegroundDispatcher : ForegroundDispatcher + { + public SingleThreadedForegroundDispatcher() + { + ForegroundScheduler = SynchronizationContext.Current == null ? new ThrowingTaskScheduler() : TaskScheduler.FromCurrentSynchronizationContext(); + BackgroundScheduler = TaskScheduler.Default; + } + + public override TaskScheduler ForegroundScheduler { get; } + + public override TaskScheduler BackgroundScheduler { get; } + + private Thread Thread { get; } = Thread.CurrentThread; + + public override bool IsForegroundThread => Thread.CurrentThread == Thread; + + private class ThrowingTaskScheduler : TaskScheduler + { + protected override IEnumerable GetScheduledTasks() + { + return Enumerable.Empty(); + } + + protected override void QueueTask(Task task) + { + throw new InvalidOperationException($"Use [{nameof(ForegroundFactAttribute)}]"); + } + + protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) + { + throw new InvalidOperationException($"Use [{nameof(ForegroundFactAttribute)}]"); + } + } + } +} diff --git a/test/Microsoft.VisualStudio.Editor.Razor.Test.Common/Xunit/ForegroundDispatcherTestBase.cs b/test/Microsoft.VisualStudio.Editor.Razor.Test.Common/Xunit/ForegroundDispatcherTestBase.cs index 0bcbb7b697..1690577b82 100644 --- a/test/Microsoft.VisualStudio.Editor.Razor.Test.Common/Xunit/ForegroundDispatcherTestBase.cs +++ b/test/Microsoft.VisualStudio.Editor.Razor.Test.Common/Xunit/ForegroundDispatcherTestBase.cs @@ -1,11 +1,6 @@ // 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.Collections.Generic; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; using Microsoft.CodeAnalysis.Razor; namespace Xunit @@ -13,40 +8,5 @@ namespace Xunit public abstract class ForegroundDispatcherTestBase { internal ForegroundDispatcher Dispatcher { get; } = new SingleThreadedForegroundDispatcher(); - - private class SingleThreadedForegroundDispatcher : ForegroundDispatcher - { - public SingleThreadedForegroundDispatcher() - { - ForegroundScheduler = SynchronizationContext.Current == null ? new ThrowingTaskScheduler() : TaskScheduler.FromCurrentSynchronizationContext(); - BackgroundScheduler = TaskScheduler.Default; - } - - public override TaskScheduler ForegroundScheduler { get; } - - public override TaskScheduler BackgroundScheduler { get; } - - private Thread Thread { get; } = Thread.CurrentThread; - - public override bool IsForegroundThread => Thread.CurrentThread == Thread; - } - - private class ThrowingTaskScheduler : TaskScheduler - { - protected override IEnumerable GetScheduledTasks() - { - return Enumerable.Empty(); - } - - protected override void QueueTask(Task task) - { - throw new InvalidOperationException($"Use [{nameof(ForegroundFactAttribute)}]"); - } - - protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) - { - throw new InvalidOperationException($"Use [{nameof(ForegroundFactAttribute)}]"); - } - } } } diff --git a/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultImportDocumentManagerIntegrationTest.cs b/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultImportDocumentManagerIntegrationTest.cs index 9fd3cf04ea..8a4086b294 100644 --- a/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultImportDocumentManagerIntegrationTest.cs +++ b/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultImportDocumentManagerIntegrationTest.cs @@ -14,7 +14,8 @@ namespace Microsoft.VisualStudio.Editor.Razor.Documents { public DefaultImportDocumentManagerIntegrationTest() { - ProjectPath = "C:\\path\\to\\project\\project.csproj"; + ProjectPath = TestProjectData.SomeProject.FilePath; + DirectoryPath = Path.GetDirectoryName(ProjectPath); FileSystem = RazorProjectFileSystem.Create(Path.GetDirectoryName(ProjectPath)); ProjectEngine = RazorProjectEngine.Create(FallbackRazorConfiguration.MVC_2_1, FileSystem, b => @@ -24,7 +25,7 @@ namespace Microsoft.VisualStudio.Editor.Razor.Documents }); } - private string FilePath { get; } + private string DirectoryPath { get; } private string ProjectPath { get; } @@ -36,15 +37,15 @@ namespace Microsoft.VisualStudio.Editor.Razor.Documents public void Changed_TrackerChanged_ResultsInChangedHavingCorrectArgs() { // Arrange - var testImportsPath = "C:\\path\\to\\project\\_ViewImports.cshtml"; - + var testImportsPath = Path.Combine(DirectoryPath, "_ViewImports.cshtml"); + var tracker = Mock.Of( - t => t.FilePath == "C:\\path\\to\\project\\Views\\Home\\file.cshtml" && + t => t.FilePath == Path.Combine(DirectoryPath, "Views", "Home", "_ViewImports.cshtml") && t.ProjectPath == ProjectPath && t.ProjectSnapshot == Mock.Of(p => p.GetProjectEngine() == ProjectEngine)); var anotherTracker = Mock.Of( - t => t.FilePath == "C:\\path\\to\\project\\anotherFile.cshtml" && + t => t.FilePath == Path.Combine(DirectoryPath, "anotherFile.cshtml") && t.ProjectPath == ProjectPath && t.ProjectSnapshot == Mock.Of(p => p.GetProjectEngine() == ProjectEngine)); @@ -56,12 +57,12 @@ namespace Microsoft.VisualStudio.Editor.Razor.Documents fileChangeTrackerFactory .Setup(f => f.Create(testImportsPath)) .Returns(fileChangeTracker.Object); - + fileChangeTrackerFactory - .Setup(f => f.Create("C:\\path\\to\\project\\Views\\_ViewImports.cshtml")) + .Setup(f => f.Create(Path.Combine(DirectoryPath, "Views", "_ViewImports.cshtml"))) .Returns(Mock.Of()); fileChangeTrackerFactory - .Setup(f => f.Create("C:\\path\\to\\project\\Views\\Home\\_ViewImports.cshtml")) + .Setup(f => f.Create(Path.Combine(DirectoryPath, "Views", "Home", "_ViewImports.cshtml"))) .Returns(Mock.Of()); var called = false; @@ -76,8 +77,8 @@ namespace Microsoft.VisualStudio.Editor.Razor.Documents Assert.Equal(FileChangeKind.Changed, args.Kind); Assert.Collection( args.AssociatedDocuments, - f => Assert.Equal("C:\\path\\to\\project\\Views\\Home\\file.cshtml", f), - f => Assert.Equal("C:\\path\\to\\project\\anotherFile.cshtml", f)); + f => Assert.Equal(Path.Combine(DirectoryPath, "Views", "Home", "_ViewImports.cshtml"), f), + f => Assert.Equal(Path.Combine(DirectoryPath, "anotherFile.cshtml"), f)); }; // Act diff --git a/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultImportDocumentManagerTest.cs b/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultImportDocumentManagerTest.cs index a84a26fb04..77c4e8cbaf 100644 --- a/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultImportDocumentManagerTest.cs +++ b/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultImportDocumentManagerTest.cs @@ -16,7 +16,8 @@ namespace Microsoft.VisualStudio.Editor.Razor { public DefaultImportDocumentManagerTest() { - ProjectPath = "C:\\path\\to\\project\\project.csproj"; + ProjectPath = TestProjectData.SomeProject.FilePath; + DirectoryPath = Path.GetDirectoryName(ProjectPath); FileSystem = RazorProjectFileSystem.Create(Path.GetDirectoryName(ProjectPath)); ProjectEngine = RazorProjectEngine.Create(FallbackRazorConfiguration.MVC_2_1, FileSystem, b => @@ -26,10 +27,10 @@ namespace Microsoft.VisualStudio.Editor.Razor }); } - private string FilePath { get; } - private string ProjectPath { get; } + private string DirectoryPath { get; } + private RazorProjectFileSystem FileSystem { get; } private RazorProjectEngine ProjectEngine { get; } @@ -39,17 +40,17 @@ namespace Microsoft.VisualStudio.Editor.Razor { // Arrange var tracker = Mock.Of( - t => t.FilePath == "C:\\path\\to\\project\\Views\\Home\\file.cshtml" && + t => t.FilePath == Path.Combine(DirectoryPath, "Views", "Home", "file.cshtml") && t.ProjectPath == ProjectPath && t.ProjectSnapshot == Mock.Of(p => p.GetProjectEngine() == ProjectEngine)); - + var fileChangeTrackerFactory = new Mock(); var fileChangeTracker1 = new Mock(); fileChangeTracker1 .Setup(f => f.StartListening()) .Verifiable(); fileChangeTrackerFactory - .Setup(f => f.Create("C:\\path\\to\\project\\Views\\Home\\_ViewImports.cshtml")) + .Setup(f => f.Create(Path.Combine(DirectoryPath, "Views", "Home", "_ViewImports.cshtml"))) .Returns(fileChangeTracker1.Object) .Verifiable(); var fileChangeTracker2 = new Mock(); @@ -57,13 +58,13 @@ namespace Microsoft.VisualStudio.Editor.Razor .Setup(f => f.StartListening()) .Verifiable(); fileChangeTrackerFactory - .Setup(f => f.Create("C:\\path\\to\\project\\Views\\_ViewImports.cshtml")) + .Setup(f => f.Create(Path.Combine(DirectoryPath, "Views", "_ViewImports.cshtml"))) .Returns(fileChangeTracker2.Object) .Verifiable(); var fileChangeTracker3 = new Mock(); fileChangeTracker3.Setup(f => f.StartListening()).Verifiable(); fileChangeTrackerFactory - .Setup(f => f.Create("C:\\path\\to\\project\\_ViewImports.cshtml")) + .Setup(f => f.Create(Path.Combine(DirectoryPath, "_ViewImports.cshtml"))) .Returns(fileChangeTracker3.Object) .Verifiable(); @@ -84,12 +85,12 @@ namespace Microsoft.VisualStudio.Editor.Razor { // Arrange var tracker = Mock.Of( - t => t.FilePath == "C:\\path\\to\\project\\file.cshtml" && + t => t.FilePath == Path.Combine(DirectoryPath, "file.cshtml") && t.ProjectPath == ProjectPath && t.ProjectSnapshot == Mock.Of(p => p.GetProjectEngine() == ProjectEngine)); var anotherTracker = Mock.Of( - t => t.FilePath == "C:\\path\\to\\project\\anotherFile.cshtml" && + t => t.FilePath == Path.Combine(DirectoryPath, "anotherFile.cshtml") && t.ProjectPath == ProjectPath && t.ProjectSnapshot == Mock.Of(p => p.GetProjectEngine() == ProjectEngine)); @@ -115,7 +116,7 @@ namespace Microsoft.VisualStudio.Editor.Razor { // Arrange var tracker = Mock.Of( - t => t.FilePath == "C:\\path\\to\\project\\file.cshtml" && + t => t.FilePath == Path.Combine(DirectoryPath, "file.cshtml") && t.ProjectPath == ProjectPath && t.ProjectSnapshot == Mock.Of(p => p.GetProjectEngine() == ProjectEngine)); @@ -125,7 +126,7 @@ namespace Microsoft.VisualStudio.Editor.Razor .Setup(f => f.StopListening()) .Verifiable(); fileChangeTrackerFactory - .Setup(f => f.Create("C:\\path\\to\\project\\_ViewImports.cshtml")) + .Setup(f => f.Create(Path.Combine(DirectoryPath, "_ViewImports.cshtml"))) .Returns(fileChangeTracker.Object) .Verifiable(); @@ -145,12 +146,12 @@ namespace Microsoft.VisualStudio.Editor.Razor { // Arrange var tracker = Mock.Of( - t => t.FilePath == "C:\\path\\to\\project\\file.cshtml" && + t => t.FilePath == Path.Combine(DirectoryPath, "file.cshtml") && t.ProjectPath == ProjectPath && t.ProjectSnapshot == Mock.Of(p => p.GetProjectEngine() == ProjectEngine)); var anotherTracker = Mock.Of( - t => t.FilePath == "C:\\path\\to\\project\\anotherFile.cshtml" && + t => t.FilePath == Path.Combine(DirectoryPath, "anotherFile.cshtml") && t.ProjectPath == ProjectPath && t.ProjectSnapshot == Mock.Of(p => p.GetProjectEngine() == ProjectEngine)); diff --git a/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultVisualStudioDocumentTrackerTest.cs b/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultVisualStudioDocumentTrackerTest.cs index 352c49ecc3..0a1051d62d 100644 --- a/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultVisualStudioDocumentTrackerTest.cs +++ b/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultVisualStudioDocumentTrackerTest.cs @@ -26,8 +26,8 @@ namespace Microsoft.VisualStudio.Editor.Razor RazorCoreContentType = Mock.Of(c => c.IsOfType(RazorLanguage.ContentType) == true); TextBuffer = Mock.Of(b => b.ContentType == RazorCoreContentType); - FilePath = "C:/Some/Path/TestDocumentTracker.cshtml"; - ProjectPath = "C:/Some/Path/TestProject.csproj"; + FilePath = TestProjectData.SomeProjectFile1.FilePath; + ProjectPath = TestProjectData.SomeProject.FilePath; ImportDocumentManager = Mock.Of(); WorkspaceEditorSettings = new DefaultWorkspaceEditorSettings(Mock.Of(), Mock.Of()); @@ -56,7 +56,8 @@ namespace Microsoft.VisualStudio.Editor.Razor ProjectManager = new TestProjectSnapshotManager(Dispatcher, Workspace) { AllowNotifyListeners = true }; HostProject = new HostProject(ProjectPath, FallbackRazorConfiguration.MVC_2_1); - OtherHostProject = new HostProject(ProjectPath, FallbackRazorConfiguration.MVC_2_0); + UpdatedHostProject = new HostProject(ProjectPath, FallbackRazorConfiguration.MVC_2_0); + OtherHostProject = new HostProject(TestProjectData.AnotherProject.FilePath, FallbackRazorConfiguration.MVC_2_0); DocumentTracker = new DefaultVisualStudioDocumentTracker( Dispatcher, @@ -79,6 +80,8 @@ namespace Microsoft.VisualStudio.Editor.Razor private HostProject HostProject { get; } + private HostProject UpdatedHostProject { get; } + private HostProject OtherHostProject { get; } private Project WorkspaceProject { get; set; } @@ -191,7 +194,7 @@ namespace Microsoft.VisualStudio.Editor.Razor ProjectManager.HostProjectAdded(HostProject); ProjectManager.WorkspaceProjectAdded(WorkspaceProject); - var e = new ProjectChangeEventArgs(ProjectPath, ProjectChangeKind.ProjectAdded); + var e = new ProjectChangeEventArgs(null, ProjectManager.GetLoadedProject(HostProject.FilePath), ProjectChangeKind.ProjectAdded); var called = false; DocumentTracker.ContextChanged += (sender, args) => @@ -215,8 +218,8 @@ namespace Microsoft.VisualStudio.Editor.Razor // Arrange ProjectManager.HostProjectAdded(HostProject); ProjectManager.WorkspaceProjectAdded(WorkspaceProject); - - var e = new ProjectChangeEventArgs(ProjectPath, ProjectChangeKind.ProjectChanged); + + var e = new ProjectChangeEventArgs(null, ProjectManager.GetLoadedProject(HostProject.FilePath), ProjectChangeKind.ProjectChanged); var called = false; DocumentTracker.ContextChanged += (sender, args) => @@ -238,7 +241,13 @@ namespace Microsoft.VisualStudio.Editor.Razor public void ProjectManager_Changed_ProjectRemoved_TriggersContextChanged_WithEphemeralProject() { // Arrange - var e = new ProjectChangeEventArgs(ProjectPath, ProjectChangeKind.ProjectRemoved); + ProjectManager.HostProjectAdded(HostProject); + ProjectManager.WorkspaceProjectAdded(WorkspaceProject); + + var project = ProjectManager.GetLoadedProject(HostProject.FilePath); + ProjectManager.HostProjectRemoved(HostProject); + + var e = new ProjectChangeEventArgs(project, null, ProjectChangeKind.ProjectRemoved); var called = false; DocumentTracker.ContextChanged += (sender, args) => @@ -260,7 +269,9 @@ namespace Microsoft.VisualStudio.Editor.Razor public void ProjectManager_Changed_IgnoresUnknownProject() { // Arrange - var e = new ProjectChangeEventArgs("c:/OtherPath/OtherProject.csproj", ProjectChangeKind.ProjectChanged); + ProjectManager.HostProjectAdded(OtherHostProject); + + var e = new ProjectChangeEventArgs(null, ProjectManager.GetLoadedProject(OtherHostProject.FilePath), ProjectChangeKind.ProjectChanged); var called = false; DocumentTracker.ContextChanged += (sender, args) => @@ -488,13 +499,13 @@ namespace Microsoft.VisualStudio.Editor.Razor DocumentTracker.ContextChanged += (sender, e) => { args.Add(e); }; // Act - ProjectManager.HostProjectChanged(OtherHostProject); + ProjectManager.HostProjectChanged(UpdatedHostProject); await DocumentTracker.PendingTagHelperTask; // Assert var snapshot = Assert.IsType(DocumentTracker.ProjectSnapshot); - Assert.Same(OtherHostProject, snapshot.HostProject); + Assert.Same(UpdatedHostProject, snapshot.HostProject); Assert.Collection( args, diff --git a/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultVisualStudioRazorParserTest.cs b/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultVisualStudioRazorParserTest.cs index 3df48c553f..70c4072528 100644 --- a/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultVisualStudioRazorParserTest.cs +++ b/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultVisualStudioRazorParserTest.cs @@ -24,7 +24,7 @@ namespace Microsoft.VisualStudio.Editor.Razor var engine = RazorProjectEngine.Create(RazorConfiguration.Default, RazorProjectFileSystem.Empty); ProjectEngineFactory = Mock.Of( f => f.Create( - It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny>()) == engine); } diff --git a/test/Microsoft.VisualStudio.Editor.Razor.Test/Documents/EditorDocumentManagerBaseTest.cs b/test/Microsoft.VisualStudio.Editor.Razor.Test/Documents/EditorDocumentManagerBaseTest.cs index e11fd055a3..bd81fa59de 100644 --- a/test/Microsoft.VisualStudio.Editor.Razor.Test/Documents/EditorDocumentManagerBaseTest.cs +++ b/test/Microsoft.VisualStudio.Editor.Razor.Test/Documents/EditorDocumentManagerBaseTest.cs @@ -21,13 +21,13 @@ namespace Microsoft.VisualStudio.Editor.Razor.Documents private TestEditorDocumentManager Manager { get; } - public string Project1 => "c:\\Project1"; + public string Project1 => TestProjectData.SomeProject.FilePath; - public string Project2 => "c:\\Project2"; + public string Project2 => TestProjectData.AnotherProject.FilePath; - public string File1 => "c:\\Project1\\File1.cshtml"; + public string File1 => TestProjectData.SomeProjectFile1.FilePath; - public string File2 => "c:\\Project2\\File2.cshtml"; + public string File2 => TestProjectData.AnotherProjectFile2.FilePath; public TestTextBuffer TextBuffer => new TestTextBuffer(new StringTextSnapshot("HI")); @@ -113,8 +113,8 @@ namespace Microsoft.VisualStudio.Editor.Razor.Documents // Assert Assert.Collection( documents.OrderBy(d => d.ProjectFilePath), - d => Assert.Same(document1, d), - d => Assert.Same(document2, d)); + d => Assert.Same(document2, d), + d => Assert.Same(document1, d)); } [ForegroundFact] @@ -147,8 +147,8 @@ namespace Microsoft.VisualStudio.Editor.Razor.Documents // Assert Assert.Collection( Manager.Opened.OrderBy(d => d.ProjectFilePath), - d => Assert.Same(document1, d), - d => Assert.Same(document2, d)); + d => Assert.Same(document2, d), + d => Assert.Same(document1, d)); } [ForegroundFact] @@ -165,8 +165,8 @@ namespace Microsoft.VisualStudio.Editor.Razor.Documents // Assert Assert.Collection( Manager.Closed.OrderBy(d => d.ProjectFilePath), - d => Assert.Same(document1, d), - d => Assert.Same(document2, d)); + d => Assert.Same(document2, d), + d => Assert.Same(document1, d)); } private class TestEditorDocumentManager : EditorDocumentManagerBase diff --git a/test/Microsoft.VisualStudio.Editor.Razor.Test/Documents/EditorDocumentManagerListenerTest.cs b/test/Microsoft.VisualStudio.Editor.Razor.Test/Documents/EditorDocumentManagerListenerTest.cs index 6d1eddacee..7a1e6122de 100644 --- a/test/Microsoft.VisualStudio.Editor.Razor.Test/Documents/EditorDocumentManagerListenerTest.cs +++ b/test/Microsoft.VisualStudio.Editor.Razor.Test/Documents/EditorDocumentManagerListenerTest.cs @@ -17,8 +17,8 @@ namespace Microsoft.VisualStudio.Editor.Razor.Documents { public EditorDocumentManagerListenerTest() { - ProjectFilePath = "C:\\project1\\project.csproj"; - DocumentFilePath = "c:\\project1\\file1.cshtml"; + ProjectFilePath = TestProjectData.SomeProject.FilePath; + DocumentFilePath = TestProjectData.SomeProjectFile1.FilePath; TextLoader = TextLoader.From(TextAndVersion.Create(SourceText.From("FILE"), VersionStamp.Default)); FileChangeTracker = new DefaultFileChangeTracker(DocumentFilePath); @@ -58,8 +58,10 @@ namespace Microsoft.VisualStudio.Editor.Razor.Documents var listener = new EditorDocumentManagerListener(editorDocumentManger.Object, changedOnDisk, changedInEditor, opened, closed); + var project = Mock.Of(p => p.FilePath == "/Path/to/project.csproj"); + // Act & Assert - listener.ProjectManager_Changed(null, new ProjectChangeEventArgs("/Path/to/project.csproj", ProjectChangeKind.DocumentAdded)); + listener.ProjectManager_Changed(null, new ProjectChangeEventArgs(project, project, ProjectChangeKind.DocumentAdded)); } [Fact] @@ -76,8 +78,10 @@ namespace Microsoft.VisualStudio.Editor.Razor.Documents var listener = new EditorDocumentManagerListener(editorDocumentManger.Object, onChangedOnDisk: null, onChangedInEditor: null, onOpened: opened, onClosed: null); + var project = Mock.Of(p => p.FilePath == "/Path/to/project.csproj"); + // Act - listener.ProjectManager_Changed(null, new ProjectChangeEventArgs("/Path/to/project.csproj", ProjectChangeKind.DocumentAdded)); + listener.ProjectManager_Changed(null, new ProjectChangeEventArgs(project, project, ProjectChangeKind.DocumentAdded)); // Assert Assert.True(called); diff --git a/test/Microsoft.VisualStudio.Editor.Razor.Test/Documents/EditorDocumentTest.cs b/test/Microsoft.VisualStudio.Editor.Razor.Test/Documents/EditorDocumentTest.cs index 7173be6d15..491e69dd60 100644 --- a/test/Microsoft.VisualStudio.Editor.Razor.Test/Documents/EditorDocumentTest.cs +++ b/test/Microsoft.VisualStudio.Editor.Razor.Test/Documents/EditorDocumentTest.cs @@ -3,6 +3,7 @@ using System; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Razor; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Test; using Microsoft.VisualStudio.Text; @@ -16,8 +17,8 @@ namespace Microsoft.VisualStudio.Editor.Razor.Documents public EditorDocumentTest() { DocumentManager = Mock.Of(); - ProjectFilePath = "C:\\project1\\project.csproj"; - DocumentFilePath = "c:\\project1\\file1.cshtml"; + ProjectFilePath = TestProjectData.SomeProject.FilePath; + DocumentFilePath = TestProjectData.SomeProjectFile1.FilePath; TextLoader = TextLoader.From(TextAndVersion.Create(SourceText.From("FILE"), VersionStamp.Default)); FileChangeTracker = new DefaultFileChangeTracker(DocumentFilePath); diff --git a/test/Microsoft.VisualStudio.Editor.Razor.Test/Shared/ForegroundDispatcherWorkspaceTestBase.cs b/test/Microsoft.VisualStudio.Editor.Razor.Test/Shared/ForegroundDispatcherWorkspaceTestBase.cs new file mode 100644 index 0000000000..495b0ff172 --- /dev/null +++ b/test/Microsoft.VisualStudio.Editor.Razor.Test/Shared/ForegroundDispatcherWorkspaceTestBase.cs @@ -0,0 +1,12 @@ +// 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.Razor; + +namespace Xunit +{ + public abstract class ForegroundDispatcherWorkspaceTestBase : WorkspaceTestBase + { + internal ForegroundDispatcher Dispatcher { get; } = new SingleThreadedForegroundDispatcher(); + } +} diff --git a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/DocumentGenerator/BackgroundDocumentGeneratorTest.cs b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/DocumentGenerator/BackgroundDocumentGeneratorTest.cs index fdc6bd3698..cf94a74358 100644 --- a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/DocumentGenerator/BackgroundDocumentGeneratorTest.cs +++ b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/DocumentGenerator/BackgroundDocumentGeneratorTest.cs @@ -5,6 +5,8 @@ using System; using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Razor.Language; +using Microsoft.CodeAnalysis.Host; using Moq; using Xunit; @@ -12,20 +14,18 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { // These tests are really integration tests. There isn't a good way to unit test this functionality since // the only thing in here is threading. - public class BackgroundDocumentGeneratorTest : ForegroundDispatcherTestBase + public class BackgroundDocumentGeneratorTest : ForegroundDispatcherWorkspaceTestBase { public BackgroundDocumentGeneratorTest() { Documents = new HostDocument[] { - new HostDocument("c:\\Test1\\Index.cshtml", "Index.cshtml"), - new HostDocument("c:\\Test1\\Components\\Counter.cshtml", "Components\\Counter.cshtml"), + TestProjectData.SomeProjectFile1, + TestProjectData.AnotherProjectFile1, }; - HostProject1 = new HostProject("c:\\Test1\\Test1.csproj", FallbackRazorConfiguration.MVC_1_0); - HostProject2 = new HostProject("c:\\Test2\\Test2.csproj", FallbackRazorConfiguration.MVC_1_0); - - Workspace = TestWorkspace.Create(); + HostProject1 = new HostProject(TestProjectData.SomeProject.FilePath, FallbackRazorConfiguration.MVC_1_0); + HostProject2 = new HostProject(TestProjectData.AnotherProject.FilePath, FallbackRazorConfiguration.MVC_1_0); var projectId1 = ProjectId.CreateNewId("Test1"); var projectId2 = ProjectId.CreateNewId("Test2"); @@ -37,14 +37,14 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem "Test1", "Test1", LanguageNames.CSharp, - "c:\\Test1\\Test1.csproj")) + TestProjectData.SomeProject.FilePath)) .AddProject(ProjectInfo.Create( projectId2, VersionStamp.Default, "Test2", "Test2", LanguageNames.CSharp, - "c:\\Test2\\Test2.csproj")); ; + TestProjectData.AnotherProject.FilePath)); ; WorkspaceProject1 = solution.GetProject(projectId1); WorkspaceProject2 = solution.GetProject(projectId2); @@ -60,7 +60,11 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem private Project WorkspaceProject2 { get; } - private Workspace Workspace { get; } + protected override void ConfigureProjectEngine(RazorProjectEngineBuilder builder) + { + builder.Features.Remove(builder.Features.OfType().Single()); + builder.Features.Add(new TestImportProjectFeature()); + } [ForegroundFact] public async Task Queue_ProcessesNotifications_AndGoesBackToSleep() diff --git a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Documents/VisualStudioFileChangeTrackerTest.cs b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Documents/VisualStudioFileChangeTrackerTest.cs index fcef648173..93e48b1f4f 100644 --- a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Documents/VisualStudioFileChangeTrackerTest.cs +++ b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Documents/VisualStudioFileChangeTrackerTest.cs @@ -23,7 +23,7 @@ namespace Microsoft.VisualStudio.Editor.Razor.Documents .Setup(f => f.AdviseFileChange(It.IsAny(), It.IsAny(), It.IsAny(), out cookie)) .Returns(VSConstants.S_OK) .Verifiable(); - var tracker = new VisualStudioFileChangeTracker("C:/_ViewImports.cshtml", Dispatcher, ErrorReporter, fileChangeService.Object); + var tracker = new VisualStudioFileChangeTracker(TestProjectData.SomeProjectImportFile.FilePath, Dispatcher, ErrorReporter, fileChangeService.Object); // Act tracker.StartListening(); @@ -43,7 +43,7 @@ namespace Microsoft.VisualStudio.Editor.Razor.Documents .Setup(f => f.AdviseFileChange(It.IsAny(), It.IsAny(), It.IsAny(), out cookie)) .Returns(VSConstants.S_OK) .Callback(() => callCount++); - var tracker = new VisualStudioFileChangeTracker("C:/_ViewImports.cshtml", Dispatcher, ErrorReporter, fileChangeService.Object); + var tracker = new VisualStudioFileChangeTracker(TestProjectData.SomeProjectImportFile.FilePath, Dispatcher, ErrorReporter, fileChangeService.Object); tracker.StartListening(); // Act @@ -67,7 +67,7 @@ namespace Microsoft.VisualStudio.Editor.Razor.Documents .Setup(f => f.UnadviseFileChange(cookie)) .Returns(VSConstants.S_OK) .Verifiable(); - var tracker = new VisualStudioFileChangeTracker("C:/_ViewImports.cshtml", Dispatcher, ErrorReporter, fileChangeService.Object); + var tracker = new VisualStudioFileChangeTracker(TestProjectData.SomeProjectImportFile.FilePath, Dispatcher, ErrorReporter, fileChangeService.Object); tracker.StartListening(); // Start listening for changes. // Act @@ -86,7 +86,7 @@ namespace Microsoft.VisualStudio.Editor.Razor.Documents fileChangeService .Setup(f => f.UnadviseFileChange(cookie)) .Throws(new InvalidOperationException()); - var tracker = new VisualStudioFileChangeTracker("C:/_ViewImports.cshtml", Dispatcher, ErrorReporter, fileChangeService.Object); + var tracker = new VisualStudioFileChangeTracker(TestProjectData.SomeProjectImportFile.FilePath, Dispatcher, ErrorReporter, fileChangeService.Object); // Act & Assert tracker.StopListening(); @@ -100,7 +100,7 @@ namespace Microsoft.VisualStudio.Editor.Razor.Documents public void FilesChanged_WithSpecificFlags_InvokesChangedHandler_WithExpectedArguments(uint fileChangeFlag, int expectedKind) { // Arrange - var filePath = "C:\\path\\to\\project\\_ViewImports.cshtml"; + var filePath = TestProjectData.SomeProjectImportFile.FilePath; uint cookie; var fileChangeService = new Mock(); fileChangeService diff --git a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Microsoft.VisualStudio.LanguageServices.Razor.Test.csproj b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Microsoft.VisualStudio.LanguageServices.Razor.Test.csproj index 1989b12093..02011f7fac 100644 --- a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Microsoft.VisualStudio.LanguageServices.Razor.Test.csproj +++ b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Microsoft.VisualStudio.LanguageServices.Razor.Test.csproj @@ -14,6 +14,9 @@ Shared\%(RecursiveDir)%(FileName)%(Extension) + + Shared\%(RecursiveDir)%(FileName)%(Extension) + diff --git a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/DefaultProjectSnapshotManagerTest.cs b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/DefaultProjectSnapshotManagerTest.cs index c69fc87f91..9915b2ca44 100644 --- a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/DefaultProjectSnapshotManagerTest.cs +++ b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/DefaultProjectSnapshotManagerTest.cs @@ -13,35 +13,24 @@ using Xunit; namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { - public class DefaultProjectSnapshotManagerTest : ForegroundDispatcherTestBase + public class DefaultProjectSnapshotManagerTest : ForegroundDispatcherWorkspaceTestBase { public DefaultProjectSnapshotManagerTest() { TagHelperResolver = new TestTagHelperResolver(); - HostServices = TestServices.Create( - new IWorkspaceService[] - { - new TestProjectSnapshotProjectEngineFactory(), - }, - new ILanguageService[] - { - TagHelperResolver, - }); - Documents = new HostDocument[] { - new HostDocument("c:\\MyProject\\File.cshtml", "File.cshtml"), - new HostDocument("c:\\MyProject\\Index.cshtml", "Index.cshtml"), + TestProjectData.SomeProjectFile1, + TestProjectData.SomeProjectFile2, // linked file - new HostDocument("c:\\SomeOtherProject\\Index.cshtml", "Pages\\Index.cshtml"), + TestProjectData.AnotherProjectNestedFile3, }; - HostProject = new HostProject("c:\\MyProject\\Test.csproj", FallbackRazorConfiguration.MVC_2_0); - HostProjectWithConfigurationChange = new HostProject("c:\\MyProject\\Test.csproj", FallbackRazorConfiguration.MVC_1_0); - - Workspace = TestWorkspace.Create(HostServices); + HostProject = new HostProject(TestProjectData.SomeProject.FilePath, FallbackRazorConfiguration.MVC_2_0); + HostProjectWithConfigurationChange = new HostProject(TestProjectData.SomeProject.FilePath, FallbackRazorConfiguration.MVC_1_0); + ProjectManager = new TestProjectSnapshotManager(Dispatcher, Enumerable.Empty(), Workspace); var projectId = ProjectId.CreateNewId("Test"); @@ -51,7 +40,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem "Test", "Test", LanguageNames.CSharp, - "c:\\MyProject\\Test.csproj")); + TestProjectData.SomeProject.FilePath)); WorkspaceProject = solution.GetProject(projectId); var vbProjectId = ProjectId.CreateNewId("VB"); @@ -81,7 +70,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem "Test (Different TFM)", "Test", LanguageNames.CSharp, - "c:\\MyProject\\Test.csproj")); + TestProjectData.SomeProject.FilePath)); WorkspaceProjectWithDifferentTfm = solution.GetProject(projectIdWithDifferentTfm); SomeTagHelpers = TagHelperResolver.TagHelpers; @@ -108,14 +97,15 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem private TestProjectSnapshotManager ProjectManager { get; } - private HostServices HostServices { get; } - - private Workspace Workspace { get; } - private SourceText SourceText { get; } private IList SomeTagHelpers { get; } + protected override void ConfigureLanguageServices(List services) + { + services.Add(TagHelperResolver); + } + [ForegroundFact] public void DocumentAdded_AddsDocument() { @@ -129,7 +119,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem // Assert var snapshot = ProjectManager.GetSnapshot(HostProject); - Assert.Collection(snapshot.DocumentFilePaths, d => Assert.Equal(Documents[0].FilePath, d)); + Assert.Collection(snapshot.DocumentFilePaths.OrderBy(f => f), d => Assert.Equal(Documents[0].FilePath, d)); Assert.Equal(ProjectChangeKind.DocumentAdded, ProjectManager.ListenersNotifiedOf); } @@ -148,7 +138,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem // Assert var snapshot = ProjectManager.GetSnapshot(HostProject); - Assert.Collection(snapshot.DocumentFilePaths, d => Assert.Equal(Documents[0].FilePath, d)); + Assert.Collection(snapshot.DocumentFilePaths.OrderBy(f => f), d => Assert.Equal(Documents[0].FilePath, d)); Assert.Null(ProjectManager.ListenersNotifiedOf); } @@ -262,9 +252,9 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem // Assert var snapshot = ProjectManager.GetSnapshot(HostProject); Assert.Collection( - snapshot.DocumentFilePaths, - d => Assert.Equal(Documents[0].FilePath, d), - d => Assert.Equal(Documents[2].FilePath, d)); + snapshot.DocumentFilePaths.OrderBy(f => f), + d => Assert.Equal(Documents[2].FilePath, d), + d => Assert.Equal(Documents[0].FilePath, d)); Assert.Equal(ProjectChangeKind.DocumentRemoved, ProjectManager.ListenersNotifiedOf); } diff --git a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/DefaultRazorProjectHostTest.cs b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/DefaultRazorProjectHostTest.cs index ce7de26d39..24e94872db 100644 --- a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/DefaultRazorProjectHostTest.cs +++ b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/DefaultRazorProjectHostTest.cs @@ -4,6 +4,7 @@ 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; @@ -15,11 +16,10 @@ using ItemCollection = Microsoft.VisualStudio.ProjectSystem.ItemCollection; namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { - public class DefaultRazorProjectHostTest : ForegroundDispatcherTestBase + public class DefaultRazorProjectHostTest : ForegroundDispatcherWorkspaceTestBase { public DefaultRazorProjectHostTest() { - Workspace = new AdhocWorkspace(); ProjectManager = new TestProjectSnapshotManager(Dispatcher, Workspace); ConfigurationItems = new ItemCollection(Rules.RazorConfiguration.SchemaName); @@ -38,8 +38,6 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem private TestProjectSnapshotManager ProjectManager { get; } - private Workspace Workspace { get; } - [Fact] public void TryGetDefaultConfiguration_FailsIfNoRule() { @@ -612,7 +610,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem public async Task DefaultRazorProjectHost_ForegroundThread_CreateAndDispose_Succeeds() { // Arrange - var services = new TestProjectSystemServices("c:\\MyProject\\Test.csproj"); + var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath); var host = new DefaultRazorProjectHost(services, Workspace, ProjectManager); // Act & Assert @@ -627,7 +625,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem public async Task DefaultRazorProjectHost_BackgroundThread_CreateAndDispose_Succeeds() { // Arrange - var services = new TestProjectSystemServices("c:\\MyProject\\Test.csproj"); + var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath); var host = new DefaultRazorProjectHost(services, Workspace, ProjectManager); // Act & Assert @@ -646,7 +644,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { }; - var services = new TestProjectSystemServices("Test.csproj"); + var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath); var host = new DefaultRazorProjectHost(services, Workspace, ProjectManager); // Act & Assert @@ -670,8 +668,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem ExtensionItems.Item("MVC-2.1"); ExtensionItems.Item("Another-Thing"); - DocumentItems.Item("File.cshtml"); - DocumentItems.Property("File.cshtml", Rules.RazorGenerateWithTargetPath.TargetPathProperty, "File.cshtml"); + DocumentItems.Item(Path.GetFileName(TestProjectData.SomeProjectFile1.FilePath)); + DocumentItems.Property(Path.GetFileName(TestProjectData.SomeProjectFile1.FilePath), Rules.RazorGenerateWithTargetPath.TargetPathProperty, TestProjectData.SomeProjectFile1.TargetPath); var changes = new TestProjectChangeDescription[] { @@ -681,7 +679,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem DocumentItems.ToChange(), }; - var services = new TestProjectSystemServices("c:\\MyProject\\Test.csproj"); + var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath); var host = new DefaultRazorProjectHost(services, Workspace, ProjectManager); @@ -693,7 +691,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem // Assert var snapshot = Assert.Single(ProjectManager.Projects); - Assert.Equal("c:\\MyProject\\Test.csproj", snapshot.FilePath); + Assert.Equal(TestProjectData.SomeProject.FilePath, snapshot.FilePath); Assert.Equal(RazorLanguageVersion.Version_2_1, snapshot.Configuration.LanguageVersion); Assert.Equal("MVC-2.1", snapshot.Configuration.ConfigurationName); @@ -707,8 +705,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem d => { var document = snapshot.GetDocument(d); - Assert.Equal("c:\\MyProject\\File.cshtml", document.FilePath); - Assert.Equal("File.cshtml", document.TargetPath); + Assert.Equal(TestProjectData.SomeProjectFile1.FilePath, document.FilePath); + Assert.Equal(TestProjectData.SomeProjectFile1.TargetPath, document.TargetPath); }); await Task.Run(async () => await host.DisposeAsync()); @@ -726,7 +724,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem ExtensionItems.Item("TestExtension"); - DocumentItems.Item("File.cshtml"); + DocumentItems.Item(Path.GetFileName(TestProjectData.SomeProjectFile1.FilePath)); var changes = new TestProjectChangeDescription[] { @@ -736,7 +734,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem DocumentItems.ToChange(), }; - var services = new TestProjectSystemServices("c:\\MyProject\\Test.csproj"); + var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath); var host = new DefaultRazorProjectHost(services, Workspace, ProjectManager); @@ -766,8 +764,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem ExtensionItems.Item("MVC-2.1"); ExtensionItems.Item("Another-Thing"); - DocumentItems.Item("File.cshtml"); - DocumentItems.Property("File.cshtml", Rules.RazorGenerateWithTargetPath.TargetPathProperty, "File.cshtml"); + DocumentItems.Item(Path.GetFileName(TestProjectData.SomeProjectFile1.FilePath)); + DocumentItems.Property(Path.GetFileName(TestProjectData.SomeProjectFile1.FilePath), Rules.RazorGenerateWithTargetPath.TargetPathProperty, TestProjectData.SomeProjectFile1.TargetPath); var changes = new TestProjectChangeDescription[] { @@ -777,7 +775,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem DocumentItems.ToChange(), }; - var services = new TestProjectSystemServices("c:\\MyProject\\Test.csproj"); + var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath); var host = new DefaultRazorProjectHost(services, Workspace, ProjectManager); @@ -789,7 +787,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem // Assert - 1 var snapshot = Assert.Single(ProjectManager.Projects); - Assert.Equal("c:\\MyProject\\Test.csproj", snapshot.FilePath); + Assert.Equal(TestProjectData.SomeProject.FilePath, snapshot.FilePath); Assert.Equal(RazorLanguageVersion.Version_2_1, snapshot.Configuration.LanguageVersion); Assert.Equal("MVC-2.1", snapshot.Configuration.ConfigurationName); @@ -803,8 +801,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem d => { var document = snapshot.GetDocument(d); - Assert.Equal("c:\\MyProject\\File.cshtml", document.FilePath); - Assert.Equal("File.cshtml", document.TargetPath); + Assert.Equal(TestProjectData.SomeProjectFile1.FilePath, document.FilePath); + Assert.Equal(TestProjectData.SomeProjectFile1.TargetPath, document.TargetPath); }); // Act - 2 @@ -813,9 +811,9 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem ConfigurationItems.RemoveItem("MVC-2.1"); ConfigurationItems.Item("MVC-2.0", new Dictionary() { { "Extensions", "MVC-2.0;Another-Thing" }, }); ExtensionItems.Item("MVC-2.0"); - DocumentItems.Item("c:\\AnotherProject\\AnotherFile.cshtml", new Dictionary() + DocumentItems.Item(TestProjectData.AnotherProjectNestedFile3.FilePath, new Dictionary() { - { Rules.RazorGenerateWithTargetPath.TargetPathProperty, "Pages\\AnotherFile.cshtml" }, + { Rules.RazorGenerateWithTargetPath.TargetPathProperty, TestProjectData.AnotherProjectNestedFile3.TargetPath }, }); changes = new TestProjectChangeDescription[] @@ -830,7 +828,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem // Assert - 2 snapshot = Assert.Single(ProjectManager.Projects); - Assert.Equal("c:\\MyProject\\Test.csproj", snapshot.FilePath); + Assert.Equal(TestProjectData.SomeProject.FilePath, snapshot.FilePath); Assert.Equal(RazorLanguageVersion.Version_2_0, snapshot.Configuration.LanguageVersion); Assert.Equal("MVC-2.0", snapshot.Configuration.ConfigurationName); @@ -844,14 +842,14 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem d => { var document = snapshot.GetDocument(d); - Assert.Equal("c:\\AnotherProject\\AnotherFile.cshtml", document.FilePath); - Assert.Equal("Pages\\AnotherFile.cshtml", document.TargetPath); + Assert.Equal(TestProjectData.AnotherProjectNestedFile3.FilePath, document.FilePath); + Assert.Equal(TestProjectData.AnotherProjectNestedFile3.TargetPath, document.TargetPath); }, d => { var document = snapshot.GetDocument(d); - Assert.Equal("c:\\MyProject\\File.cshtml", document.FilePath); - Assert.Equal("File.cshtml", document.TargetPath); + Assert.Equal(TestProjectData.SomeProjectFile1.FilePath, document.FilePath); + Assert.Equal(TestProjectData.SomeProjectFile1.TargetPath, document.TargetPath); }); await Task.Run(async () => await host.DisposeAsync()); @@ -871,8 +869,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem ExtensionItems.Item("MVC-2.1"); ExtensionItems.Item("Another-Thing"); - DocumentItems.Item("File.cshtml"); - DocumentItems.Property("File.cshtml", Rules.RazorGenerateWithTargetPath.TargetPathProperty, "File.cshtml"); + DocumentItems.Item(Path.GetFileName(TestProjectData.SomeProjectFile1.FilePath)); + DocumentItems.Property(Path.GetFileName(TestProjectData.SomeProjectFile1.FilePath), Rules.RazorGenerateWithTargetPath.TargetPathProperty, TestProjectData.SomeProjectFile1.TargetPath); var changes = new TestProjectChangeDescription[] { @@ -882,7 +880,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem DocumentItems.ToChange(), }; - var services = new TestProjectSystemServices("c:\\MyProject\\Test.csproj"); + var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath); var host = new DefaultRazorProjectHost(services, Workspace, ProjectManager); @@ -894,7 +892,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem // Assert - 1 var snapshot = Assert.Single(ProjectManager.Projects); - Assert.Equal("c:\\MyProject\\Test.csproj", snapshot.FilePath); + Assert.Equal(TestProjectData.SomeProject.FilePath, snapshot.FilePath); Assert.Equal(RazorLanguageVersion.Version_2_1, snapshot.Configuration.LanguageVersion); Assert.Equal("MVC-2.1", snapshot.Configuration.ConfigurationName); @@ -937,8 +935,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem ExtensionItems.Item("MVC-2.1"); ExtensionItems.Item("Another-Thing"); - DocumentItems.Item("File.cshtml"); - DocumentItems.Property("File.cshtml", Rules.RazorGenerateWithTargetPath.TargetPathProperty, "File.cshtml"); + DocumentItems.Item(Path.GetFileName(TestProjectData.SomeProjectFile1.FilePath)); + DocumentItems.Property(Path.GetFileName(TestProjectData.SomeProjectFile1.FilePath), Rules.RazorGenerateWithTargetPath.TargetPathProperty, TestProjectData.SomeProjectFile1.TargetPath); var changes = new TestProjectChangeDescription[] { @@ -948,7 +946,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem DocumentItems.ToChange(), }; - var services = new TestProjectSystemServices("c:\\MyProject\\Test.csproj"); + var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath); var host = new DefaultRazorProjectHost(services, Workspace, ProjectManager); @@ -960,7 +958,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem // Assert - 1 var snapshot = Assert.Single(ProjectManager.Projects); - Assert.Equal("c:\\MyProject\\Test.csproj", snapshot.FilePath); + Assert.Equal(TestProjectData.SomeProject.FilePath, snapshot.FilePath); Assert.Equal(RazorLanguageVersion.Version_2_1, snapshot.Configuration.LanguageVersion); Assert.Equal("MVC-2.1", snapshot.Configuration.ConfigurationName); @@ -1007,8 +1005,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem ExtensionItems.Item("MVC-2.1"); ExtensionItems.Item("Another-Thing"); - DocumentItems.Item("File.cshtml"); - DocumentItems.Property("File.cshtml", Rules.RazorGenerateWithTargetPath.TargetPathProperty, "File.cshtml"); + DocumentItems.Item(Path.GetFileName(TestProjectData.SomeProjectFile1.FilePath)); + DocumentItems.Property(Path.GetFileName(TestProjectData.SomeProjectFile1.FilePath), Rules.RazorGenerateWithTargetPath.TargetPathProperty, TestProjectData.SomeProjectFile1.TargetPath); var changes = new TestProjectChangeDescription[] { @@ -1018,7 +1016,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem DocumentItems.ToChange(), }; - var services = new TestProjectSystemServices("c:\\MyProject\\Test.csproj"); + var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath); var host = new DefaultRazorProjectHost(services, Workspace, ProjectManager); @@ -1030,16 +1028,16 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem // Assert - 1 var snapshot = Assert.Single(ProjectManager.Projects); - Assert.Equal("c:\\MyProject\\Test.csproj", snapshot.FilePath); + Assert.Equal(TestProjectData.SomeProject.FilePath, snapshot.FilePath); Assert.Same("MVC-2.1", snapshot.Configuration.ConfigurationName); // Act - 2 - services.UnconfiguredProject.FullPath = "c:\\AnotherProject\\Test2.csproj"; + services.UnconfiguredProject.FullPath = TestProjectData.AnotherProject.FilePath; await Task.Run(async () => await host.OnProjectRenamingAsync()); // Assert - 1 snapshot = Assert.Single(ProjectManager.Projects); - Assert.Equal("c:\\AnotherProject\\Test2.csproj", snapshot.FilePath); + Assert.Equal(TestProjectData.AnotherProject.FilePath, snapshot.FilePath); Assert.Same("MVC-2.1", snapshot.Configuration.ConfigurationName); await Task.Run(async () => await host.DisposeAsync()); diff --git a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/FallbackRazorProjectHostTest.cs b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/FallbackRazorProjectHostTest.cs index 8cb7174d1b..22d9951f8f 100644 --- a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/FallbackRazorProjectHostTest.cs +++ b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/FallbackRazorProjectHostTest.cs @@ -12,11 +12,10 @@ using ItemReference = Microsoft.CodeAnalysis.Razor.ProjectSystem.ManagedProjectS namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { - public class FallbackRazorProjectHostTest : ForegroundDispatcherTestBase + public class FallbackRazorProjectHostTest : ForegroundDispatcherWorkspaceTestBase { public FallbackRazorProjectHostTest() { - Workspace = new AdhocWorkspace(); ProjectManager = new TestProjectSnapshotManager(Dispatcher, Workspace); ReferenceItems = new ItemCollection(ManagedProjectSystemSchema.ResolvedCompilationReference.SchemaName); @@ -32,8 +31,6 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem private ItemCollection NoneItems { get; } - private Workspace Workspace { get; } - [Fact] public void GetChangedAndRemovedDocuments_ReturnsChangedContentAndNoneItems() { diff --git a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/WorkspaceProjectSnapshotChangeTriggerTest.cs b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/WorkspaceProjectSnapshotChangeTriggerTest.cs index f77a283b33..f5931269d2 100644 --- a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/WorkspaceProjectSnapshotChangeTriggerTest.cs +++ b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/WorkspaceProjectSnapshotChangeTriggerTest.cs @@ -9,11 +9,10 @@ using Xunit; namespace Microsoft.CodeAnalysis.Razor.ProjectSystem { - public class WorkspaceProjectSnapshotChangeTriggerTest : ForegroundDispatcherTestBase + public class WorkspaceProjectSnapshotChangeTriggerTest : ForegroundDispatcherWorkspaceTestBase { public WorkspaceProjectSnapshotChangeTriggerTest() { - Workspace = TestWorkspace.Create(); EmptySolution = Workspace.CurrentSolution.GetIsolatedSolution(); var projectId1 = ProjectId.CreateNewId("One"); @@ -72,8 +71,6 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem private Project ProjectNumberThree { get; } - private Workspace Workspace { get; } - [ForegroundTheory] [InlineData(WorkspaceChangeKind.SolutionAdded)] [InlineData(WorkspaceChangeKind.SolutionChanged)] diff --git a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/VsSolutionUpdatesProjectSnapshotChangeTriggerTest.cs b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/VsSolutionUpdatesProjectSnapshotChangeTriggerTest.cs index c5ce077844..d180992d83 100644 --- a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/VsSolutionUpdatesProjectSnapshotChangeTriggerTest.cs +++ b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/VsSolutionUpdatesProjectSnapshotChangeTriggerTest.cs @@ -2,8 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using Microsoft.AspNetCore.Razor.Language; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Razor; using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Microsoft.VisualStudio.Editor.Razor; using Microsoft.VisualStudio.Shell.Interop; @@ -16,8 +16,8 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor { public VsSolutionUpdatesProjectSnapshotChangeTriggerTest() { - SomeProject = new HostProject("c:\\SomeProject\\SomeProject.csproj", FallbackRazorConfiguration.MVC_1_0); - SomeOtherProject = new HostProject("c:\\SomeOtherProject\\SomeOtherProject.csproj", FallbackRazorConfiguration.MVC_2_0); + SomeProject = new HostProject(TestProjectData.SomeProject.FilePath, FallbackRazorConfiguration.MVC_1_0); + SomeOtherProject = new HostProject(TestProjectData.AnotherProject.FilePath, FallbackRazorConfiguration.MVC_2_0); Workspace = TestWorkspace.Create(w => { diff --git a/test/Microsoft.VisualStudio.Mac.LanguageServices.Razor.Test/Microsoft.VisualStudio.Mac.LanguageServices.Razor.Test.csproj b/test/Microsoft.VisualStudio.Mac.LanguageServices.Razor.Test/Microsoft.VisualStudio.Mac.LanguageServices.Razor.Test.csproj index a413433e4a..d0f3a281df 100644 --- a/test/Microsoft.VisualStudio.Mac.LanguageServices.Razor.Test/Microsoft.VisualStudio.Mac.LanguageServices.Razor.Test.csproj +++ b/test/Microsoft.VisualStudio.Mac.LanguageServices.Razor.Test/Microsoft.VisualStudio.Mac.LanguageServices.Razor.Test.csproj @@ -8,6 +8,9 @@ Shared\%(RecursiveDir)%(FileName)%(Extension) + + Shared\%(RecursiveDir)%(FileName)%(Extension) +