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.
This commit is contained in:
Ryan Nowak 2018-10-19 21:39:43 -07:00 committed by GitHub
parent 333989af7a
commit baa71375d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 1347 additions and 434 deletions

View File

@ -132,14 +132,6 @@ namespace Microsoft.AspNetCore.Razor.Performance
private class StaticProjectSnapshotProjectEngineFactory : ProjectSnapshotProjectEngineFactory private class StaticProjectSnapshotProjectEngineFactory : ProjectSnapshotProjectEngineFactory
{ {
public override RazorProjectEngine Create(ProjectSnapshot project, RazorProjectFileSystem fileSystem, Action<RazorProjectEngineBuilder> configure)
{
return RazorProjectEngine.Create(project.Configuration, fileSystem, b =>
{
RazorExtensions.Register(b);
});
}
public override IProjectEngineFactory FindFactory(ProjectSnapshot project) public override IProjectEngineFactory FindFactory(ProjectSnapshot project)
{ {
throw new NotImplementedException(); throw new NotImplementedException();
@ -149,6 +141,14 @@ namespace Microsoft.AspNetCore.Razor.Performance
{ {
throw new NotImplementedException(); throw new NotImplementedException();
} }
public override RazorProjectEngine Create(RazorConfiguration configuration, RazorProjectFileSystem fileSystem, Action<RazorProjectEngineBuilder> configure)
{
return RazorProjectEngine.Create(configuration, fileSystem, b =>
{
RazorExtensions.Register(b);
});
}
} }
} }
} }

View File

@ -49,6 +49,10 @@ namespace Microsoft.AspNetCore.Razor.Language
var absolutePath = NormalizeAndEnsureValidPath(path); var absolutePath = NormalizeAndEnsureValidPath(path);
var file = new FileInfo(absolutePath); 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 relativePhysicalPath = file.FullName.Substring(absoluteBasePath.Length + 1); // Include leading separator
var filePath = "/" + relativePhysicalPath.Replace(Path.DirectorySeparatorChar, '/'); var filePath = "/" + relativePhysicalPath.Replace(Path.DirectorySeparatorChar, '/');

View File

@ -32,13 +32,8 @@ namespace Microsoft.CodeAnalysis.Razor
_factories = factories; _factories = factories;
} }
public override RazorProjectEngine Create(ProjectSnapshot project, RazorProjectFileSystem fileSystem, Action<RazorProjectEngineBuilder> configure) public override RazorProjectEngine Create(RazorConfiguration configuration, RazorProjectFileSystem fileSystem, Action<RazorProjectEngineBuilder> configure)
{ {
if (project == null)
{
throw new ArgumentNullException(nameof(project));
}
if (fileSystem == null) if (fileSystem == null)
{ {
throw new ArgumentNullException(nameof(fileSystem)); 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 // 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. // 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. // If there's no factory to handle the configuration then fall back to a very basic configuration.
// //

View File

@ -45,7 +45,36 @@ namespace Microsoft.CodeAnalysis.Razor
return Create(project, RazorProjectFileSystem.Create(Path.GetDirectoryName(project.FilePath)), configure); return Create(project, RazorProjectFileSystem.Create(Path.GetDirectoryName(project.FilePath)), configure);
} }
public abstract RazorProjectEngine Create(ProjectSnapshot project, RazorProjectFileSystem fileSystem, Action<RazorProjectEngineBuilder> configure); public RazorProjectEngine Create(ProjectSnapshot project, RazorProjectFileSystem fileSystem, Action<RazorProjectEngineBuilder> 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<RazorProjectEngineBuilder> 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<RazorProjectEngineBuilder> configure);
} }
} }

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language; 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<DocumentSnapshot> 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<DocumentSnapshot>();
}
public override RazorProjectEngine GetProjectEngine() public override RazorProjectEngine GetProjectEngine()
{ {
return State.ProjectEngine.GetProjectEngine(this); return State.ProjectEngine.GetProjectEngine(this.State);
} }
public override Task<IReadOnlyList<TagHelperDescriptor>> GetTagHelpersAsync() public override Task<IReadOnlyList<TagHelperDescriptor>> GetTagHelpersAsync()

View File

@ -90,12 +90,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
var projects = new ProjectSnapshot[_projects.Count]; var projects = new ProjectSnapshot[_projects.Count];
foreach (var entry in _projects) foreach (var entry in _projects)
{ {
if (entry.Value.Snapshot == null) projects[i++] = entry.Value.GetSnapshot();
{
entry.Value.Snapshot = new DefaultProjectSnapshot(entry.Value.State);
}
projects[i++] = entry.Value.Snapshot;
} }
return projects; return projects;
@ -115,12 +110,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
if (_projects.TryGetValue(filePath, out var entry)) if (_projects.TryGetValue(filePath, out var entry))
{ {
if (entry.Snapshot == null) return entry.GetSnapshot();
{
entry.Snapshot = new DefaultProjectSnapshot(entry.State);
}
return entry.Snapshot;
} }
return null; return null;
@ -175,8 +165,10 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Document updates can no-op. // Document updates can no-op.
if (!object.ReferenceEquals(state, entry.State)) if (!object.ReferenceEquals(state, entry.State))
{ {
_projects[hostProject.FilePath] = new Entry(state); var oldSnapshot = entry.GetSnapshot();
NotifyListeners(new ProjectChangeEventArgs(hostProject.FilePath, document.FilePath, ProjectChangeKind.DocumentAdded)); 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. // Document updates can no-op.
if (!object.ReferenceEquals(state, entry.State)) if (!object.ReferenceEquals(state, entry.State))
{ {
_projects[hostProject.FilePath] = new Entry(state); var oldSnapshot = entry.GetSnapshot();
NotifyListeners(new ProjectChangeEventArgs(hostProject.FilePath, document.FilePath, ProjectChangeKind.DocumentRemoved)); 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)) entry.State.Documents.TryGetValue(documentFilePath, out var older))
{ {
ProjectState state; ProjectState state;
SourceText olderText;
VersionStamp olderVersion;
var currentText = sourceText; var currentText = sourceText;
if (older.TryGetText(out olderText) && if (older.TryGetText(out var olderText) &&
older.TryGetTextVersion(out olderVersion)) older.TryGetTextVersion(out var olderVersion))
{ {
var version = currentText.ContentEquals(olderText) ? olderVersion : olderVersion.GetNewerVersion(); var version = currentText.ContentEquals(olderText) ? olderVersion : olderVersion.GetNewerVersion();
state = entry.State.WithChangedHostDocument(older.HostDocument, currentText, version); state = entry.State.WithChangedHostDocument(older.HostDocument, currentText, version);
@ -256,8 +248,10 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Document updates can no-op. // Document updates can no-op.
if (!object.ReferenceEquals(state, entry.State)) if (!object.ReferenceEquals(state, entry.State))
{ {
_projects[projectFilePath] = new Entry(state); var oldSnapshot = entry.GetSnapshot();
NotifyListeners(new ProjectChangeEventArgs(projectFilePath, documentFilePath, ProjectChangeKind.DocumentChanged)); 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. // Document updates can no-op.
if (!object.ReferenceEquals(state, entry.State)) if (!object.ReferenceEquals(state, entry.State))
{ {
_projects[projectFilePath] = new Entry(state); var oldSnapshot = entry.GetSnapshot();
NotifyListeners(new ProjectChangeEventArgs(projectFilePath, documentFilePath, ProjectChangeKind.DocumentChanged)); 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)) entry.State.Documents.TryGetValue(documentFilePath, out var older))
{ {
ProjectState state; ProjectState state;
SourceText olderText;
VersionStamp olderVersion;
var currentText = sourceText; var currentText = sourceText;
if (older.TryGetText(out olderText) && if (older.TryGetText(out var olderText) &&
older.TryGetTextVersion(out olderVersion)) older.TryGetTextVersion(out var olderVersion))
{ {
var version = currentText.ContentEquals(olderText) ? olderVersion : olderVersion.GetNewerVersion(); var version = currentText.ContentEquals(olderText) ? olderVersion : olderVersion.GetNewerVersion();
state = entry.State.WithChangedHostDocument(older.HostDocument, currentText, version); state = entry.State.WithChangedHostDocument(older.HostDocument, currentText, version);
@ -346,8 +340,10 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Document updates can no-op. // Document updates can no-op.
if (!object.ReferenceEquals(state, entry.State)) if (!object.ReferenceEquals(state, entry.State))
{ {
_projects[projectFilePath] = new Entry(state); var oldSnapshot = entry.GetSnapshot();
NotifyListeners(new ProjectChangeEventArgs(projectFilePath, documentFilePath, ProjectChangeKind.DocumentChanged)); 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. // Document updates can no-op.
if (!object.ReferenceEquals(state, entry.State)) if (!object.ReferenceEquals(state, entry.State))
{ {
_projects[projectFilePath] = new Entry(state); var oldSnapshot = entry.GetSnapshot();
NotifyListeners(new ProjectChangeEventArgs(projectFilePath, documentFilePath, ProjectChangeKind.DocumentChanged)); 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 workspaceProject = GetWorkspaceProject(hostProject.FilePath);
var state = ProjectState.Create(Workspace.Services, hostProject, workspaceProject); 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. // 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) public override void HostProjectChanged(HostProject hostProject)
@ -429,9 +428,10 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// HostProject updates can no-op. // HostProject updates can no-op.
if (!object.ReferenceEquals(state, entry.State)) if (!object.ReferenceEquals(state, entry.State))
{ {
_projects[hostProject.FilePath] = new Entry(state); var oldSnapshot = entry.GetSnapshot();
entry = new Entry(state);
NotifyListeners(new ProjectChangeEventArgs(hostProject.FilePath, ProjectChangeKind.ProjectChanged)); _projects[hostProject.FilePath] = entry;
NotifyListeners(new ProjectChangeEventArgs(oldSnapshot, entry.GetSnapshot(), ProjectChangeKind.ProjectChanged));
} }
} }
} }
@ -445,12 +445,12 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
_foregroundDispatcher.AssertForegroundThread(); _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. // 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) if (entry.State.WorkspaceProject == null)
{ {
var state = entry.State.WithWorkspaceProject(workspaceProject); 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));
} }
} }
} }
@ -510,9 +512,10 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// already seen the update. // already seen the update.
if (!object.ReferenceEquals(state, entry.State)) if (!object.ReferenceEquals(state, entry.State))
{ {
_projects[workspaceProject.FilePath] = new Entry(state); var oldSnapshot = entry.GetSnapshot();
entry = new Entry(state);
NotifyListeners(new ProjectChangeEventArgs(workspaceProject.FilePath, ProjectChangeKind.ProjectChanged)); _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. // OK there's another WorkspaceProject, use that.
state = entry.State.WithWorkspaceProject(otherWorkspaceProject); 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 else
{ {
state = entry.State.WithWorkspaceProject(null);
_projects[workspaceProject.FilePath] = new Entry(state);
// Notify listeners of a change because we've removed computed 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));
} }
} }
} }
@ -644,13 +651,18 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
private class Entry private class Entry
{ {
public ProjectSnapshot Snapshot; public ProjectSnapshot SnapshotUnsafe;
public readonly ProjectState State; public readonly ProjectState State;
public Entry(ProjectState state) public Entry(ProjectState state)
{ {
State = state; State = state;
} }
public ProjectSnapshot GetSnapshot()
{
return SnapshotUnsafe ?? (SnapshotUnsafe = new DefaultProjectSnapshot(State));
}
} }
} }
} }

View File

@ -183,6 +183,18 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
return state; 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() public virtual DocumentState WithWorkspaceProjectChange()
{ {
var state = new DocumentState(Services, HostDocument, _sourceText, _version, _loader); var state = new DocumentState(Services, HostDocument, _sourceText, _version, _loader);

View File

@ -56,6 +56,26 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
return null; return null;
} }
public override bool IsImportDocument(DocumentSnapshot document)
{
if (document == null)
{
throw new ArgumentNullException(nameof(document));
}
return false;
}
public override IEnumerable<DocumentSnapshot> GetRelatedDocuments(DocumentSnapshot document)
{
if (document == null)
{
throw new ArgumentNullException(nameof(document));
}
return Array.Empty<DocumentSnapshot>();
}
public override RazorProjectEngine GetProjectEngine() public override RazorProjectEngine GetProjectEngine()
{ {
return _projectEngine.Value; return _projectEngine.Value;

View File

@ -27,6 +27,10 @@ namespace Microsoft.CodeAnalysis.Razor.Workspaces.ProjectSystem
public override DocumentSnapshot GetDocument(string filePath) => throw new NotImplementedException(); public override DocumentSnapshot GetDocument(string filePath) => throw new NotImplementedException();
public override bool IsImportDocument(DocumentSnapshot document) => throw new NotImplementedException();
public override IEnumerable<DocumentSnapshot> GetRelatedDocuments(DocumentSnapshot document) => throw new NotImplementedException();
public override RazorProjectEngine GetProjectEngine() => throw new NotImplementedException(); public override RazorProjectEngine GetProjectEngine() => throw new NotImplementedException();
public override Task<IReadOnlyList<TagHelperDescriptor>> GetTagHelpersAsync() => throw new NotImplementedException(); public override Task<IReadOnlyList<TagHelperDescriptor>> GetTagHelpersAsync() => throw new NotImplementedException();

View File

@ -7,29 +7,39 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{ {
internal class ProjectChangeEventArgs : EventArgs 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; 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; DocumentFilePath = documentFilePath;
Kind = kind; Kind = kind;
ProjectFilePath = older?.FilePath ?? newer.FilePath;
} }
public ProjectSnapshot Older { get; }
public ProjectSnapshot Newer { get; }
public string ProjectFilePath { get; } public string ProjectFilePath { get; }
public string DocumentFilePath { get; } public string DocumentFilePath { get; }

View File

@ -2,6 +2,10 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host;
@ -41,11 +45,11 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
return this; 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) if (_projectEngine == null)
@ -55,12 +59,30 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
if (_projectEngine == null) if (_projectEngine == null)
{ {
var factory = _services.GetRequiredService<ProjectSnapshotProjectEngineFactory>(); var factory = _services.GetRequiredService<ProjectSnapshotProjectEngineFactory>();
_projectEngine = factory.Create(snapshot); _projectEngine = factory.Create(state.HostProject.Configuration, Path.GetDirectoryName(state.HostProject.FilePath), configure: null);
} }
} }
} }
return _projectEngine; return _projectEngine;
} }
public List<string> GetImportDocumentTargetPaths(ProjectState state, string targetPath)
{
var projectEngine = GetProjectEngine(state);
var importFeature = projectEngine.ProjectFeatures.OfType<IImportProjectFeature>().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<string>();
foreach (var importItem in importItems)
{
var itemTargetPath = importItem.FilePath.Replace('/', '\\').TrimStart('\\');
targetPaths.Add(itemTargetPath);
}
return targetPaths;
}
} }
} }

View File

@ -25,6 +25,17 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
public abstract DocumentSnapshot GetDocument(string filePath); public abstract DocumentSnapshot GetDocument(string filePath);
public abstract bool IsImportDocument(DocumentSnapshot document);
/// <summary>
/// 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.
/// </summary>
/// <param name="document">The document.</param>
/// <returns>A list of related documents.</returns>
public abstract IEnumerable<DocumentSnapshot> GetRelatedDocuments(DocumentSnapshot document);
public abstract Task<IReadOnlyList<TagHelperDescriptor>> GetTagHelpersAsync(); public abstract Task<IReadOnlyList<TagHelperDescriptor>> GetTagHelpersAsync();
public abstract bool TryGetTagHelpers(out IReadOnlyList<TagHelperDescriptor> result); public abstract bool TryGetTagHelpers(out IReadOnlyList<TagHelperDescriptor> result);

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text;
@ -12,8 +13,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Internal tracker for DefaultProjectSnapshot // Internal tracker for DefaultProjectSnapshot
internal class ProjectState internal class ProjectState
{ {
private static readonly IReadOnlyDictionary<string, DocumentState> EmptyDocuments = new Dictionary<string, DocumentState>(); private static readonly ImmutableDictionary<string, DocumentState> EmptyDocuments = ImmutableDictionary.Create<string, DocumentState>(FilePathComparer.Instance);
private static readonly ImmutableDictionary<string, ImmutableArray<string>> EmptyImportsToRelatedDocuments = ImmutableDictionary.Create<string, ImmutableArray<string>>(FilePathComparer.Instance);
private readonly object _lock; private readonly object _lock;
private ProjectEngineTracker _projectEngine; private ProjectEngineTracker _projectEngine;
@ -43,6 +44,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
HostProject = hostProject; HostProject = hostProject;
WorkspaceProject = workspaceProject; WorkspaceProject = workspaceProject;
Documents = EmptyDocuments; Documents = EmptyDocuments;
ImportsToRelatedDocuments = EmptyImportsToRelatedDocuments;
Version = VersionStamp.Create(); Version = VersionStamp.Create();
_lock = new object(); _lock = new object();
@ -53,7 +55,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
ProjectDifference difference, ProjectDifference difference,
HostProject hostProject, HostProject hostProject,
Project workspaceProject, Project workspaceProject,
IReadOnlyDictionary<string, DocumentState> documents) ImmutableDictionary<string, DocumentState> documents,
ImmutableDictionary<string, ImmutableArray<string>> importsToRelatedDocuments)
{ {
if (older == null) if (older == null)
{ {
@ -70,12 +73,18 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
throw new ArgumentNullException(nameof(documents)); throw new ArgumentNullException(nameof(documents));
} }
if (importsToRelatedDocuments == null)
{
throw new ArgumentNullException(nameof(importsToRelatedDocuments));
}
Services = older.Services; Services = older.Services;
Version = older.Version.GetNewerVersion(); Version = older.Version.GetNewerVersion();
HostProject = hostProject; HostProject = hostProject;
WorkspaceProject = workspaceProject; WorkspaceProject = workspaceProject;
Documents = documents; Documents = documents;
ImportsToRelatedDocuments = importsToRelatedDocuments;
_lock = new object(); _lock = new object();
@ -84,7 +93,10 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
} }
// Internal set for testing. // Internal set for testing.
public IReadOnlyDictionary<string, DocumentState> Documents { get; internal set; } public ImmutableDictionary<string, DocumentState> Documents { get; internal set; }
// Internal set for testing.
public ImmutableDictionary<string, ImmutableArray<string>> ImportsToRelatedDocuments { get; internal set; }
public HostProject HostProject { get; } public HostProject HostProject { get; }
@ -153,16 +165,23 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
return this; return this;
} }
var documents = new Dictionary<string, DocumentState>(FilePathComparer.Instance); var documents = Documents.Add(hostDocument.FilePath, DocumentState.Create(Services, hostDocument, loader));
foreach (var kvp in 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))
{ {
documents.Add(kvp.Key, kvp.Value); foreach (var relatedDocument in relatedDocuments)
{
documents = documents.SetItem(relatedDocument, documents[relatedDocument].WithImportsChange());
}
} }
documents.Add(hostDocument.FilePath, DocumentState.Create(Services, hostDocument, loader)); var state = new ProjectState(this, ProjectDifference.DocumentAdded, HostProject, WorkspaceProject, documents, importsToRelatedDocuments);
var difference = ProjectDifference.DocumentAdded;
var state = new ProjectState(this, difference, HostProject, WorkspaceProject, documents);
return state; return state;
} }
@ -178,16 +197,23 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
return this; return this;
} }
var documents = new Dictionary<string, DocumentState>(FilePathComparer.Instance); var documents = Documents.Remove(hostDocument.FilePath);
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, ProjectDifference.DocumentRemoved, HostProject, WorkspaceProject, documents, importsToRelatedDocuments);
var state = new ProjectState(this, difference, HostProject, WorkspaceProject, documents);
return state; return state;
} }
@ -198,23 +224,22 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
throw new ArgumentNullException(nameof(hostDocument)); throw new ArgumentNullException(nameof(hostDocument));
} }
if (!Documents.ContainsKey(hostDocument.FilePath)) if (!Documents.TryGetValue(hostDocument.FilePath, out var document))
{ {
return this; return this;
} }
var documents = new Dictionary<string, DocumentState>(FilePathComparer.Instance); var documents = Documents.SetItem(hostDocument.FilePath, document.WithText(sourceText, version));
foreach (var kvp in Documents)
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)) var state = new ProjectState(this, ProjectDifference.DocumentChanged, HostProject, WorkspaceProject, documents, ImportsToRelatedDocuments);
{
documents[hostDocument.FilePath] = document.WithText(sourceText, version);
}
var state = new ProjectState(this, ProjectDifference.DocumentChanged, HostProject, WorkspaceProject, documents);
return state; return state;
} }
@ -225,23 +250,22 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
throw new ArgumentNullException(nameof(hostDocument)); throw new ArgumentNullException(nameof(hostDocument));
} }
if (!Documents.ContainsKey(hostDocument.FilePath)) if (!Documents.TryGetValue(hostDocument.FilePath, out var document))
{ {
return this; return this;
} }
var documents = new Dictionary<string, DocumentState>(FilePathComparer.Instance); var documents = Documents.SetItem(hostDocument.FilePath, document.WithTextLoader(loader));
foreach (var kvp in Documents)
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)) var state = new ProjectState(this, ProjectDifference.DocumentChanged, HostProject, WorkspaceProject, documents, ImportsToRelatedDocuments);
{
documents[hostDocument.FilePath] = document.WithTextLoader(loader);
}
var state = new ProjectState(this, ProjectDifference.DocumentChanged, HostProject, WorkspaceProject, documents);
return state; return state;
} }
@ -257,14 +281,19 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
return this; return this;
} }
var difference = ProjectDifference.ConfigurationChanged; var documents = Documents.ToImmutableDictionary(kvp => kvp.Key, kvp => kvp.Value.WithConfigurationChange(), FilePathComparer.Instance);
var documents = new Dictionary<string, DocumentState>(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; return state;
} }
@ -291,14 +320,52 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
return this; return this;
} }
var documents = new Dictionary<string, DocumentState>(FilePathComparer.Instance); var documents = Documents.ToImmutableDictionary(kvp => kvp.Key, kvp => kvp.Value.WithWorkspaceProjectChange(), FilePathComparer.Instance);
foreach (var kvp in Documents) var state = new ProjectState(this, difference, HostProject, workspaceProject, documents, ImportsToRelatedDocuments);
return state;
}
private static ImmutableDictionary<string, ImmutableArray<string>> AddToImportsToRelatedDocuments(
ImmutableDictionary<string, ImmutableArray<string>> importsToRelatedDocuments,
HostDocument hostDocument,
List<string> importTargetPaths)
{
foreach (var importTargetPath in importTargetPaths)
{ {
documents.Add(kvp.Key, kvp.Value.WithWorkspaceProjectChange()); if (!importsToRelatedDocuments.TryGetValue(importTargetPath, out var relatedDocuments))
{
relatedDocuments = ImmutableArray.Create<string>();
}
relatedDocuments = relatedDocuments.Add(hostDocument.FilePath);
importsToRelatedDocuments = importsToRelatedDocuments.SetItem(importTargetPath, relatedDocuments);
} }
var state = new ProjectState(this, difference, HostProject, workspaceProject, documents); return importsToRelatedDocuments;
return state; }
private static ImmutableDictionary<string, ImmutableArray<string>> RemoveFromImportsToRelatedDocuments(
ImmutableDictionary<string, ImmutableArray<string>> importsToRelatedDocuments,
HostDocument hostDocument,
List<string> 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;
} }
} }
} }

View File

@ -76,6 +76,16 @@ namespace Microsoft.CodeAnalysis.Remote.Razor
return null; return null;
} }
public override bool IsImportDocument(DocumentSnapshot document)
{
throw new NotImplementedException();
}
public override IEnumerable<DocumentSnapshot> GetRelatedDocuments(DocumentSnapshot document)
{
throw new NotImplementedException();
}
public override RazorProjectEngine GetProjectEngine() public override RazorProjectEngine GetProjectEngine()
{ {
throw new NotImplementedException(); throw new NotImplementedException();

View File

@ -237,7 +237,7 @@ namespace Microsoft.CodeAnalysis.Razor
{ {
case ProjectChangeKind.ProjectAdded: case ProjectChangeKind.ProjectAdded:
{ {
var projectSnapshot = _projectManager.GetLoadedProject(e.ProjectFilePath); var projectSnapshot = e.Newer;
foreach (var documentFilePath in projectSnapshot.DocumentFilePaths) foreach (var documentFilePath in projectSnapshot.DocumentFilePaths)
{ {
Enqueue(projectSnapshot, projectSnapshot.GetDocument(documentFilePath)); Enqueue(projectSnapshot, projectSnapshot.GetDocument(documentFilePath));
@ -247,7 +247,7 @@ namespace Microsoft.CodeAnalysis.Razor
} }
case ProjectChangeKind.ProjectChanged: case ProjectChangeKind.ProjectChanged:
{ {
var projectSnapshot = _projectManager.GetLoadedProject(e.ProjectFilePath); var projectSnapshot = e.Newer;
foreach (var documentFilePath in projectSnapshot.DocumentFilePaths) foreach (var documentFilePath in projectSnapshot.DocumentFilePaths)
{ {
Enqueue(projectSnapshot, projectSnapshot.GetDocument(documentFilePath)); Enqueue(projectSnapshot, projectSnapshot.GetDocument(documentFilePath));
@ -257,23 +257,35 @@ namespace Microsoft.CodeAnalysis.Razor
} }
case ProjectChangeKind.DocumentAdded: case ProjectChangeKind.DocumentAdded:
case ProjectChangeKind.DocumentChanged:
{ {
var project = _projectManager.GetLoadedProject(e.ProjectFilePath); var project = e.Newer;
Enqueue(project, project.GetDocument(e.DocumentFilePath)); var document = project.GetDocument(e.DocumentFilePath);
Enqueue(project, document);
foreach (var relatedDocument in project.GetRelatedDocuments(document))
{
Enqueue(project, document);
}
break; break;
} }
case ProjectChangeKind.DocumentChanged: case ProjectChangeKind.DocumentRemoved:
{ {
var project = _projectManager.GetLoadedProject(e.ProjectFilePath); // For removals use the old snapshot to find the removed document, so we can figure out
Enqueue(project, project.GetDocument(e.DocumentFilePath)); // 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; break;
} }
case ProjectChangeKind.ProjectRemoved: case ProjectChangeKind.ProjectRemoved:
case ProjectChangeKind.DocumentRemoved:
{ {
// ignore // ignore
break; break;

View File

@ -1,36 +1,28 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // 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 System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text;
using Xunit; using Xunit;
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{ {
public class DefaultDocumentSnapshotTest public class DefaultDocumentSnapshotTest : WorkspaceTestBase
{ {
public DefaultDocumentSnapshotTest() public DefaultDocumentSnapshotTest()
{ {
var services = TestServices.Create( var projectState = ProjectState.Create(Workspace.Services, TestProjectData.SomeProject);
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 project = new DefaultProjectSnapshot(projectState); var project = new DefaultProjectSnapshot(projectState);
HostDocument = new HostDocument("C:/some/path/file.cshtml", "C:/some/path/file.cshtml"); HostDocument = TestProjectData.SomeProjectFile1;
SourceText = Text.SourceText.From("<p>Hello World</p>"); SourceText = SourceText.From("<p>Hello World</p>");
Version = VersionStamp.Default.GetNewerVersion(); Version = VersionStamp.Default.GetNewerVersion();
var textAndVersion = TextAndVersion.Create(SourceText, Version); var textAndVersion = TextAndVersion.Create(SourceText, Version);
var documentState = DocumentState.Create(Workspace.Services, HostDocument, () => Task.FromResult(textAndVersion)); var documentState = DocumentState.Create(Workspace.Services, HostDocument, () => Task.FromResult(textAndVersion));
Document = new DefaultDocumentSnapshot(project, documentState); Document = new DefaultDocumentSnapshot(project, documentState);
} }
private Workspace Workspace { get; }
private SourceText SourceText { get; } private SourceText SourceText { get; }
private VersionStamp Version { get; } private VersionStamp Version { get; }
@ -39,6 +31,11 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
private DefaultDocumentSnapshot Document { get; } private DefaultDocumentSnapshot Document { get; }
protected override void ConfigureLanguageServices(List<ILanguageService> services)
{
services.Add(new TestTagHelperResolver());
}
[Fact] [Fact]
public async Task GetGeneratedOutputAsync_SetsHostDocumentOutput() public async Task GetGeneratedOutputAsync_SetsHostDocumentOutput()
{ {

View File

@ -1,35 +1,23 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // 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.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis.Host;
using Xunit; using Xunit;
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{ {
public class DefaultProjectSnapshotTest public class DefaultProjectSnapshotTest : WorkspaceTestBase
{ {
public DefaultProjectSnapshotTest() public DefaultProjectSnapshotTest()
{ {
TagHelperResolver = new TestTagHelperResolver(); TagHelperResolver = new TestTagHelperResolver();
HostServices = TestServices.Create( HostProject = new HostProject(TestProjectData.SomeProject.FilePath, FallbackRazorConfiguration.MVC_2_0);
new IWorkspaceService[] HostProjectWithConfigurationChange = new HostProject(TestProjectData.SomeProject.FilePath, FallbackRazorConfiguration.MVC_1_0);
{
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);
var projectId = ProjectId.CreateNewId("Test"); var projectId = ProjectId.CreateNewId("Test");
var solution = Workspace.CurrentSolution.AddProject(ProjectInfo.Create( var solution = Workspace.CurrentSolution.AddProject(ProjectInfo.Create(
@ -38,19 +26,21 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
"Test", "Test",
"Test", "Test",
LanguageNames.CSharp, LanguageNames.CSharp,
"c:\\MyProject\\Test.csproj")); TestProjectData.SomeProject.FilePath));
WorkspaceProject = solution.GetProject(projectId); WorkspaceProject = solution.GetProject(projectId);
SomeTagHelpers = new List<TagHelperDescriptor>(); SomeTagHelpers = new List<TagHelperDescriptor>
SomeTagHelpers.Add(TagHelperDescriptorBuilder.Create("Test1", "TestAssembly").Build()); {
TagHelperDescriptorBuilder.Create("Test1", "TestAssembly").Build()
};
Documents = new HostDocument[] Documents = new HostDocument[]
{ {
new HostDocument("c:\\MyProject\\File.cshtml", "File.cshtml"), TestProjectData.SomeProjectFile1,
new HostDocument("c:\\MyProject\\Index.cshtml", "Index.cshtml"), TestProjectData.SomeProjectFile2,
// linked file // 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 TestTagHelperResolver TagHelperResolver { get; }
private HostServices HostServices { get; }
private Workspace Workspace { get; }
private List<TagHelperDescriptor> SomeTagHelpers { get; } private List<TagHelperDescriptor> SomeTagHelpers { get; }
private void Configure(RazorProjectEngineBuilder builder)
{
builder.Features.Remove(builder.Features.OfType<IImportProjectFeature>().Single());
builder.Features.Add(new TestImportProjectFeature());
}
protected override void ConfigureLanguageServices(List<ILanguageService> services)
{
services.Add(TagHelperResolver);
}
protected override void ConfigureProjectEngine(RazorProjectEngineBuilder builder)
{
builder.Features.Remove(builder.Features.OfType<IImportProjectFeature>().Single());
builder.Features.Add(new TestImportProjectFeature());
}
[Fact] [Fact]
public void ProjectSnapshot_CachesDocumentSnapshots() public void ProjectSnapshot_CachesDocumentSnapshots()
{ {
@ -114,5 +117,79 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
TagHelperResolver.CompletionSource.SetCanceled(); 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));
}
} }
} }

View File

@ -3,37 +3,22 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text;
using Moq;
using Xunit; using Xunit;
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{ {
public class DocumentStateTest public class DocumentStateTest : WorkspaceTestBase
{ {
public DocumentStateTest() public DocumentStateTest()
{ {
TagHelperResolver = new TestTagHelperResolver(); TagHelperResolver = new TestTagHelperResolver();
HostServices = TestServices.Create( HostProject = new HostProject(TestProjectData.SomeProject.FilePath, FallbackRazorConfiguration.MVC_2_0);
new IWorkspaceService[] HostProjectWithConfigurationChange = new HostProject(TestProjectData.SomeProject.FilePath, FallbackRazorConfiguration.MVC_1_0);
{
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);
var projectId = ProjectId.CreateNewId("Test"); var projectId = ProjectId.CreateNewId("Test");
var solution = Workspace.CurrentSolution.AddProject(ProjectInfo.Create( var solution = Workspace.CurrentSolution.AddProject(ProjectInfo.Create(
@ -42,13 +27,13 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
"Test", "Test",
"Test", "Test",
LanguageNames.CSharp, LanguageNames.CSharp,
"c:\\MyProject\\Test.csproj")); TestProjectData.SomeProject.FilePath));
WorkspaceProject = solution.GetProject(projectId); WorkspaceProject = solution.GetProject(projectId);
SomeTagHelpers = new List<TagHelperDescriptor>(); SomeTagHelpers = new List<TagHelperDescriptor>();
SomeTagHelpers.Add(TagHelperDescriptorBuilder.Create("Test1", "TestAssembly").Build()); SomeTagHelpers.Add(TagHelperDescriptorBuilder.Create("Test1", "TestAssembly").Build());
Document = new HostDocument("c:\\MyProject\\File.cshtml", "File.cshtml"); Document = TestProjectData.SomeProjectFile1;
Text = SourceText.From("Hello, world!"); Text = SourceText.From("Hello, world!");
TextLoader = () => Task.FromResult(TextAndVersion.Create(Text, VersionStamp.Create())); TextLoader = () => Task.FromResult(TextAndVersion.Create(Text, VersionStamp.Create()));
@ -64,16 +49,17 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
private TestTagHelperResolver TagHelperResolver { get; } private TestTagHelperResolver TagHelperResolver { get; }
private HostServices HostServices { get; }
private Workspace Workspace { get; }
private List<TagHelperDescriptor> SomeTagHelpers { get; } private List<TagHelperDescriptor> SomeTagHelpers { get; }
private Func<Task<TextAndVersion>> TextLoader { get; } private Func<Task<TextAndVersion>> TextLoader { get; }
private SourceText Text { get; } private SourceText Text { get; }
protected override void ConfigureLanguageServices(List<ILanguageService> services)
{
services.Add(TagHelperResolver);
}
[Fact] [Fact]
public async Task DocumentState_CreatedNew_HasEmptyText() public async Task DocumentState_CreatedNew_HasEmptyText()
{ {
@ -145,6 +131,38 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
Assert.True(state.TryGetTextVersion(out _)); 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] [Fact]
public void DocumentState_WithWorkspaceProjectChange_CachesSnapshotText() public void DocumentState_WithWorkspaceProjectChange_CachesSnapshotText()
{ {
@ -160,7 +178,6 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
Assert.True(state.TryGetTextVersion(out _)); Assert.True(state.TryGetTextVersion(out _));
} }
[Fact] [Fact]
public async Task DocumentState_WithWorkspaceProjectChange_CachesLoadedText() public async Task DocumentState_WithWorkspaceProjectChange_CachesLoadedText()
{ {

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language;
@ -12,26 +13,14 @@ using Xunit;
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{ {
public class ProjectStateTest public class ProjectStateTest : WorkspaceTestBase
{ {
public ProjectStateTest() public ProjectStateTest()
{ {
TagHelperResolver = new TestTagHelperResolver(); TagHelperResolver = new TestTagHelperResolver();
HostServices = TestServices.Create( HostProject = new HostProject(TestProjectData.SomeProject.FilePath, FallbackRazorConfiguration.MVC_2_0);
new IWorkspaceService[] HostProjectWithConfigurationChange = new HostProject(TestProjectData.SomeProject.FilePath, FallbackRazorConfiguration.MVC_1_0);
{
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);
var projectId = ProjectId.CreateNewId("Test"); var projectId = ProjectId.CreateNewId("Test");
var solution = Workspace.CurrentSolution.AddProject(ProjectInfo.Create( var solution = Workspace.CurrentSolution.AddProject(ProjectInfo.Create(
@ -40,7 +29,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
"Test", "Test",
"Test", "Test",
LanguageNames.CSharp, LanguageNames.CSharp,
"c:\\MyProject\\Test.csproj")); TestProjectData.SomeProject.FilePath));
WorkspaceProject = solution.GetProject(projectId); WorkspaceProject = solution.GetProject(projectId);
SomeTagHelpers = new List<TagHelperDescriptor>(); SomeTagHelpers = new List<TagHelperDescriptor>();
@ -48,11 +37,11 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
Documents = new HostDocument[] Documents = new HostDocument[]
{ {
new HostDocument("c:\\MyProject\\File.cshtml", "File.cshtml"), TestProjectData.SomeProjectFile1,
new HostDocument("c:\\MyProject\\Index.cshtml", "Index.cshtml"), TestProjectData.SomeProjectFile2,
// linked file // linked file
new HostDocument("c:\\SomeOtherProject\\Index.cshtml", "Pages\\Index.cshtml"), TestProjectData.AnotherProjectNestedFile3,
}; };
Text = SourceText.From("Hello, world!"); Text = SourceText.From("Hello, world!");
@ -69,16 +58,23 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
private TestTagHelperResolver TagHelperResolver { get; } private TestTagHelperResolver TagHelperResolver { get; }
private HostServices HostServices { get; }
private Workspace Workspace { get; }
private List<TagHelperDescriptor> SomeTagHelpers { get; } private List<TagHelperDescriptor> SomeTagHelpers { get; }
private Func<Task<TextAndVersion>> TextLoader { get; } private Func<Task<TextAndVersion>> TextLoader { get; }
private SourceText Text { get; } private SourceText Text { get; }
protected override void ConfigureLanguageServices(List<ILanguageService> services)
{
services.Add(TagHelperResolver);
}
protected override void ConfigureProjectEngine(RazorProjectEngineBuilder builder)
{
builder.Features.Remove(builder.Features.OfType<IImportProjectFeature>().Single());
builder.Features.Add(new TestImportProjectFeature());
}
[Fact] [Fact]
public void ProjectState_ConstructedNew() public void ProjectState_ConstructedNew()
{ {
@ -139,9 +135,93 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
Assert.Collection( Assert.Collection(
state.Documents.OrderBy(kvp => kvp.Key), 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[0], d.Value.HostDocument),
d => Assert.Same(Documents[1], d.Value.HostDocument), d => Assert.Same(Documents[1], d.Value.HostDocument));
d => Assert.Same(Documents[2], 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] [Fact]
@ -311,6 +391,68 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
d => Assert.Same(Documents[2], d.Value.HostDocument)); 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] [Fact]
public void ProjectState_RemoveHostDocument_RetainsComputedState() public void ProjectState_RemoveHostDocument_RetainsComputedState()
{ {
@ -399,12 +541,12 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Arrange // Arrange
var callCount = 0; var callCount = 0;
var documents = new Dictionary<string, DocumentState>(); var documents = ImmutableDictionary.CreateBuilder<string, DocumentState>(FilePathComparer.Instance);
documents[Documents[1].FilePath] = TestDocumentState.Create(Workspace.Services, Documents[1], onConfigurationChange: () => callCount++); documents[Documents[1].FilePath] = TestDocumentState.Create(Workspace.Services, Documents[1], onConfigurationChange: () => callCount++);
documents[Documents[2].FilePath] = TestDocumentState.Create(Workspace.Services, Documents[2], onConfigurationChange: () => callCount++); documents[Documents[2].FilePath] = TestDocumentState.Create(Workspace.Services, Documents[2], onConfigurationChange: () => callCount++);
var original = ProjectState.Create(Workspace.Services, HostProject, WorkspaceProject); var original = ProjectState.Create(Workspace.Services, HostProject, WorkspaceProject);
original.Documents = documents; original.Documents = documents.ToImmutable();
var changed = WorkspaceProject.WithAssemblyName("Test1"); var changed = WorkspaceProject.WithAssemblyName("Test1");
@ -503,12 +645,12 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Arrange // Arrange
var callCount = 0; var callCount = 0;
var documents = new Dictionary<string, DocumentState>(); var documents = ImmutableDictionary.CreateBuilder<string, DocumentState>(FilePathComparer.Instance);
documents[Documents[1].FilePath] = TestDocumentState.Create(Workspace.Services, Documents[1], onWorkspaceProjectChange: () => callCount++); documents[Documents[1].FilePath] = TestDocumentState.Create(Workspace.Services, Documents[1], onWorkspaceProjectChange: () => callCount++);
documents[Documents[2].FilePath] = TestDocumentState.Create(Workspace.Services, Documents[2], onWorkspaceProjectChange: () => callCount++); documents[Documents[2].FilePath] = TestDocumentState.Create(Workspace.Services, Documents[2], onWorkspaceProjectChange: () => callCount++);
var original = ProjectState.Create(Workspace.Services, HostProject, WorkspaceProject); var original = ProjectState.Create(Workspace.Services, HostProject, WorkspaceProject);
original.Documents = documents; original.Documents = documents.ToImmutable();
var changed = WorkspaceProject.WithAssemblyName("Test1"); var changed = WorkspaceProject.WithAssemblyName("Test1");
@ -520,6 +662,231 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
Assert.Equal(2, callCount); 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<string, DocumentState>(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<string, ImmutableArray<string>>(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<string, DocumentState>(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<string, ImmutableArray<string>>(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<string, DocumentState>(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<string, ImmutableArray<string>>(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<string, DocumentState>(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<string, ImmutableArray<string>>(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<string, DocumentState>(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<string, ImmutableArray<string>>(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 private class TestDocumentState : DocumentState
{ {
public static TestDocumentState Create( public static TestDocumentState Create(
@ -529,15 +896,27 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
Action onTextChange = null, Action onTextChange = null,
Action onTextLoaderChange = null, Action onTextLoaderChange = null,
Action onConfigurationChange = null, Action onConfigurationChange = null,
Action onImportsChange = null,
Action onWorkspaceProjectChange = 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; private readonly Action _onTextChange;
Action _onTextLoaderChange; private readonly Action _onTextLoaderChange;
Action _onConfigurationChange; private readonly Action _onConfigurationChange;
Action _onWorkspaceProjectChange; private readonly Action _onImportsChange;
private readonly Action _onWorkspaceProjectChange;
private TestDocumentState( private TestDocumentState(
HostWorkspaceServices services, HostWorkspaceServices services,
@ -548,12 +927,14 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
Action onTextChange, Action onTextChange,
Action onTextLoaderChange, Action onTextLoaderChange,
Action onConfigurationChange, Action onConfigurationChange,
Action onImportsChange,
Action onWorkspaceProjectChange) Action onWorkspaceProjectChange)
: base(services, hostDocument, text, version, loader) : base(services, hostDocument, text, version, loader)
{ {
_onTextChange = onTextChange; _onTextChange = onTextChange;
_onTextLoaderChange = onTextLoaderChange; _onTextLoaderChange = onTextLoaderChange;
_onConfigurationChange = onConfigurationChange; _onConfigurationChange = onConfigurationChange;
_onImportsChange = onImportsChange;
_onWorkspaceProjectChange = onWorkspaceProjectChange; _onWorkspaceProjectChange = onWorkspaceProjectChange;
} }
@ -575,6 +956,12 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
return base.WithConfigurationChange(); return base.WithConfigurationChange();
} }
public override DocumentState WithImportsChange()
{
_onImportsChange?.Invoke();
return base.WithImportsChange();
}
public override DocumentState WithWorkspaceProjectChange() public override DocumentState WithWorkspaceProjectChange()
{ {
_onWorkspaceProjectChange?.Invoke(); _onWorkspaceProjectChange?.Invoke();

View File

@ -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<RazorProjectItem> GetImports(RazorProjectItem projectItem)
{
if (projectItem == null)
{
throw new ArgumentNullException(nameof(projectItem));
}
var imports = new List<RazorProjectItem>();
AddHierarchicalImports(projectItem, imports);
return imports;
}
private void AddHierarchicalImports(RazorProjectItem projectItem, List<RazorProjectItem> 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);
}
}
}

View File

@ -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;
}
}

View File

@ -8,11 +8,13 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{ {
internal class TestProjectSnapshotProjectEngineFactory : ProjectSnapshotProjectEngineFactory internal class TestProjectSnapshotProjectEngineFactory : ProjectSnapshotProjectEngineFactory
{ {
public Action<RazorProjectEngineBuilder> Configure { get; set; }
public RazorProjectEngine Engine { get; set; } public RazorProjectEngine Engine { get; set; }
public override RazorProjectEngine Create(ProjectSnapshot project, RazorProjectFileSystem fileSystem, Action<RazorProjectEngineBuilder> configure) public override RazorProjectEngine Create(RazorConfiguration configuration, RazorProjectFileSystem fileSystem, Action<RazorProjectEngineBuilder> configure)
{ {
return Engine ?? RazorProjectEngine.Create(project.Configuration, fileSystem, configure); return Engine ?? RazorProjectEngine.Create(configuration, fileSystem, configure ?? Configure);
} }
public override IProjectEngineFactory FindFactory(ProjectSnapshot project) public override IProjectEngineFactory FindFactory(ProjectSnapshot project)

View File

@ -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<IWorkspaceService> services)
{
}
protected virtual void ConfigureLanguageServices(List<ILanguageService> services)
{
}
protected virtual void ConfigureWorkspace(AdhocWorkspace workspace)
{
}
protected virtual void ConfigureProjectEngine(RazorProjectEngineBuilder builder)
{
}
private void EnsureInitialized()
{
if (_initialized)
{
return;
}
var workspaceServices = new List<IWorkspaceService>()
{
new TestProjectSnapshotProjectEngineFactory()
{
Configure = ConfigureProjectEngine,
},
};
ConfigureWorkspaceServices(workspaceServices);
var languageServices = new List<ILanguageService>();
ConfigureLanguageServices(languageServices);
_hostServices = TestServices.Create(workspaceServices, languageServices);
_workspace = TestWorkspace.Create(_hostServices, ConfigureWorkspace);
_initialized = true;
}
}
}

View File

@ -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<Task> GetScheduledTasks()
{
return Enumerable.Empty<Task>();
}
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)}]");
}
}
}
}

View File

@ -1,11 +1,6 @@
// Copyright (c) .NET Foundation. All rights reserved. // 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. // 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; using Microsoft.CodeAnalysis.Razor;
namespace Xunit namespace Xunit
@ -13,40 +8,5 @@ namespace Xunit
public abstract class ForegroundDispatcherTestBase public abstract class ForegroundDispatcherTestBase
{ {
internal ForegroundDispatcher Dispatcher { get; } = new SingleThreadedForegroundDispatcher(); 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<Task> GetScheduledTasks()
{
return Enumerable.Empty<Task>();
}
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)}]");
}
}
} }
} }

View File

@ -14,7 +14,8 @@ namespace Microsoft.VisualStudio.Editor.Razor.Documents
{ {
public DefaultImportDocumentManagerIntegrationTest() public DefaultImportDocumentManagerIntegrationTest()
{ {
ProjectPath = "C:\\path\\to\\project\\project.csproj"; ProjectPath = TestProjectData.SomeProject.FilePath;
DirectoryPath = Path.GetDirectoryName(ProjectPath);
FileSystem = RazorProjectFileSystem.Create(Path.GetDirectoryName(ProjectPath)); FileSystem = RazorProjectFileSystem.Create(Path.GetDirectoryName(ProjectPath));
ProjectEngine = RazorProjectEngine.Create(FallbackRazorConfiguration.MVC_2_1, FileSystem, b => 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; } private string ProjectPath { get; }
@ -36,15 +37,15 @@ namespace Microsoft.VisualStudio.Editor.Razor.Documents
public void Changed_TrackerChanged_ResultsInChangedHavingCorrectArgs() public void Changed_TrackerChanged_ResultsInChangedHavingCorrectArgs()
{ {
// Arrange // Arrange
var testImportsPath = "C:\\path\\to\\project\\_ViewImports.cshtml"; var testImportsPath = Path.Combine(DirectoryPath, "_ViewImports.cshtml");
var tracker = Mock.Of<VisualStudioDocumentTracker>( var tracker = Mock.Of<VisualStudioDocumentTracker>(
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.ProjectPath == ProjectPath &&
t.ProjectSnapshot == Mock.Of<ProjectSnapshot>(p => p.GetProjectEngine() == ProjectEngine)); t.ProjectSnapshot == Mock.Of<ProjectSnapshot>(p => p.GetProjectEngine() == ProjectEngine));
var anotherTracker = Mock.Of<VisualStudioDocumentTracker>( var anotherTracker = Mock.Of<VisualStudioDocumentTracker>(
t => t.FilePath == "C:\\path\\to\\project\\anotherFile.cshtml" && t => t.FilePath == Path.Combine(DirectoryPath, "anotherFile.cshtml") &&
t.ProjectPath == ProjectPath && t.ProjectPath == ProjectPath &&
t.ProjectSnapshot == Mock.Of<ProjectSnapshot>(p => p.GetProjectEngine() == ProjectEngine)); t.ProjectSnapshot == Mock.Of<ProjectSnapshot>(p => p.GetProjectEngine() == ProjectEngine));
@ -58,10 +59,10 @@ namespace Microsoft.VisualStudio.Editor.Razor.Documents
.Returns(fileChangeTracker.Object); .Returns(fileChangeTracker.Object);
fileChangeTrackerFactory 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<FileChangeTracker>()); .Returns(Mock.Of<FileChangeTracker>());
fileChangeTrackerFactory 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<FileChangeTracker>()); .Returns(Mock.Of<FileChangeTracker>());
var called = false; var called = false;
@ -76,8 +77,8 @@ namespace Microsoft.VisualStudio.Editor.Razor.Documents
Assert.Equal(FileChangeKind.Changed, args.Kind); Assert.Equal(FileChangeKind.Changed, args.Kind);
Assert.Collection( Assert.Collection(
args.AssociatedDocuments, args.AssociatedDocuments,
f => Assert.Equal("C:\\path\\to\\project\\Views\\Home\\file.cshtml", f), f => Assert.Equal(Path.Combine(DirectoryPath, "Views", "Home", "_ViewImports.cshtml"), f),
f => Assert.Equal("C:\\path\\to\\project\\anotherFile.cshtml", f)); f => Assert.Equal(Path.Combine(DirectoryPath, "anotherFile.cshtml"), f));
}; };
// Act // Act

View File

@ -16,7 +16,8 @@ namespace Microsoft.VisualStudio.Editor.Razor
{ {
public DefaultImportDocumentManagerTest() public DefaultImportDocumentManagerTest()
{ {
ProjectPath = "C:\\path\\to\\project\\project.csproj"; ProjectPath = TestProjectData.SomeProject.FilePath;
DirectoryPath = Path.GetDirectoryName(ProjectPath);
FileSystem = RazorProjectFileSystem.Create(Path.GetDirectoryName(ProjectPath)); FileSystem = RazorProjectFileSystem.Create(Path.GetDirectoryName(ProjectPath));
ProjectEngine = RazorProjectEngine.Create(FallbackRazorConfiguration.MVC_2_1, FileSystem, b => 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 ProjectPath { get; }
private string DirectoryPath { get; }
private RazorProjectFileSystem FileSystem { get; } private RazorProjectFileSystem FileSystem { get; }
private RazorProjectEngine ProjectEngine { get; } private RazorProjectEngine ProjectEngine { get; }
@ -39,7 +40,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
{ {
// Arrange // Arrange
var tracker = Mock.Of<VisualStudioDocumentTracker>( var tracker = Mock.Of<VisualStudioDocumentTracker>(
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.ProjectPath == ProjectPath &&
t.ProjectSnapshot == Mock.Of<ProjectSnapshot>(p => p.GetProjectEngine() == ProjectEngine)); t.ProjectSnapshot == Mock.Of<ProjectSnapshot>(p => p.GetProjectEngine() == ProjectEngine));
@ -49,7 +50,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
.Setup(f => f.StartListening()) .Setup(f => f.StartListening())
.Verifiable(); .Verifiable();
fileChangeTrackerFactory 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) .Returns(fileChangeTracker1.Object)
.Verifiable(); .Verifiable();
var fileChangeTracker2 = new Mock<FileChangeTracker>(); var fileChangeTracker2 = new Mock<FileChangeTracker>();
@ -57,13 +58,13 @@ namespace Microsoft.VisualStudio.Editor.Razor
.Setup(f => f.StartListening()) .Setup(f => f.StartListening())
.Verifiable(); .Verifiable();
fileChangeTrackerFactory 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) .Returns(fileChangeTracker2.Object)
.Verifiable(); .Verifiable();
var fileChangeTracker3 = new Mock<FileChangeTracker>(); var fileChangeTracker3 = new Mock<FileChangeTracker>();
fileChangeTracker3.Setup(f => f.StartListening()).Verifiable(); fileChangeTracker3.Setup(f => f.StartListening()).Verifiable();
fileChangeTrackerFactory fileChangeTrackerFactory
.Setup(f => f.Create("C:\\path\\to\\project\\_ViewImports.cshtml")) .Setup(f => f.Create(Path.Combine(DirectoryPath, "_ViewImports.cshtml")))
.Returns(fileChangeTracker3.Object) .Returns(fileChangeTracker3.Object)
.Verifiable(); .Verifiable();
@ -84,12 +85,12 @@ namespace Microsoft.VisualStudio.Editor.Razor
{ {
// Arrange // Arrange
var tracker = Mock.Of<VisualStudioDocumentTracker>( var tracker = Mock.Of<VisualStudioDocumentTracker>(
t => t.FilePath == "C:\\path\\to\\project\\file.cshtml" && t => t.FilePath == Path.Combine(DirectoryPath, "file.cshtml") &&
t.ProjectPath == ProjectPath && t.ProjectPath == ProjectPath &&
t.ProjectSnapshot == Mock.Of<ProjectSnapshot>(p => p.GetProjectEngine() == ProjectEngine)); t.ProjectSnapshot == Mock.Of<ProjectSnapshot>(p => p.GetProjectEngine() == ProjectEngine));
var anotherTracker = Mock.Of<VisualStudioDocumentTracker>( var anotherTracker = Mock.Of<VisualStudioDocumentTracker>(
t => t.FilePath == "C:\\path\\to\\project\\anotherFile.cshtml" && t => t.FilePath == Path.Combine(DirectoryPath, "anotherFile.cshtml") &&
t.ProjectPath == ProjectPath && t.ProjectPath == ProjectPath &&
t.ProjectSnapshot == Mock.Of<ProjectSnapshot>(p => p.GetProjectEngine() == ProjectEngine)); t.ProjectSnapshot == Mock.Of<ProjectSnapshot>(p => p.GetProjectEngine() == ProjectEngine));
@ -115,7 +116,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
{ {
// Arrange // Arrange
var tracker = Mock.Of<VisualStudioDocumentTracker>( var tracker = Mock.Of<VisualStudioDocumentTracker>(
t => t.FilePath == "C:\\path\\to\\project\\file.cshtml" && t => t.FilePath == Path.Combine(DirectoryPath, "file.cshtml") &&
t.ProjectPath == ProjectPath && t.ProjectPath == ProjectPath &&
t.ProjectSnapshot == Mock.Of<ProjectSnapshot>(p => p.GetProjectEngine() == ProjectEngine)); t.ProjectSnapshot == Mock.Of<ProjectSnapshot>(p => p.GetProjectEngine() == ProjectEngine));
@ -125,7 +126,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
.Setup(f => f.StopListening()) .Setup(f => f.StopListening())
.Verifiable(); .Verifiable();
fileChangeTrackerFactory fileChangeTrackerFactory
.Setup(f => f.Create("C:\\path\\to\\project\\_ViewImports.cshtml")) .Setup(f => f.Create(Path.Combine(DirectoryPath, "_ViewImports.cshtml")))
.Returns(fileChangeTracker.Object) .Returns(fileChangeTracker.Object)
.Verifiable(); .Verifiable();
@ -145,12 +146,12 @@ namespace Microsoft.VisualStudio.Editor.Razor
{ {
// Arrange // Arrange
var tracker = Mock.Of<VisualStudioDocumentTracker>( var tracker = Mock.Of<VisualStudioDocumentTracker>(
t => t.FilePath == "C:\\path\\to\\project\\file.cshtml" && t => t.FilePath == Path.Combine(DirectoryPath, "file.cshtml") &&
t.ProjectPath == ProjectPath && t.ProjectPath == ProjectPath &&
t.ProjectSnapshot == Mock.Of<ProjectSnapshot>(p => p.GetProjectEngine() == ProjectEngine)); t.ProjectSnapshot == Mock.Of<ProjectSnapshot>(p => p.GetProjectEngine() == ProjectEngine));
var anotherTracker = Mock.Of<VisualStudioDocumentTracker>( var anotherTracker = Mock.Of<VisualStudioDocumentTracker>(
t => t.FilePath == "C:\\path\\to\\project\\anotherFile.cshtml" && t => t.FilePath == Path.Combine(DirectoryPath, "anotherFile.cshtml") &&
t.ProjectPath == ProjectPath && t.ProjectPath == ProjectPath &&
t.ProjectSnapshot == Mock.Of<ProjectSnapshot>(p => p.GetProjectEngine() == ProjectEngine)); t.ProjectSnapshot == Mock.Of<ProjectSnapshot>(p => p.GetProjectEngine() == ProjectEngine));

View File

@ -26,8 +26,8 @@ namespace Microsoft.VisualStudio.Editor.Razor
RazorCoreContentType = Mock.Of<IContentType>(c => c.IsOfType(RazorLanguage.ContentType) == true); RazorCoreContentType = Mock.Of<IContentType>(c => c.IsOfType(RazorLanguage.ContentType) == true);
TextBuffer = Mock.Of<ITextBuffer>(b => b.ContentType == RazorCoreContentType); TextBuffer = Mock.Of<ITextBuffer>(b => b.ContentType == RazorCoreContentType);
FilePath = "C:/Some/Path/TestDocumentTracker.cshtml"; FilePath = TestProjectData.SomeProjectFile1.FilePath;
ProjectPath = "C:/Some/Path/TestProject.csproj"; ProjectPath = TestProjectData.SomeProject.FilePath;
ImportDocumentManager = Mock.Of<ImportDocumentManager>(); ImportDocumentManager = Mock.Of<ImportDocumentManager>();
WorkspaceEditorSettings = new DefaultWorkspaceEditorSettings(Mock.Of<ForegroundDispatcher>(), Mock.Of<EditorSettingsManager>()); WorkspaceEditorSettings = new DefaultWorkspaceEditorSettings(Mock.Of<ForegroundDispatcher>(), Mock.Of<EditorSettingsManager>());
@ -56,7 +56,8 @@ namespace Microsoft.VisualStudio.Editor.Razor
ProjectManager = new TestProjectSnapshotManager(Dispatcher, Workspace) { AllowNotifyListeners = true }; ProjectManager = new TestProjectSnapshotManager(Dispatcher, Workspace) { AllowNotifyListeners = true };
HostProject = new HostProject(ProjectPath, FallbackRazorConfiguration.MVC_2_1); 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( DocumentTracker = new DefaultVisualStudioDocumentTracker(
Dispatcher, Dispatcher,
@ -79,6 +80,8 @@ namespace Microsoft.VisualStudio.Editor.Razor
private HostProject HostProject { get; } private HostProject HostProject { get; }
private HostProject UpdatedHostProject { get; }
private HostProject OtherHostProject { get; } private HostProject OtherHostProject { get; }
private Project WorkspaceProject { get; set; } private Project WorkspaceProject { get; set; }
@ -191,7 +194,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
ProjectManager.HostProjectAdded(HostProject); ProjectManager.HostProjectAdded(HostProject);
ProjectManager.WorkspaceProjectAdded(WorkspaceProject); ProjectManager.WorkspaceProjectAdded(WorkspaceProject);
var e = new ProjectChangeEventArgs(ProjectPath, ProjectChangeKind.ProjectAdded); var e = new ProjectChangeEventArgs(null, ProjectManager.GetLoadedProject(HostProject.FilePath), ProjectChangeKind.ProjectAdded);
var called = false; var called = false;
DocumentTracker.ContextChanged += (sender, args) => DocumentTracker.ContextChanged += (sender, args) =>
@ -216,7 +219,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
ProjectManager.HostProjectAdded(HostProject); ProjectManager.HostProjectAdded(HostProject);
ProjectManager.WorkspaceProjectAdded(WorkspaceProject); ProjectManager.WorkspaceProjectAdded(WorkspaceProject);
var e = new ProjectChangeEventArgs(ProjectPath, ProjectChangeKind.ProjectChanged); var e = new ProjectChangeEventArgs(null, ProjectManager.GetLoadedProject(HostProject.FilePath), ProjectChangeKind.ProjectChanged);
var called = false; var called = false;
DocumentTracker.ContextChanged += (sender, args) => DocumentTracker.ContextChanged += (sender, args) =>
@ -238,7 +241,13 @@ namespace Microsoft.VisualStudio.Editor.Razor
public void ProjectManager_Changed_ProjectRemoved_TriggersContextChanged_WithEphemeralProject() public void ProjectManager_Changed_ProjectRemoved_TriggersContextChanged_WithEphemeralProject()
{ {
// Arrange // 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; var called = false;
DocumentTracker.ContextChanged += (sender, args) => DocumentTracker.ContextChanged += (sender, args) =>
@ -260,7 +269,9 @@ namespace Microsoft.VisualStudio.Editor.Razor
public void ProjectManager_Changed_IgnoresUnknownProject() public void ProjectManager_Changed_IgnoresUnknownProject()
{ {
// Arrange // 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; var called = false;
DocumentTracker.ContextChanged += (sender, args) => DocumentTracker.ContextChanged += (sender, args) =>
@ -488,13 +499,13 @@ namespace Microsoft.VisualStudio.Editor.Razor
DocumentTracker.ContextChanged += (sender, e) => { args.Add(e); }; DocumentTracker.ContextChanged += (sender, e) => { args.Add(e); };
// Act // Act
ProjectManager.HostProjectChanged(OtherHostProject); ProjectManager.HostProjectChanged(UpdatedHostProject);
await DocumentTracker.PendingTagHelperTask; await DocumentTracker.PendingTagHelperTask;
// Assert // Assert
var snapshot = Assert.IsType<DefaultProjectSnapshot>(DocumentTracker.ProjectSnapshot); var snapshot = Assert.IsType<DefaultProjectSnapshot>(DocumentTracker.ProjectSnapshot);
Assert.Same(OtherHostProject, snapshot.HostProject); Assert.Same(UpdatedHostProject, snapshot.HostProject);
Assert.Collection( Assert.Collection(
args, args,

View File

@ -24,7 +24,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
var engine = RazorProjectEngine.Create(RazorConfiguration.Default, RazorProjectFileSystem.Empty); var engine = RazorProjectEngine.Create(RazorConfiguration.Default, RazorProjectFileSystem.Empty);
ProjectEngineFactory = Mock.Of<ProjectSnapshotProjectEngineFactory>( ProjectEngineFactory = Mock.Of<ProjectSnapshotProjectEngineFactory>(
f => f.Create( f => f.Create(
It.IsAny<ProjectSnapshot>(), It.IsAny<RazorConfiguration>(),
It.IsAny<RazorProjectFileSystem>(), It.IsAny<RazorProjectFileSystem>(),
It.IsAny<Action<RazorProjectEngineBuilder>>()) == engine); It.IsAny<Action<RazorProjectEngineBuilder>>()) == engine);
} }

View File

@ -21,13 +21,13 @@ namespace Microsoft.VisualStudio.Editor.Razor.Documents
private TestEditorDocumentManager Manager { get; } 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")); public TestTextBuffer TextBuffer => new TestTextBuffer(new StringTextSnapshot("HI"));
@ -113,8 +113,8 @@ namespace Microsoft.VisualStudio.Editor.Razor.Documents
// Assert // Assert
Assert.Collection( Assert.Collection(
documents.OrderBy(d => d.ProjectFilePath), 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] [ForegroundFact]
@ -147,8 +147,8 @@ namespace Microsoft.VisualStudio.Editor.Razor.Documents
// Assert // Assert
Assert.Collection( Assert.Collection(
Manager.Opened.OrderBy(d => d.ProjectFilePath), 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] [ForegroundFact]
@ -165,8 +165,8 @@ namespace Microsoft.VisualStudio.Editor.Razor.Documents
// Assert // Assert
Assert.Collection( Assert.Collection(
Manager.Closed.OrderBy(d => d.ProjectFilePath), 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 private class TestEditorDocumentManager : EditorDocumentManagerBase

View File

@ -17,8 +17,8 @@ namespace Microsoft.VisualStudio.Editor.Razor.Documents
{ {
public EditorDocumentManagerListenerTest() public EditorDocumentManagerListenerTest()
{ {
ProjectFilePath = "C:\\project1\\project.csproj"; ProjectFilePath = TestProjectData.SomeProject.FilePath;
DocumentFilePath = "c:\\project1\\file1.cshtml"; DocumentFilePath = TestProjectData.SomeProjectFile1.FilePath;
TextLoader = TextLoader.From(TextAndVersion.Create(SourceText.From("FILE"), VersionStamp.Default)); TextLoader = TextLoader.From(TextAndVersion.Create(SourceText.From("FILE"), VersionStamp.Default));
FileChangeTracker = new DefaultFileChangeTracker(DocumentFilePath); 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 listener = new EditorDocumentManagerListener(editorDocumentManger.Object, changedOnDisk, changedInEditor, opened, closed);
var project = Mock.Of<ProjectSnapshot>(p => p.FilePath == "/Path/to/project.csproj");
// Act & Assert // 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] [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 listener = new EditorDocumentManagerListener(editorDocumentManger.Object, onChangedOnDisk: null, onChangedInEditor: null, onOpened: opened, onClosed: null);
var project = Mock.Of<ProjectSnapshot>(p => p.FilePath == "/Path/to/project.csproj");
// Act // 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
Assert.True(called); Assert.True(called);

View File

@ -3,6 +3,7 @@
using System; using System;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Razor;
using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text;
using Microsoft.VisualStudio.Test; using Microsoft.VisualStudio.Test;
using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text;
@ -16,8 +17,8 @@ namespace Microsoft.VisualStudio.Editor.Razor.Documents
public EditorDocumentTest() public EditorDocumentTest()
{ {
DocumentManager = Mock.Of<EditorDocumentManager>(); DocumentManager = Mock.Of<EditorDocumentManager>();
ProjectFilePath = "C:\\project1\\project.csproj"; ProjectFilePath = TestProjectData.SomeProject.FilePath;
DocumentFilePath = "c:\\project1\\file1.cshtml"; DocumentFilePath = TestProjectData.SomeProjectFile1.FilePath;
TextLoader = TextLoader.From(TextAndVersion.Create(SourceText.From("FILE"), VersionStamp.Default)); TextLoader = TextLoader.From(TextAndVersion.Create(SourceText.From("FILE"), VersionStamp.Default));
FileChangeTracker = new DefaultFileChangeTracker(DocumentFilePath); FileChangeTracker = new DefaultFileChangeTracker(DocumentFilePath);

View File

@ -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();
}
}

View File

@ -5,6 +5,8 @@ using System;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis.Host;
using Moq; using Moq;
using Xunit; 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 // 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. // the only thing in here is threading.
public class BackgroundDocumentGeneratorTest : ForegroundDispatcherTestBase public class BackgroundDocumentGeneratorTest : ForegroundDispatcherWorkspaceTestBase
{ {
public BackgroundDocumentGeneratorTest() public BackgroundDocumentGeneratorTest()
{ {
Documents = new HostDocument[] Documents = new HostDocument[]
{ {
new HostDocument("c:\\Test1\\Index.cshtml", "Index.cshtml"), TestProjectData.SomeProjectFile1,
new HostDocument("c:\\Test1\\Components\\Counter.cshtml", "Components\\Counter.cshtml"), TestProjectData.AnotherProjectFile1,
}; };
HostProject1 = new HostProject("c:\\Test1\\Test1.csproj", FallbackRazorConfiguration.MVC_1_0); HostProject1 = new HostProject(TestProjectData.SomeProject.FilePath, FallbackRazorConfiguration.MVC_1_0);
HostProject2 = new HostProject("c:\\Test2\\Test2.csproj", FallbackRazorConfiguration.MVC_1_0); HostProject2 = new HostProject(TestProjectData.AnotherProject.FilePath, FallbackRazorConfiguration.MVC_1_0);
Workspace = TestWorkspace.Create();
var projectId1 = ProjectId.CreateNewId("Test1"); var projectId1 = ProjectId.CreateNewId("Test1");
var projectId2 = ProjectId.CreateNewId("Test2"); var projectId2 = ProjectId.CreateNewId("Test2");
@ -37,14 +37,14 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
"Test1", "Test1",
"Test1", "Test1",
LanguageNames.CSharp, LanguageNames.CSharp,
"c:\\Test1\\Test1.csproj")) TestProjectData.SomeProject.FilePath))
.AddProject(ProjectInfo.Create( .AddProject(ProjectInfo.Create(
projectId2, projectId2,
VersionStamp.Default, VersionStamp.Default,
"Test2", "Test2",
"Test2", "Test2",
LanguageNames.CSharp, LanguageNames.CSharp,
"c:\\Test2\\Test2.csproj")); ; TestProjectData.AnotherProject.FilePath)); ;
WorkspaceProject1 = solution.GetProject(projectId1); WorkspaceProject1 = solution.GetProject(projectId1);
WorkspaceProject2 = solution.GetProject(projectId2); WorkspaceProject2 = solution.GetProject(projectId2);
@ -60,7 +60,11 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
private Project WorkspaceProject2 { get; } private Project WorkspaceProject2 { get; }
private Workspace Workspace { get; } protected override void ConfigureProjectEngine(RazorProjectEngineBuilder builder)
{
builder.Features.Remove(builder.Features.OfType<IImportProjectFeature>().Single());
builder.Features.Add(new TestImportProjectFeature());
}
[ForegroundFact] [ForegroundFact]
public async Task Queue_ProcessesNotifications_AndGoesBackToSleep() public async Task Queue_ProcessesNotifications_AndGoesBackToSleep()

View File

@ -23,7 +23,7 @@ namespace Microsoft.VisualStudio.Editor.Razor.Documents
.Setup(f => f.AdviseFileChange(It.IsAny<string>(), It.IsAny<uint>(), It.IsAny<IVsFileChangeEvents>(), out cookie)) .Setup(f => f.AdviseFileChange(It.IsAny<string>(), It.IsAny<uint>(), It.IsAny<IVsFileChangeEvents>(), out cookie))
.Returns(VSConstants.S_OK) .Returns(VSConstants.S_OK)
.Verifiable(); .Verifiable();
var tracker = new VisualStudioFileChangeTracker("C:/_ViewImports.cshtml", Dispatcher, ErrorReporter, fileChangeService.Object); var tracker = new VisualStudioFileChangeTracker(TestProjectData.SomeProjectImportFile.FilePath, Dispatcher, ErrorReporter, fileChangeService.Object);
// Act // Act
tracker.StartListening(); tracker.StartListening();
@ -43,7 +43,7 @@ namespace Microsoft.VisualStudio.Editor.Razor.Documents
.Setup(f => f.AdviseFileChange(It.IsAny<string>(), It.IsAny<uint>(), It.IsAny<IVsFileChangeEvents>(), out cookie)) .Setup(f => f.AdviseFileChange(It.IsAny<string>(), It.IsAny<uint>(), It.IsAny<IVsFileChangeEvents>(), out cookie))
.Returns(VSConstants.S_OK) .Returns(VSConstants.S_OK)
.Callback(() => callCount++); .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(); tracker.StartListening();
// Act // Act
@ -67,7 +67,7 @@ namespace Microsoft.VisualStudio.Editor.Razor.Documents
.Setup(f => f.UnadviseFileChange(cookie)) .Setup(f => f.UnadviseFileChange(cookie))
.Returns(VSConstants.S_OK) .Returns(VSConstants.S_OK)
.Verifiable(); .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. tracker.StartListening(); // Start listening for changes.
// Act // Act
@ -86,7 +86,7 @@ namespace Microsoft.VisualStudio.Editor.Razor.Documents
fileChangeService fileChangeService
.Setup(f => f.UnadviseFileChange(cookie)) .Setup(f => f.UnadviseFileChange(cookie))
.Throws(new InvalidOperationException()); .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 // Act & Assert
tracker.StopListening(); tracker.StopListening();
@ -100,7 +100,7 @@ namespace Microsoft.VisualStudio.Editor.Razor.Documents
public void FilesChanged_WithSpecificFlags_InvokesChangedHandler_WithExpectedArguments(uint fileChangeFlag, int expectedKind) public void FilesChanged_WithSpecificFlags_InvokesChangedHandler_WithExpectedArguments(uint fileChangeFlag, int expectedKind)
{ {
// Arrange // Arrange
var filePath = "C:\\path\\to\\project\\_ViewImports.cshtml"; var filePath = TestProjectData.SomeProjectImportFile.FilePath;
uint cookie; uint cookie;
var fileChangeService = new Mock<IVsFileChangeEx>(); var fileChangeService = new Mock<IVsFileChangeEx>();
fileChangeService fileChangeService

View File

@ -14,6 +14,9 @@
<Compile Include="..\Microsoft.CodeAnalysis.Razor.Workspaces.Test\Shared\**\*.cs"> <Compile Include="..\Microsoft.CodeAnalysis.Razor.Workspaces.Test\Shared\**\*.cs">
<Link>Shared\%(RecursiveDir)%(FileName)%(Extension)</Link> <Link>Shared\%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile> </Compile>
<Compile Include="..\Microsoft.VisualStudio.Editor.Razor.Test\Shared\**\*.cs">
<Link>Shared\%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>

View File

@ -13,35 +13,24 @@ using Xunit;
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{ {
public class DefaultProjectSnapshotManagerTest : ForegroundDispatcherTestBase public class DefaultProjectSnapshotManagerTest : ForegroundDispatcherWorkspaceTestBase
{ {
public DefaultProjectSnapshotManagerTest() public DefaultProjectSnapshotManagerTest()
{ {
TagHelperResolver = new TestTagHelperResolver(); TagHelperResolver = new TestTagHelperResolver();
HostServices = TestServices.Create(
new IWorkspaceService[]
{
new TestProjectSnapshotProjectEngineFactory(),
},
new ILanguageService[]
{
TagHelperResolver,
});
Documents = new HostDocument[] Documents = new HostDocument[]
{ {
new HostDocument("c:\\MyProject\\File.cshtml", "File.cshtml"), TestProjectData.SomeProjectFile1,
new HostDocument("c:\\MyProject\\Index.cshtml", "Index.cshtml"), TestProjectData.SomeProjectFile2,
// linked file // linked file
new HostDocument("c:\\SomeOtherProject\\Index.cshtml", "Pages\\Index.cshtml"), TestProjectData.AnotherProjectNestedFile3,
}; };
HostProject = new HostProject("c:\\MyProject\\Test.csproj", FallbackRazorConfiguration.MVC_2_0); HostProject = new HostProject(TestProjectData.SomeProject.FilePath, FallbackRazorConfiguration.MVC_2_0);
HostProjectWithConfigurationChange = new HostProject("c:\\MyProject\\Test.csproj", FallbackRazorConfiguration.MVC_1_0); HostProjectWithConfigurationChange = new HostProject(TestProjectData.SomeProject.FilePath, FallbackRazorConfiguration.MVC_1_0);
Workspace = TestWorkspace.Create(HostServices);
ProjectManager = new TestProjectSnapshotManager(Dispatcher, Enumerable.Empty<ProjectSnapshotChangeTrigger>(), Workspace); ProjectManager = new TestProjectSnapshotManager(Dispatcher, Enumerable.Empty<ProjectSnapshotChangeTrigger>(), Workspace);
var projectId = ProjectId.CreateNewId("Test"); var projectId = ProjectId.CreateNewId("Test");
@ -51,7 +40,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
"Test", "Test",
"Test", "Test",
LanguageNames.CSharp, LanguageNames.CSharp,
"c:\\MyProject\\Test.csproj")); TestProjectData.SomeProject.FilePath));
WorkspaceProject = solution.GetProject(projectId); WorkspaceProject = solution.GetProject(projectId);
var vbProjectId = ProjectId.CreateNewId("VB"); var vbProjectId = ProjectId.CreateNewId("VB");
@ -81,7 +70,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
"Test (Different TFM)", "Test (Different TFM)",
"Test", "Test",
LanguageNames.CSharp, LanguageNames.CSharp,
"c:\\MyProject\\Test.csproj")); TestProjectData.SomeProject.FilePath));
WorkspaceProjectWithDifferentTfm = solution.GetProject(projectIdWithDifferentTfm); WorkspaceProjectWithDifferentTfm = solution.GetProject(projectIdWithDifferentTfm);
SomeTagHelpers = TagHelperResolver.TagHelpers; SomeTagHelpers = TagHelperResolver.TagHelpers;
@ -108,14 +97,15 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
private TestProjectSnapshotManager ProjectManager { get; } private TestProjectSnapshotManager ProjectManager { get; }
private HostServices HostServices { get; }
private Workspace Workspace { get; }
private SourceText SourceText { get; } private SourceText SourceText { get; }
private IList<TagHelperDescriptor> SomeTagHelpers { get; } private IList<TagHelperDescriptor> SomeTagHelpers { get; }
protected override void ConfigureLanguageServices(List<ILanguageService> services)
{
services.Add(TagHelperResolver);
}
[ForegroundFact] [ForegroundFact]
public void DocumentAdded_AddsDocument() public void DocumentAdded_AddsDocument()
{ {
@ -129,7 +119,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Assert // Assert
var snapshot = ProjectManager.GetSnapshot(HostProject); 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); Assert.Equal(ProjectChangeKind.DocumentAdded, ProjectManager.ListenersNotifiedOf);
} }
@ -148,7 +138,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Assert // Assert
var snapshot = ProjectManager.GetSnapshot(HostProject); 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); Assert.Null(ProjectManager.ListenersNotifiedOf);
} }
@ -262,9 +252,9 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Assert // Assert
var snapshot = ProjectManager.GetSnapshot(HostProject); var snapshot = ProjectManager.GetSnapshot(HostProject);
Assert.Collection( Assert.Collection(
snapshot.DocumentFilePaths, snapshot.DocumentFilePaths.OrderBy(f => f),
d => Assert.Equal(Documents[0].FilePath, d), d => Assert.Equal(Documents[2].FilePath, d),
d => Assert.Equal(Documents[2].FilePath, d)); d => Assert.Equal(Documents[0].FilePath, d));
Assert.Equal(ProjectChangeKind.DocumentRemoved, ProjectManager.ListenersNotifiedOf); Assert.Equal(ProjectChangeKind.DocumentRemoved, ProjectManager.ListenersNotifiedOf);
} }

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable; using System.Collections.Immutable;
using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language;
@ -15,11 +16,10 @@ using ItemCollection = Microsoft.VisualStudio.ProjectSystem.ItemCollection;
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{ {
public class DefaultRazorProjectHostTest : ForegroundDispatcherTestBase public class DefaultRazorProjectHostTest : ForegroundDispatcherWorkspaceTestBase
{ {
public DefaultRazorProjectHostTest() public DefaultRazorProjectHostTest()
{ {
Workspace = new AdhocWorkspace();
ProjectManager = new TestProjectSnapshotManager(Dispatcher, Workspace); ProjectManager = new TestProjectSnapshotManager(Dispatcher, Workspace);
ConfigurationItems = new ItemCollection(Rules.RazorConfiguration.SchemaName); ConfigurationItems = new ItemCollection(Rules.RazorConfiguration.SchemaName);
@ -38,8 +38,6 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
private TestProjectSnapshotManager ProjectManager { get; } private TestProjectSnapshotManager ProjectManager { get; }
private Workspace Workspace { get; }
[Fact] [Fact]
public void TryGetDefaultConfiguration_FailsIfNoRule() public void TryGetDefaultConfiguration_FailsIfNoRule()
{ {
@ -612,7 +610,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
public async Task DefaultRazorProjectHost_ForegroundThread_CreateAndDispose_Succeeds() public async Task DefaultRazorProjectHost_ForegroundThread_CreateAndDispose_Succeeds()
{ {
// Arrange // Arrange
var services = new TestProjectSystemServices("c:\\MyProject\\Test.csproj"); var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath);
var host = new DefaultRazorProjectHost(services, Workspace, ProjectManager); var host = new DefaultRazorProjectHost(services, Workspace, ProjectManager);
// Act & Assert // Act & Assert
@ -627,7 +625,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
public async Task DefaultRazorProjectHost_BackgroundThread_CreateAndDispose_Succeeds() public async Task DefaultRazorProjectHost_BackgroundThread_CreateAndDispose_Succeeds()
{ {
// Arrange // Arrange
var services = new TestProjectSystemServices("c:\\MyProject\\Test.csproj"); var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath);
var host = new DefaultRazorProjectHost(services, Workspace, ProjectManager); var host = new DefaultRazorProjectHost(services, Workspace, ProjectManager);
// Act & Assert // 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); var host = new DefaultRazorProjectHost(services, Workspace, ProjectManager);
// Act & Assert // Act & Assert
@ -670,8 +668,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
ExtensionItems.Item("MVC-2.1"); ExtensionItems.Item("MVC-2.1");
ExtensionItems.Item("Another-Thing"); ExtensionItems.Item("Another-Thing");
DocumentItems.Item("File.cshtml"); DocumentItems.Item(Path.GetFileName(TestProjectData.SomeProjectFile1.FilePath));
DocumentItems.Property("File.cshtml", Rules.RazorGenerateWithTargetPath.TargetPathProperty, "File.cshtml"); DocumentItems.Property(Path.GetFileName(TestProjectData.SomeProjectFile1.FilePath), Rules.RazorGenerateWithTargetPath.TargetPathProperty, TestProjectData.SomeProjectFile1.TargetPath);
var changes = new TestProjectChangeDescription[] var changes = new TestProjectChangeDescription[]
{ {
@ -681,7 +679,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
DocumentItems.ToChange(), DocumentItems.ToChange(),
}; };
var services = new TestProjectSystemServices("c:\\MyProject\\Test.csproj"); var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath);
var host = new DefaultRazorProjectHost(services, Workspace, ProjectManager); var host = new DefaultRazorProjectHost(services, Workspace, ProjectManager);
@ -693,7 +691,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Assert // Assert
var snapshot = Assert.Single(ProjectManager.Projects); 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(RazorLanguageVersion.Version_2_1, snapshot.Configuration.LanguageVersion);
Assert.Equal("MVC-2.1", snapshot.Configuration.ConfigurationName); Assert.Equal("MVC-2.1", snapshot.Configuration.ConfigurationName);
@ -707,8 +705,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
d => d =>
{ {
var document = snapshot.GetDocument(d); var document = snapshot.GetDocument(d);
Assert.Equal("c:\\MyProject\\File.cshtml", document.FilePath); Assert.Equal(TestProjectData.SomeProjectFile1.FilePath, document.FilePath);
Assert.Equal("File.cshtml", document.TargetPath); Assert.Equal(TestProjectData.SomeProjectFile1.TargetPath, document.TargetPath);
}); });
await Task.Run(async () => await host.DisposeAsync()); await Task.Run(async () => await host.DisposeAsync());
@ -726,7 +724,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
ExtensionItems.Item("TestExtension"); ExtensionItems.Item("TestExtension");
DocumentItems.Item("File.cshtml"); DocumentItems.Item(Path.GetFileName(TestProjectData.SomeProjectFile1.FilePath));
var changes = new TestProjectChangeDescription[] var changes = new TestProjectChangeDescription[]
{ {
@ -736,7 +734,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
DocumentItems.ToChange(), DocumentItems.ToChange(),
}; };
var services = new TestProjectSystemServices("c:\\MyProject\\Test.csproj"); var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath);
var host = new DefaultRazorProjectHost(services, Workspace, ProjectManager); var host = new DefaultRazorProjectHost(services, Workspace, ProjectManager);
@ -766,8 +764,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
ExtensionItems.Item("MVC-2.1"); ExtensionItems.Item("MVC-2.1");
ExtensionItems.Item("Another-Thing"); ExtensionItems.Item("Another-Thing");
DocumentItems.Item("File.cshtml"); DocumentItems.Item(Path.GetFileName(TestProjectData.SomeProjectFile1.FilePath));
DocumentItems.Property("File.cshtml", Rules.RazorGenerateWithTargetPath.TargetPathProperty, "File.cshtml"); DocumentItems.Property(Path.GetFileName(TestProjectData.SomeProjectFile1.FilePath), Rules.RazorGenerateWithTargetPath.TargetPathProperty, TestProjectData.SomeProjectFile1.TargetPath);
var changes = new TestProjectChangeDescription[] var changes = new TestProjectChangeDescription[]
{ {
@ -777,7 +775,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
DocumentItems.ToChange(), DocumentItems.ToChange(),
}; };
var services = new TestProjectSystemServices("c:\\MyProject\\Test.csproj"); var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath);
var host = new DefaultRazorProjectHost(services, Workspace, ProjectManager); var host = new DefaultRazorProjectHost(services, Workspace, ProjectManager);
@ -789,7 +787,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Assert - 1 // Assert - 1
var snapshot = Assert.Single(ProjectManager.Projects); 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(RazorLanguageVersion.Version_2_1, snapshot.Configuration.LanguageVersion);
Assert.Equal("MVC-2.1", snapshot.Configuration.ConfigurationName); Assert.Equal("MVC-2.1", snapshot.Configuration.ConfigurationName);
@ -803,8 +801,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
d => d =>
{ {
var document = snapshot.GetDocument(d); var document = snapshot.GetDocument(d);
Assert.Equal("c:\\MyProject\\File.cshtml", document.FilePath); Assert.Equal(TestProjectData.SomeProjectFile1.FilePath, document.FilePath);
Assert.Equal("File.cshtml", document.TargetPath); Assert.Equal(TestProjectData.SomeProjectFile1.TargetPath, document.TargetPath);
}); });
// Act - 2 // Act - 2
@ -813,9 +811,9 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
ConfigurationItems.RemoveItem("MVC-2.1"); ConfigurationItems.RemoveItem("MVC-2.1");
ConfigurationItems.Item("MVC-2.0", new Dictionary<string, string>() { { "Extensions", "MVC-2.0;Another-Thing" }, }); ConfigurationItems.Item("MVC-2.0", new Dictionary<string, string>() { { "Extensions", "MVC-2.0;Another-Thing" }, });
ExtensionItems.Item("MVC-2.0"); ExtensionItems.Item("MVC-2.0");
DocumentItems.Item("c:\\AnotherProject\\AnotherFile.cshtml", new Dictionary<string, string>() DocumentItems.Item(TestProjectData.AnotherProjectNestedFile3.FilePath, new Dictionary<string, string>()
{ {
{ Rules.RazorGenerateWithTargetPath.TargetPathProperty, "Pages\\AnotherFile.cshtml" }, { Rules.RazorGenerateWithTargetPath.TargetPathProperty, TestProjectData.AnotherProjectNestedFile3.TargetPath },
}); });
changes = new TestProjectChangeDescription[] changes = new TestProjectChangeDescription[]
@ -830,7 +828,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Assert - 2 // Assert - 2
snapshot = Assert.Single(ProjectManager.Projects); 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(RazorLanguageVersion.Version_2_0, snapshot.Configuration.LanguageVersion);
Assert.Equal("MVC-2.0", snapshot.Configuration.ConfigurationName); Assert.Equal("MVC-2.0", snapshot.Configuration.ConfigurationName);
@ -844,14 +842,14 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
d => d =>
{ {
var document = snapshot.GetDocument(d); var document = snapshot.GetDocument(d);
Assert.Equal("c:\\AnotherProject\\AnotherFile.cshtml", document.FilePath); Assert.Equal(TestProjectData.AnotherProjectNestedFile3.FilePath, document.FilePath);
Assert.Equal("Pages\\AnotherFile.cshtml", document.TargetPath); Assert.Equal(TestProjectData.AnotherProjectNestedFile3.TargetPath, document.TargetPath);
}, },
d => d =>
{ {
var document = snapshot.GetDocument(d); var document = snapshot.GetDocument(d);
Assert.Equal("c:\\MyProject\\File.cshtml", document.FilePath); Assert.Equal(TestProjectData.SomeProjectFile1.FilePath, document.FilePath);
Assert.Equal("File.cshtml", document.TargetPath); Assert.Equal(TestProjectData.SomeProjectFile1.TargetPath, document.TargetPath);
}); });
await Task.Run(async () => await host.DisposeAsync()); await Task.Run(async () => await host.DisposeAsync());
@ -871,8 +869,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
ExtensionItems.Item("MVC-2.1"); ExtensionItems.Item("MVC-2.1");
ExtensionItems.Item("Another-Thing"); ExtensionItems.Item("Another-Thing");
DocumentItems.Item("File.cshtml"); DocumentItems.Item(Path.GetFileName(TestProjectData.SomeProjectFile1.FilePath));
DocumentItems.Property("File.cshtml", Rules.RazorGenerateWithTargetPath.TargetPathProperty, "File.cshtml"); DocumentItems.Property(Path.GetFileName(TestProjectData.SomeProjectFile1.FilePath), Rules.RazorGenerateWithTargetPath.TargetPathProperty, TestProjectData.SomeProjectFile1.TargetPath);
var changes = new TestProjectChangeDescription[] var changes = new TestProjectChangeDescription[]
{ {
@ -882,7 +880,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
DocumentItems.ToChange(), DocumentItems.ToChange(),
}; };
var services = new TestProjectSystemServices("c:\\MyProject\\Test.csproj"); var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath);
var host = new DefaultRazorProjectHost(services, Workspace, ProjectManager); var host = new DefaultRazorProjectHost(services, Workspace, ProjectManager);
@ -894,7 +892,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Assert - 1 // Assert - 1
var snapshot = Assert.Single(ProjectManager.Projects); 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(RazorLanguageVersion.Version_2_1, snapshot.Configuration.LanguageVersion);
Assert.Equal("MVC-2.1", snapshot.Configuration.ConfigurationName); Assert.Equal("MVC-2.1", snapshot.Configuration.ConfigurationName);
@ -937,8 +935,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
ExtensionItems.Item("MVC-2.1"); ExtensionItems.Item("MVC-2.1");
ExtensionItems.Item("Another-Thing"); ExtensionItems.Item("Another-Thing");
DocumentItems.Item("File.cshtml"); DocumentItems.Item(Path.GetFileName(TestProjectData.SomeProjectFile1.FilePath));
DocumentItems.Property("File.cshtml", Rules.RazorGenerateWithTargetPath.TargetPathProperty, "File.cshtml"); DocumentItems.Property(Path.GetFileName(TestProjectData.SomeProjectFile1.FilePath), Rules.RazorGenerateWithTargetPath.TargetPathProperty, TestProjectData.SomeProjectFile1.TargetPath);
var changes = new TestProjectChangeDescription[] var changes = new TestProjectChangeDescription[]
{ {
@ -948,7 +946,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
DocumentItems.ToChange(), DocumentItems.ToChange(),
}; };
var services = new TestProjectSystemServices("c:\\MyProject\\Test.csproj"); var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath);
var host = new DefaultRazorProjectHost(services, Workspace, ProjectManager); var host = new DefaultRazorProjectHost(services, Workspace, ProjectManager);
@ -960,7 +958,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Assert - 1 // Assert - 1
var snapshot = Assert.Single(ProjectManager.Projects); 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(RazorLanguageVersion.Version_2_1, snapshot.Configuration.LanguageVersion);
Assert.Equal("MVC-2.1", snapshot.Configuration.ConfigurationName); Assert.Equal("MVC-2.1", snapshot.Configuration.ConfigurationName);
@ -1007,8 +1005,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
ExtensionItems.Item("MVC-2.1"); ExtensionItems.Item("MVC-2.1");
ExtensionItems.Item("Another-Thing"); ExtensionItems.Item("Another-Thing");
DocumentItems.Item("File.cshtml"); DocumentItems.Item(Path.GetFileName(TestProjectData.SomeProjectFile1.FilePath));
DocumentItems.Property("File.cshtml", Rules.RazorGenerateWithTargetPath.TargetPathProperty, "File.cshtml"); DocumentItems.Property(Path.GetFileName(TestProjectData.SomeProjectFile1.FilePath), Rules.RazorGenerateWithTargetPath.TargetPathProperty, TestProjectData.SomeProjectFile1.TargetPath);
var changes = new TestProjectChangeDescription[] var changes = new TestProjectChangeDescription[]
{ {
@ -1018,7 +1016,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
DocumentItems.ToChange(), DocumentItems.ToChange(),
}; };
var services = new TestProjectSystemServices("c:\\MyProject\\Test.csproj"); var services = new TestProjectSystemServices(TestProjectData.SomeProject.FilePath);
var host = new DefaultRazorProjectHost(services, Workspace, ProjectManager); var host = new DefaultRazorProjectHost(services, Workspace, ProjectManager);
@ -1030,16 +1028,16 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Assert - 1 // Assert - 1
var snapshot = Assert.Single(ProjectManager.Projects); 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); Assert.Same("MVC-2.1", snapshot.Configuration.ConfigurationName);
// Act - 2 // Act - 2
services.UnconfiguredProject.FullPath = "c:\\AnotherProject\\Test2.csproj"; services.UnconfiguredProject.FullPath = TestProjectData.AnotherProject.FilePath;
await Task.Run(async () => await host.OnProjectRenamingAsync()); await Task.Run(async () => await host.OnProjectRenamingAsync());
// Assert - 1 // Assert - 1
snapshot = Assert.Single(ProjectManager.Projects); 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); Assert.Same("MVC-2.1", snapshot.Configuration.ConfigurationName);
await Task.Run(async () => await host.DisposeAsync()); await Task.Run(async () => await host.DisposeAsync());

View File

@ -12,11 +12,10 @@ using ItemReference = Microsoft.CodeAnalysis.Razor.ProjectSystem.ManagedProjectS
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{ {
public class FallbackRazorProjectHostTest : ForegroundDispatcherTestBase public class FallbackRazorProjectHostTest : ForegroundDispatcherWorkspaceTestBase
{ {
public FallbackRazorProjectHostTest() public FallbackRazorProjectHostTest()
{ {
Workspace = new AdhocWorkspace();
ProjectManager = new TestProjectSnapshotManager(Dispatcher, Workspace); ProjectManager = new TestProjectSnapshotManager(Dispatcher, Workspace);
ReferenceItems = new ItemCollection(ManagedProjectSystemSchema.ResolvedCompilationReference.SchemaName); ReferenceItems = new ItemCollection(ManagedProjectSystemSchema.ResolvedCompilationReference.SchemaName);
@ -32,8 +31,6 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
private ItemCollection NoneItems { get; } private ItemCollection NoneItems { get; }
private Workspace Workspace { get; }
[Fact] [Fact]
public void GetChangedAndRemovedDocuments_ReturnsChangedContentAndNoneItems() public void GetChangedAndRemovedDocuments_ReturnsChangedContentAndNoneItems()
{ {

View File

@ -9,11 +9,10 @@ using Xunit;
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{ {
public class WorkspaceProjectSnapshotChangeTriggerTest : ForegroundDispatcherTestBase public class WorkspaceProjectSnapshotChangeTriggerTest : ForegroundDispatcherWorkspaceTestBase
{ {
public WorkspaceProjectSnapshotChangeTriggerTest() public WorkspaceProjectSnapshotChangeTriggerTest()
{ {
Workspace = TestWorkspace.Create();
EmptySolution = Workspace.CurrentSolution.GetIsolatedSolution(); EmptySolution = Workspace.CurrentSolution.GetIsolatedSolution();
var projectId1 = ProjectId.CreateNewId("One"); var projectId1 = ProjectId.CreateNewId("One");
@ -72,8 +71,6 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
private Project ProjectNumberThree { get; } private Project ProjectNumberThree { get; }
private Workspace Workspace { get; }
[ForegroundTheory] [ForegroundTheory]
[InlineData(WorkspaceChangeKind.SolutionAdded)] [InlineData(WorkspaceChangeKind.SolutionAdded)]
[InlineData(WorkspaceChangeKind.SolutionChanged)] [InlineData(WorkspaceChangeKind.SolutionChanged)]

View File

@ -2,8 +2,8 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Razor;
using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using Microsoft.VisualStudio.Editor.Razor; using Microsoft.VisualStudio.Editor.Razor;
using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Shell.Interop;
@ -16,8 +16,8 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
{ {
public VsSolutionUpdatesProjectSnapshotChangeTriggerTest() public VsSolutionUpdatesProjectSnapshotChangeTriggerTest()
{ {
SomeProject = new HostProject("c:\\SomeProject\\SomeProject.csproj", FallbackRazorConfiguration.MVC_1_0); SomeProject = new HostProject(TestProjectData.SomeProject.FilePath, FallbackRazorConfiguration.MVC_1_0);
SomeOtherProject = new HostProject("c:\\SomeOtherProject\\SomeOtherProject.csproj", FallbackRazorConfiguration.MVC_2_0); SomeOtherProject = new HostProject(TestProjectData.AnotherProject.FilePath, FallbackRazorConfiguration.MVC_2_0);
Workspace = TestWorkspace.Create(w => Workspace = TestWorkspace.Create(w =>
{ {

View File

@ -8,6 +8,9 @@
<Compile Include="..\Microsoft.CodeAnalysis.Razor.Workspaces.Test\Shared\**\*.cs"> <Compile Include="..\Microsoft.CodeAnalysis.Razor.Workspaces.Test\Shared\**\*.cs">
<Link>Shared\%(RecursiveDir)%(FileName)%(Extension)</Link> <Link>Shared\%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile> </Compile>
<Compile Include="..\Microsoft.VisualStudio.Editor.Razor.Test\Shared\**\*.cs">
<Link>Shared\%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>