diff --git a/build/dependencies.props b/build/dependencies.props
index bd52ff645b..93d4967dee 100644
--- a/build/dependencies.props
+++ b/build/dependencies.props
@@ -8,6 +8,8 @@
2.1.0-preview2-30077
2.1.0-preview2-30106
2.1.0-preview2-30106
+ 15.3.409
+ 15.3.409
2.4.0
2.4.0
2.1.0-preview2-30106
@@ -46,6 +48,7 @@
2.6.0-beta1-62023-02
2.6.0-beta1-62023-02
2.6.0-beta1-62023-02
+ 2.6.0-beta1-62023-02
2.6.0-beta1-62023-02
2.6.0-beta1-62023-02
2.6.0-beta1-62023-02
diff --git a/src/Microsoft.AspNetCore.Razor.Language/RazorConfiguration.cs b/src/Microsoft.AspNetCore.Razor.Language/RazorConfiguration.cs
index f68db61f22..e8ef287c0c 100644
--- a/src/Microsoft.AspNetCore.Razor.Language/RazorConfiguration.cs
+++ b/src/Microsoft.AspNetCore.Razor.Language/RazorConfiguration.cs
@@ -7,15 +7,15 @@ using System.Linq;
namespace Microsoft.AspNetCore.Razor.Language
{
- public sealed class RazorConfiguration
+ public abstract class RazorConfiguration
{
- public static readonly RazorConfiguration Default = new RazorConfiguration(
+ public static readonly RazorConfiguration Default = new DefaultRazorConfiguration(
RazorLanguageVersion.Latest,
"unnamed",
Array.Empty());
- public RazorConfiguration(
- RazorLanguageVersion languageVersion,
+ public static RazorConfiguration Create(
+ RazorLanguageVersion languageVersion,
string configurationName,
IEnumerable extensions)
{
@@ -34,15 +34,32 @@ namespace Microsoft.AspNetCore.Razor.Language
throw new ArgumentNullException(nameof(extensions));
}
- LanguageVersion = languageVersion;
- ConfigurationName = configurationName;
- Extensions = extensions.ToArray();
+ return new DefaultRazorConfiguration(languageVersion, configurationName, extensions.ToArray());
}
- public string ConfigurationName { get; }
+ public abstract string ConfigurationName { get; }
- public IReadOnlyList Extensions { get; }
+ public abstract IReadOnlyList Extensions { get; }
- public RazorLanguageVersion LanguageVersion { get; }
+ public abstract RazorLanguageVersion LanguageVersion { get; }
+
+ private class DefaultRazorConfiguration : RazorConfiguration
+ {
+ public DefaultRazorConfiguration(
+ RazorLanguageVersion languageVersion,
+ string configurationName,
+ RazorExtension[] extensions)
+ {
+ LanguageVersion = languageVersion;
+ ConfigurationName = configurationName;
+ Extensions = extensions;
+ }
+
+ public override string ConfigurationName { get; }
+
+ public override IReadOnlyList Extensions { get; }
+
+ public override RazorLanguageVersion LanguageVersion { get; }
+ }
}
}
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultErrorReporter.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultErrorReporter.cs
index 664434a674..ff200966e8 100644
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultErrorReporter.cs
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultErrorReporter.cs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
+using Microsoft.CodeAnalysis.Razor.ProjectSystem;
namespace Microsoft.CodeAnalysis.Razor
{
@@ -9,11 +10,31 @@ namespace Microsoft.CodeAnalysis.Razor
{
public override void ReportError(Exception exception)
{
+ if (exception == null)
+ {
+ throw new ArgumentNullException(nameof(exception));
+ }
+
// Do nothing.
}
- public override void ReportError(Exception exception, Project project)
+ public override void ReportError(Exception exception, ProjectSnapshot project)
{
+ if (exception == null)
+ {
+ throw new ArgumentNullException(nameof(exception));
+ }
+
+ // Do nothing.
+ }
+
+ public override void ReportError(Exception exception, Project workspaceProject)
+ {
+ if (exception == null)
+ {
+ throw new ArgumentNullException(nameof(exception));
+ }
+
// Do nothing.
}
}
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ErrorReporter.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ErrorReporter.cs
index 03bc44f61c..4f0b0dab81 100644
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ErrorReporter.cs
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ErrorReporter.cs
@@ -3,13 +3,16 @@
using System;
using Microsoft.CodeAnalysis.Host;
+using Microsoft.CodeAnalysis.Razor.ProjectSystem;
namespace Microsoft.CodeAnalysis.Razor
{
internal abstract class ErrorReporter : IWorkspaceService
{
public abstract void ReportError(Exception exception);
+
+ public abstract void ReportError(Exception exception, ProjectSnapshot project);
- public abstract void ReportError(Exception exception, Project project);
+ public abstract void ReportError(Exception exception, Project workspaceProject);
}
}
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectExtensibilityConfigurationFactory.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectExtensibilityConfigurationFactory.cs
deleted file mode 100644
index d1c21ff88f..0000000000
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectExtensibilityConfigurationFactory.cs
+++ /dev/null
@@ -1,142 +0,0 @@
-// 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.Threading;
-using System.Threading.Tasks;
-using Microsoft.AspNetCore.Razor.Language;
-
-namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
-{
- // This is hardcoded for now. A more complete design would fan out to a list of providers.
- internal class DefaultProjectExtensibilityConfigurationFactory : ProjectExtensibilityConfigurationFactory
- {
- private const string MvcAssemblyName = "Microsoft.AspNetCore.Mvc.Razor";
- private const string RazorV1AssemblyName = "Microsoft.AspNetCore.Razor";
- private const string RazorV2AssemblyName = "Microsoft.AspNetCore.Razor.Language";
-
- // Using MaxValue here so that we ignore patch and build numbers. We only want to compare major/minor.
- private static readonly Version MaxSupportedRazorVersion = new Version(2, 0, Int32.MaxValue, Int32.MaxValue);
- private static readonly Version MaxSupportedMvcVersion = new Version(2, 0, Int32.MaxValue, Int32.MaxValue);
-
- private static readonly Version DefaultRazorVersion = new Version(2, 0, 0, 0);
- private static readonly Version DefaultMvcVersion = new Version(2, 0, 0, 0);
-
- public async override Task GetConfigurationAsync(Project project, CancellationToken cancellationToken = default(CancellationToken))
- {
- if (project == null)
- {
- throw new ArgumentNullException(nameof(project));
- }
-
- var compilation = await project.GetCompilationAsync(cancellationToken);
- return GetConfiguration(compilation.ReferencedAssemblyNames);
- }
-
- // internal/separate for testing.
- internal ProjectExtensibilityConfiguration GetConfiguration(IEnumerable references)
- {
- // Avoiding ToDictionary here because we don't want a crash if there is a duplicate name.
- var assemblies = new Dictionary();
- foreach (var assembly in references)
- {
- assemblies[assembly.Name] = assembly;
- }
-
- // First we look for the V2+ Razor Assembly. If we find this then its version is the correct Razor version.
- AssemblyIdentity razorAssembly;
- if (assemblies.TryGetValue(RazorV2AssemblyName, out razorAssembly))
- {
- if (razorAssembly.Version == null || razorAssembly.Version > MaxSupportedRazorVersion)
- {
- // This is a newer Razor version than we know, treat it as a fallback case.
- razorAssembly = null;
- }
- }
- else if (assemblies.TryGetValue(RazorV1AssemblyName, out razorAssembly))
- {
- // This assembly only counts as the 'Razor' assembly if it's a version lower than 2.0.0.
- if (razorAssembly.Version == null || razorAssembly.Version >= new Version(2, 0, 0, 0))
- {
- razorAssembly = null;
- }
- }
-
- AssemblyIdentity mvcAssembly;
- if (assemblies.TryGetValue(MvcAssemblyName, out mvcAssembly))
- {
- if (mvcAssembly.Version == null || mvcAssembly.Version > MaxSupportedMvcVersion)
- {
- // This is a newer MVC version than we know, treat it as a fallback case.
- mvcAssembly = null;
- }
- }
-
- RazorLanguageVersion languageVersion = null;
- if (razorAssembly != null && mvcAssembly != null)
- {
- languageVersion = GetLanguageVersion(razorAssembly);
-
- // This means we've definitely found a supported Razor version and an MVC version.
- return new MvcExtensibilityConfiguration(
- languageVersion,
- ProjectExtensibilityConfigurationKind.ApproximateMatch,
- new ProjectExtensibilityAssembly(razorAssembly),
- new ProjectExtensibilityAssembly(mvcAssembly));
- }
-
- // If we get here it means we didn't find everything, so we have to guess.
- if (razorAssembly == null || razorAssembly.Version == null)
- {
- razorAssembly = new AssemblyIdentity(RazorV2AssemblyName, DefaultRazorVersion);
- }
-
- if (mvcAssembly == null || mvcAssembly.Version == null)
- {
- mvcAssembly = new AssemblyIdentity(MvcAssemblyName, DefaultMvcVersion);
- }
-
- if (languageVersion == null)
- {
- languageVersion = GetLanguageVersion(razorAssembly);
- }
-
- return new MvcExtensibilityConfiguration(
- languageVersion,
- ProjectExtensibilityConfigurationKind.Fallback,
- new ProjectExtensibilityAssembly(razorAssembly),
- new ProjectExtensibilityAssembly(mvcAssembly));
- }
-
- // Internal for testing
- internal static RazorLanguageVersion GetLanguageVersion(AssemblyIdentity razorAssembly)
- {
- // This is inferred from the assembly for now, the Razor language version will eventually flow from MSBuild.
-
- var razorAssemblyVersion = razorAssembly.Version;
- if (razorAssemblyVersion.Major == 1)
- {
- if (razorAssemblyVersion.Minor >= 1)
- {
- return RazorLanguageVersion.Version_1_1;
- }
-
- return RazorLanguageVersion.Version_1_0;
- }
-
- if (razorAssemblyVersion.Major == 2)
- {
- if (razorAssemblyVersion.Minor >= 1)
- {
- return RazorLanguageVersion.Version_2_1;
- }
-
- return RazorLanguageVersion.Version_2_0;
- }
-
- // Couldn't determine version based off of assembly, fallback to latest.
- return RazorLanguageVersion.Latest;
- }
- }
-}
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectExtensibilityConfigurationFactoryFactory.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectExtensibilityConfigurationFactoryFactory.cs
deleted file mode 100644
index 93d7400ee3..0000000000
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectExtensibilityConfigurationFactoryFactory.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-// 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.Composition;
-using Microsoft.CodeAnalysis.Host;
-using Microsoft.CodeAnalysis.Host.Mef;
-
-namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
-{
- [Shared]
- [ExportLanguageServiceFactory(typeof(ProjectExtensibilityConfigurationFactory), RazorLanguage.Name)]
- internal class DefaultProjectExtensibilityConfigurationFactoryFactory : ILanguageServiceFactory
- {
- public ILanguageService CreateLanguageService(HostLanguageServices languageServices)
- {
- if (languageServices == null)
- {
- throw new ArgumentNullException(nameof(languageServices));
- }
-
- return new DefaultProjectExtensibilityConfigurationFactory();
- }
- }
-}
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshot.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshot.cs
index afbd0028f3..20d3864e4e 100644
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshot.cs
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshot.cs
@@ -2,8 +2,6 @@
// 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 Microsoft.AspNetCore.Razor.Language;
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
@@ -18,32 +16,60 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// at once.
internal class DefaultProjectSnapshot : ProjectSnapshot
{
- public DefaultProjectSnapshot(Project underlyingProject)
+ public DefaultProjectSnapshot(HostProject hostProject, Project workspaceProject, VersionStamp? version = null)
{
- if (underlyingProject == null)
+ if (hostProject == null)
{
- throw new ArgumentNullException(nameof(underlyingProject));
+ throw new ArgumentNullException(nameof(hostProject));
}
- UnderlyingProject = underlyingProject;
+ HostProject = hostProject;
+ WorkspaceProject = workspaceProject; // Might be null
+
+ FilePath = hostProject.FilePath;
+ Version = version ?? VersionStamp.Default;
}
- private DefaultProjectSnapshot(Project underlyingProject, DefaultProjectSnapshot other)
+ private DefaultProjectSnapshot(HostProject hostProject, DefaultProjectSnapshot other)
{
- if (underlyingProject == null)
+ if (hostProject == null)
{
- throw new ArgumentNullException(nameof(underlyingProject));
+ throw new ArgumentNullException(nameof(hostProject));
}
if (other == null)
{
throw new ArgumentNullException(nameof(other));
}
-
- UnderlyingProject = underlyingProject;
+
+ HostProject = hostProject;
ComputedVersion = other.ComputedVersion;
- Configuration = other.Configuration;
+ FilePath = other.FilePath;
+ WorkspaceProject = other.WorkspaceProject;
+
+ Version = other.Version.GetNewerVersion();
+ }
+
+ private DefaultProjectSnapshot(Project workspaceProject, DefaultProjectSnapshot other)
+ {
+ if (workspaceProject == null)
+ {
+ throw new ArgumentNullException(nameof(workspaceProject));
+ }
+
+ if (other == null)
+ {
+ throw new ArgumentNullException(nameof(other));
+ }
+
+ WorkspaceProject = workspaceProject;
+
+ ComputedVersion = other.ComputedVersion;
+ FilePath = other.FilePath;
+ HostProject = other.HostProject;
+
+ Version = other.Version.GetNewerVersion();
}
private DefaultProjectSnapshot(ProjectSnapshotUpdateContext update, DefaultProjectSnapshot other)
@@ -58,34 +84,67 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
throw new ArgumentNullException(nameof(other));
}
- UnderlyingProject = other.UnderlyingProject;
+ ComputedVersion = update.Version;
- ComputedVersion = update.UnderlyingProject.Version;
- Configuration = update.Configuration;
+ FilePath = other.FilePath;
+ HostProject = other.HostProject;
+ WorkspaceProject = other.WorkspaceProject;
+
+ // This doesn't represent a new version of the underlying data. Keep the same version.
+ Version = other.Version;
}
- public override ProjectExtensibilityConfiguration Configuration { get; }
+ public override RazorConfiguration Configuration => HostProject.Configuration;
- public override Project UnderlyingProject { get; }
+ public override string FilePath { get; }
+
+ public HostProject HostProject { get; }
+
+ public override bool IsInitialized => WorkspaceProject != null;
+
+ public override VersionStamp Version { get; }
+
+ public override Project WorkspaceProject { get; }
// This is the version that the computed state is based on.
public VersionStamp? ComputedVersion { get; set; }
// We know the project is dirty if we don't have a computed result, or it was computed for a different version.
// Since the PSM updates the snapshots synchronously, the snapshot can never be older than the computed state.
- public bool IsDirty => ComputedVersion == null || ComputedVersion.Value != UnderlyingProject.Version;
+ public bool IsDirty => ComputedVersion == null || ComputedVersion.Value != Version;
- public DefaultProjectSnapshot WithProjectChange(Project project)
+ public ProjectSnapshotUpdateContext CreateUpdateContext()
{
- if (project == null)
- {
- throw new ArgumentNullException(nameof(project));
- }
-
- return new DefaultProjectSnapshot(project, this);
+ return new ProjectSnapshotUpdateContext(FilePath, HostProject, WorkspaceProject, Version);
}
- public DefaultProjectSnapshot WithProjectChange(ProjectSnapshotUpdateContext update)
+ public DefaultProjectSnapshot WithHostProject(HostProject hostProject)
+ {
+ if (hostProject == null)
+ {
+ throw new ArgumentNullException(nameof(hostProject));
+ }
+
+ return new DefaultProjectSnapshot(hostProject, this);
+ }
+
+ public DefaultProjectSnapshot RemoveWorkspaceProject()
+ {
+ // We want to get rid of all of the computed state since it's not really valid.
+ return new DefaultProjectSnapshot(HostProject, null, Version.GetNewerVersion());
+ }
+
+ public DefaultProjectSnapshot WithWorkspaceProject(Project workspaceProject)
+ {
+ if (workspaceProject == null)
+ {
+ throw new ArgumentNullException(nameof(workspaceProject));
+ }
+
+ return new DefaultProjectSnapshot(workspaceProject, this);
+ }
+
+ public DefaultProjectSnapshot WithComputedUpdate(ProjectSnapshotUpdateContext update)
{
if (update == null)
{
@@ -95,14 +154,16 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
return new DefaultProjectSnapshot(update, this);
}
- public bool HasConfigurationChanged(ProjectSnapshot original)
+ public bool HasConfigurationChanged(DefaultProjectSnapshot original)
{
if (original == null)
{
throw new ArgumentNullException(nameof(original));
}
- return !object.Equals(Configuration, original.Configuration);
+ // We don't have any computed state right now, so treat all background updates as
+ // significant.
+ return !object.Equals(ComputedVersion, original.ComputedVersion);
}
}
-}
+}
\ No newline at end of file
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotManager.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotManager.cs
index 32c3ae4681..b8e041fe76 100644
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotManager.cs
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotManager.cs
@@ -3,10 +3,27 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
+ // The implementation of project snapshot manager abstracts over the Roslyn Project (WorkspaceProject)
+ // and information from the host's underlying project system (HostProject), to provide a unified and
+ // immutable view of the underlying project systems.
+ //
+ // The HostProject support all of the configuration that the Razor SDK exposes via the project system
+ // (language version, extensions, named configuration).
+ //
+ // The WorkspaceProject is needed to support our use of Roslyn Compilations for Tag Helpers and other
+ // C# based constructs.
+ //
+ // The implementation will create a ProjectSnapshot for each HostProject. Put another way, when we
+ // see a WorkspaceProject get created, we only care if we already have a HostProject for the same
+ // filepath.
+ //
+ // Our underlying HostProject infrastructure currently does not handle multiple TFMs (project with
+ // $(TargetFrameworks), so we just bind to the first WorkspaceProject we see for each HostProject.
internal class DefaultProjectSnapshotManager : ProjectSnapshotManagerBase
{
public override event EventHandler Changed;
@@ -17,8 +34,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
private readonly ProjectSnapshotWorkerQueue _workerQueue;
private readonly ProjectSnapshotWorker _worker;
- private readonly Dictionary _projects;
-
+ private readonly Dictionary _projects;
+
public DefaultProjectSnapshotManager(
ForegroundDispatcher foregroundDispatcher,
ErrorReporter errorReporter,
@@ -57,7 +74,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
_triggers = triggers.ToArray();
Workspace = workspace;
- _projects = new Dictionary();
+ _projects = new Dictionary(FilePathComparer.Instance);
+
_workerQueue = new ProjectSnapshotWorkerQueue(_foregroundDispatcher, this, worker);
for (var i = 0; i < _triggers.Length; i++)
@@ -70,63 +88,13 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
get
{
+ _foregroundDispatcher.AssertForegroundThread();
return _projects.Values.ToArray();
}
}
- public DefaultProjectSnapshot FindProject(ProjectId id)
- {
- if (id == null)
- {
- throw new ArgumentNullException(nameof(id));
- }
-
- _projects.TryGetValue(id, out var project);
- return project;
- }
-
public override Workspace Workspace { get; }
- public override void ProjectAdded(Project underlyingProject)
- {
- if (underlyingProject == null)
- {
- throw new ArgumentNullException(nameof(underlyingProject));
- }
-
- var snapshot = new DefaultProjectSnapshot(underlyingProject);
- _projects[underlyingProject.Id] = snapshot;
-
- // New projects always start dirty, need to compute state in the background.
- NotifyBackgroundWorker(snapshot.UnderlyingProject);
-
- // We need to notify listeners about every project add.
- NotifyListeners(new ProjectChangeEventArgs(snapshot, ProjectChangeKind.Added));
- }
-
- public override void ProjectChanged(Project underlyingProject)
- {
- if (underlyingProject == null)
- {
- throw new ArgumentNullException(nameof(underlyingProject));
- }
-
- if (_projects.TryGetValue(underlyingProject.Id, out var original))
- {
- // Doing an update to the project should keep computed values, but mark the project as dirty if the
- // underlying project is newer.
- var snapshot = original.WithProjectChange(underlyingProject);
- _projects[underlyingProject.Id] = snapshot;
-
- if (snapshot.IsDirty)
- {
- // We don't need to notify listeners yet because we don't have any **new** computed state. However we do
- // need to trigger the background work to asynchronously compute the effect of the updates.
- NotifyBackgroundWorker(snapshot.UnderlyingProject);
- }
- }
- }
-
public override void ProjectUpdated(ProjectSnapshotUpdateContext update)
{
if (update == null)
@@ -134,17 +102,25 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
throw new ArgumentNullException(nameof(update));
}
- if (_projects.TryGetValue(update.UnderlyingProject.Id, out var original))
+ _foregroundDispatcher.AssertForegroundThread();
+
+ if (_projects.TryGetValue(update.WorkspaceProject.FilePath, out var original))
{
+ if (!original.IsInitialized)
+ {
+ // If the project has been uninitialized, just ignore the update.
+ return;
+ }
+
// This is an update to the project's computed values, so everything should be overwritten
- var snapshot = original.WithProjectChange(update);
- _projects[update.UnderlyingProject.Id] = snapshot;
+ var snapshot = original.WithComputedUpdate(update);
+ _projects[update.WorkspaceProject.FilePath] = snapshot;
if (snapshot.IsDirty)
{
// It's possible that the snapshot can still be dirty if we got a project update while computing state in
// the background. We need to trigger the background work to asynchronously compute the effect of the updates.
- NotifyBackgroundWorker(snapshot.UnderlyingProject);
+ NotifyBackgroundWorker(snapshot.CreateUpdateContext());
}
// Now we need to know if the changes that we applied are significant. If that's the case then
@@ -156,58 +132,288 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
}
}
- public override void ProjectRemoved(Project underlyingProject)
+ public override void HostProjectAdded(HostProject hostProject)
{
- if (underlyingProject == null)
+ if (hostProject == null)
{
- throw new ArgumentNullException(nameof(underlyingProject));
+ throw new ArgumentNullException(nameof(hostProject));
}
-
- if (_projects.TryGetValue(underlyingProject.Id, out var snapshot))
+
+ _foregroundDispatcher.AssertForegroundThread();
+
+ // We don't expect to see a HostProject initialized multiple times for the same path. Just ignore it.
+ if (_projects.ContainsKey(hostProject.FilePath))
{
- _projects.Remove(underlyingProject.Id);
+ return;
+ }
+
+ // It's possible that Workspace has already created a project for this, but it's not deterministic
+ // So if possible find a WorkspaceProject.
+ var workspaceProject = GetWorkspaceProject(hostProject.FilePath);
+
+ var snapshot = new DefaultProjectSnapshot(hostProject, workspaceProject);
+ _projects[hostProject.FilePath] = snapshot;
+
+ if (snapshot.IsInitialized && snapshot.IsDirty)
+ {
+ // Start computing background state if the project is fully initialized.
+ NotifyBackgroundWorker(snapshot.CreateUpdateContext());
+ }
+
+ // We need to notify listeners about every project add.
+ NotifyListeners(new ProjectChangeEventArgs(snapshot, ProjectChangeKind.Added));
+ }
+
+ public override void HostProjectChanged(HostProject hostProject)
+ {
+ if (hostProject == null)
+ {
+ throw new ArgumentNullException(nameof(hostProject));
+ }
+
+ _foregroundDispatcher.AssertForegroundThread();
+
+ if (_projects.TryGetValue(hostProject.FilePath, out var original))
+ {
+ // Doing an update to the project should keep computed values, but mark the project as dirty if the
+ // underlying project is newer.
+ var snapshot = original.WithHostProject(hostProject);
+ _projects[hostProject.FilePath] = snapshot;
+
+ if (snapshot.IsInitialized && snapshot.IsDirty)
+ {
+ // Start computing background state if the project is fully initialized.
+ NotifyBackgroundWorker(snapshot.CreateUpdateContext());
+ }
+
+ // Notify listeners right away because if the HostProject changes then it's likely that the Razor
+ // configuration changed.
+ NotifyListeners(new ProjectChangeEventArgs(snapshot, ProjectChangeKind.Changed));
+ }
+ }
+
+ public override void HostProjectRemoved(HostProject hostProject)
+ {
+ if (hostProject == null)
+ {
+ throw new ArgumentNullException(nameof(hostProject));
+ }
+
+ _foregroundDispatcher.AssertForegroundThread();
+
+ if (_projects.TryGetValue(hostProject.FilePath, out var snapshot))
+ {
+ _projects.Remove(hostProject.FilePath);
// We need to notify listeners about every project removal.
NotifyListeners(new ProjectChangeEventArgs(snapshot, ProjectChangeKind.Removed));
}
}
- public override void ProjectBuildComplete(Project underlyingProject)
+ public override void WorkspaceProjectAdded(Project workspaceProject)
{
- if (underlyingProject == null)
+ if (workspaceProject == null)
{
- throw new ArgumentNullException(nameof(underlyingProject));
+ throw new ArgumentNullException(nameof(workspaceProject));
}
- if (_projects.TryGetValue(underlyingProject.Id, out var original))
+ _foregroundDispatcher.AssertForegroundThread();
+
+ if (!IsSupportedWorkspaceProject(workspaceProject))
+ {
+ return;
+ }
+
+ // The WorkspaceProject initialization never triggers a "Project Add" from out point of view, we
+ // only care if the new WorkspaceProject matches an existing HostProject.
+ if (_projects.TryGetValue(workspaceProject.FilePath, out var original))
+ {
+ // If this is a multi-targeting project then we are only interested in a single workspace project. If we already
+ // found one in the past just ignore this one.
+ if (original.WorkspaceProject == null)
+ {
+ var snapshot = original.WithWorkspaceProject(workspaceProject);
+ _projects[workspaceProject.FilePath] = snapshot;
+
+ if (snapshot.IsInitialized && snapshot.IsDirty)
+ {
+ // We don't need to notify listeners yet because we don't have any **new** computed state.
+ //
+ // However we do need to trigger the background work to asynchronously compute the effect of the updates.
+ NotifyBackgroundWorker(snapshot.CreateUpdateContext());
+ }
+
+ // Notify listeners right away since WorkspaceProject was just added, the project is now initialized.
+ NotifyListeners(new ProjectChangeEventArgs(snapshot, ProjectChangeKind.Changed));
+ }
+ }
+ }
+
+ public override void WorkspaceProjectChanged(Project workspaceProject)
+ {
+ if (workspaceProject == null)
+ {
+ throw new ArgumentNullException(nameof(workspaceProject));
+ }
+
+ _foregroundDispatcher.AssertForegroundThread();
+
+ if (!IsSupportedWorkspaceProject(workspaceProject))
+ {
+ return;
+ }
+
+ // We also need to check the projectId here. If this is a multi-targeting project then we are only interested
+ // in a single workspace project. Just use the one that showed up first.
+ if (_projects.TryGetValue(workspaceProject.FilePath, out var original) &&
+ (original.WorkspaceProject == null ||
+ original.WorkspaceProject.Id == workspaceProject.Id))
{
// Doing an update to the project should keep computed values, but mark the project as dirty if the
// underlying project is newer.
- var snapshot = original.WithProjectChange(underlyingProject);
- _projects[underlyingProject.Id] = snapshot;
+ var snapshot = original.WithWorkspaceProject(workspaceProject);
+ _projects[workspaceProject.FilePath] = snapshot;
- // Notify the background worker so it can trigger tag helper discovery.
- NotifyBackgroundWorker(underlyingProject);
+ if (snapshot.IsInitialized && snapshot.IsDirty)
+ {
+ // We don't need to notify listeners yet because we don't have any **new** computed state. However we do
+ // need to trigger the background work to asynchronously compute the effect of the updates.
+ NotifyBackgroundWorker(snapshot.CreateUpdateContext());
+ }
}
}
- public override void ProjectsCleared()
+ public override void WorkspaceProjectRemoved(Project workspaceProject)
{
- foreach (var kvp in _projects.ToArray())
+ if (workspaceProject == null)
{
- _projects.Remove(kvp.Key);
-
- // We need to notify listeners about every project removal.
- NotifyListeners(new ProjectChangeEventArgs(kvp.Value, ProjectChangeKind.Removed));
+ throw new ArgumentNullException(nameof(workspaceProject));
}
+
+ _foregroundDispatcher.AssertForegroundThread();
+
+ if (!IsSupportedWorkspaceProject(workspaceProject))
+ {
+ return;
+ }
+
+ if (_projects.TryGetValue(workspaceProject.FilePath, out var original))
+ {
+ // We also need to check the projectId here. If this is a multi-targeting project then we are only interested
+ // in a single workspace project. Make sure the WorkspaceProject we're using is the one that's being removed.
+ if (original.WorkspaceProject?.Id != workspaceProject.Id)
+ {
+ return;
+ }
+
+
+ DefaultProjectSnapshot snapshot;
+
+ // So if the WorkspaceProject got removed, we should double check to make sure that there aren't others
+ // hanging around. This could happen if a project is multi-targeting and one of the TFMs is removed.
+ var otherWorkspaceProject = GetWorkspaceProject(workspaceProject.FilePath);
+ if (otherWorkspaceProject != null && otherWorkspaceProject.Id != workspaceProject.Id)
+ {
+ // OK there's another WorkspaceProject, use that.
+ //
+ // Doing an update to the project should keep computed values, but mark the project as dirty if the
+ // underlying project is newer.
+ snapshot = original.WithWorkspaceProject(otherWorkspaceProject);
+ _projects[workspaceProject.FilePath] = snapshot;
+
+ if (snapshot.IsInitialized && snapshot.IsDirty)
+ {
+ // We don't need to notify listeners yet because we don't have any **new** computed state. However we do
+ // need to trigger the background work to asynchronously compute the effect of the updates.
+ NotifyBackgroundWorker(snapshot.CreateUpdateContext());
+ }
+
+ // Notify listeners of a change because it's a different WorkspaceProject.
+ NotifyListeners(new ProjectChangeEventArgs(snapshot, ProjectChangeKind.Changed));
+
+ return;
+ }
+
+ snapshot = original.RemoveWorkspaceProject();
+ _projects[workspaceProject.FilePath] = snapshot;
+
+ // Notify listeners of a change because we've removed computed state.
+ NotifyListeners(new ProjectChangeEventArgs(snapshot, ProjectChangeKind.Changed));
+ }
+ }
+
+ public override void ReportError(Exception exception)
+ {
+ if (exception == null)
+ {
+ throw new ArgumentNullException(nameof(exception));
+ }
+
+ _errorReporter.ReportError(exception);
+ }
+
+ public override void ReportError(Exception exception, ProjectSnapshot project)
+ {
+ if (exception == null)
+ {
+ throw new ArgumentNullException(nameof(exception));
+ }
+
+ _errorReporter.ReportError(exception, project);
+ }
+
+ public override void ReportError(Exception exception, HostProject hostProject)
+ {
+ if (exception == null)
+ {
+ throw new ArgumentNullException(nameof(exception));
+ }
+
+ var project = hostProject?.FilePath == null ? null : this.GetProjectWithFilePath(hostProject.FilePath);
+ _errorReporter.ReportError(exception, project);
+ }
+
+ public override void ReportError(Exception exception, Project workspaceProject)
+ {
+ if (exception == null)
+ {
+ throw new ArgumentNullException(nameof(exception));
+ }
+
+ _errorReporter.ReportError(exception, workspaceProject);
+ }
+
+ // We're only interested in CSharp projects that have a FilePath. We rely on the FilePath to
+ // unify the Workspace Project with our HostProject concept.
+ private bool IsSupportedWorkspaceProject(Project workspaceProject) => workspaceProject.Language == LanguageNames.CSharp && workspaceProject.FilePath != null;
+
+ private Project GetWorkspaceProject(string filePath)
+ {
+ var solution = Workspace.CurrentSolution;
+ if (solution == null)
+ {
+ return null;
+ }
+
+ foreach (var workspaceProject in solution.Projects)
+ {
+ if (IsSupportedWorkspaceProject(workspaceProject) &&
+ FilePathComparer.Instance.Equals(filePath, workspaceProject.FilePath))
+ {
+ // We don't try to handle mulitple TFMs anwhere in Razor, just take the first WorkspaceProject that is a match.
+ return workspaceProject;
+ }
+ }
+
+ return null;
}
// virtual so it can be overridden in tests
- protected virtual void NotifyBackgroundWorker(Project project)
+ protected virtual void NotifyBackgroundWorker(ProjectSnapshotUpdateContext context)
{
_foregroundDispatcher.AssertForegroundThread();
-
- _workerQueue.Enqueue(project);
+
+ _workerQueue.Enqueue(context);
}
// virtual so it can be overridden in tests
@@ -221,15 +427,5 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
handler(this, e);
}
}
-
- public override void ReportError(Exception exception)
- {
- _errorReporter.ReportError(exception);
- }
-
- public override void ReportError(Exception exception, Project project)
- {
- _errorReporter.ReportError(exception, project);
- }
}
-}
+}
\ No newline at end of file
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotWorker.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotWorker.cs
index 8a40c50f40..fea034a6b4 100644
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotWorker.cs
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotWorker.cs
@@ -9,25 +9,16 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
internal class DefaultProjectSnapshotWorker : ProjectSnapshotWorker
{
- private readonly ProjectExtensibilityConfigurationFactory _configurationFactory;
private readonly ForegroundDispatcher _foregroundDispatcher;
- public DefaultProjectSnapshotWorker(
- ForegroundDispatcher foregroundDispatcher,
- ProjectExtensibilityConfigurationFactory configurationFactory)
+ public DefaultProjectSnapshotWorker(ForegroundDispatcher foregroundDispatcher)
{
if (foregroundDispatcher == null)
{
throw new ArgumentNullException(nameof(foregroundDispatcher));
}
- if (configurationFactory == null)
- {
- throw new ArgumentNullException(nameof(configurationFactory));
- }
-
_foregroundDispatcher = foregroundDispatcher;
- _configurationFactory = configurationFactory;
}
public override Task ProcessUpdateAsync(ProjectSnapshotUpdateContext update, CancellationToken cancellationToken = default(CancellationToken))
@@ -46,14 +37,15 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
return ProjectUpdatesCoreAsync(update);
}
- private async Task ProjectUpdatesCoreAsync(object state)
+ protected virtual void OnProcessingUpdate()
{
- var update = (ProjectSnapshotUpdateContext)state;
+ }
- // We'll have more things to process here, but for now we're just hardcoding the configuration.
+ private Task ProjectUpdatesCoreAsync(object state)
+ {
+ OnProcessingUpdate();
- var configuration = await _configurationFactory.GetConfigurationAsync(update.UnderlyingProject);
- update.Configuration = configuration;
+ return Task.CompletedTask;
}
}
}
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotWorkerFactory.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotWorkerFactory.cs
index 321d7dfa7a..91317bf29d 100644
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotWorkerFactory.cs
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotWorkerFactory.cs
@@ -26,9 +26,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
public ILanguageService CreateLanguageService(HostLanguageServices languageServices)
{
- return new DefaultProjectSnapshotWorker(
- _foregroundDispatcher,
- languageServices.GetRequiredService());
+ return new DefaultProjectSnapshotWorker(_foregroundDispatcher);
}
}
}
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/FallbackRazorConfiguration.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/FallbackRazorConfiguration.cs
new file mode 100644
index 0000000000..707125158a
--- /dev/null
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/FallbackRazorConfiguration.cs
@@ -0,0 +1,78 @@
+// 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 Microsoft.AspNetCore.Razor.Language;
+
+namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
+{
+ internal class FallbackRazorConfiguration : RazorConfiguration
+ {
+ public static readonly RazorConfiguration MVC_1_0 = new FallbackRazorConfiguration(
+ RazorLanguageVersion.Version_1_0,
+ "MVC-1.0",
+ new[] { new FallbackRazorExtension("MVC-1.0"), });
+
+ public static readonly RazorConfiguration MVC_1_1 = new FallbackRazorConfiguration(
+ RazorLanguageVersion.Version_1_1,
+ "MVC-1.1",
+ new[] { new FallbackRazorExtension("MVC-1.1"), });
+
+ public static readonly RazorConfiguration MVC_2_0 = new FallbackRazorConfiguration(
+ RazorLanguageVersion.Version_2_0,
+ "MVC-2.0",
+ new[] { new FallbackRazorExtension("MVC-2.0"), });
+
+ public static RazorConfiguration SelectConfiguration(Version version)
+ {
+ if (version.Major == 1 && version.Minor == 0)
+ {
+ return MVC_1_0;
+ }
+ else if (version.Major == 1 && version.Minor == 1)
+ {
+ return MVC_1_1;
+ }
+ else if (version.Major == 2 && version.Minor == 0)
+ {
+ return MVC_2_0;
+ }
+ else
+ {
+ return MVC_2_0;
+ }
+ }
+
+ public FallbackRazorConfiguration(
+ RazorLanguageVersion languageVersion,
+ string configurationName,
+ RazorExtension[] extensions)
+ {
+ if (languageVersion == null)
+ {
+ throw new ArgumentNullException(nameof(languageVersion));
+ }
+
+ if (configurationName == null)
+ {
+ throw new ArgumentNullException(nameof(configurationName));
+ }
+
+ if (extensions == null)
+ {
+ throw new ArgumentNullException(nameof(extensions));
+ }
+
+ LanguageVersion = languageVersion;
+ ConfigurationName = configurationName;
+ Extensions = extensions;
+ }
+
+ public override string ConfigurationName { get; }
+
+ public override IReadOnlyList Extensions { get; }
+
+ public override RazorLanguageVersion LanguageVersion { get; }
+ }
+}
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/FallbackRazorExtension.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/FallbackRazorExtension.cs
new file mode 100644
index 0000000000..5080b0705d
--- /dev/null
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/FallbackRazorExtension.cs
@@ -0,0 +1,23 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.Razor.Language;
+
+namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
+{
+ internal class FallbackRazorExtension : RazorExtension
+ {
+ public FallbackRazorExtension(string extensionName)
+ {
+ if (extensionName == null)
+ {
+ throw new ArgumentNullException(nameof(extensionName));
+ }
+
+ ExtensionName = extensionName;
+ }
+
+ public override string ExtensionName { get; }
+ }
+}
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/HostProject.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/HostProject.cs
new file mode 100644
index 0000000000..cf3c1524b6
--- /dev/null
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/HostProject.cs
@@ -0,0 +1,31 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.Razor.Language;
+
+namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
+{
+ internal class HostProject
+ {
+ public HostProject(string projectFilePath, RazorConfiguration razorConfiguration)
+ {
+ if (projectFilePath == null)
+ {
+ throw new ArgumentNullException(nameof(projectFilePath));
+ }
+
+ if (razorConfiguration == null)
+ {
+ throw new ArgumentNullException(nameof(razorConfiguration));
+ }
+
+ FilePath = projectFilePath;
+ Configuration = razorConfiguration;
+ }
+
+ public RazorConfiguration Configuration { get; }
+
+ public string FilePath { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/MvcExtensibilityConfiguration.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/MvcExtensibilityConfiguration.cs
deleted file mode 100644
index dfe2b88904..0000000000
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/MvcExtensibilityConfiguration.cs
+++ /dev/null
@@ -1,86 +0,0 @@
-// 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 Microsoft.AspNetCore.Razor.Language;
-using Microsoft.Extensions.Internal;
-
-namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
-{
- internal class MvcExtensibilityConfiguration : ProjectExtensibilityConfiguration
- {
- public MvcExtensibilityConfiguration(
- RazorLanguageVersion languageVersion,
- ProjectExtensibilityConfigurationKind kind,
- ProjectExtensibilityAssembly razorAssembly,
- ProjectExtensibilityAssembly mvcAssembly)
- {
- if (razorAssembly == null)
- {
- throw new ArgumentNullException(nameof(razorAssembly));
- }
-
- if (mvcAssembly == null)
- {
- throw new ArgumentNullException(nameof(mvcAssembly));
- }
-
- Kind = kind;
- RazorAssembly = razorAssembly;
- MvcAssembly = mvcAssembly;
- LanguageVersion = languageVersion;
-
- Assemblies = new[] { RazorAssembly, MvcAssembly, };
- }
-
- public override IReadOnlyList Assemblies { get; }
-
- // MVC: '2.0.0' (fallback) | Razor Language '2.0.0'
- // or
- // MVC: '2.1.3' | Razor Language '2.1.3'
- public override string DisplayName => $"MVC: {MvcAssembly.Identity.Version.ToString(3)}" + (Kind == ProjectExtensibilityConfigurationKind.Fallback? " (fallback)" : string.Empty) + " | " + LanguageVersion;
-
- public override ProjectExtensibilityConfigurationKind Kind { get; }
-
- public override ProjectExtensibilityAssembly RazorAssembly { get; }
-
- public override RazorLanguageVersion LanguageVersion { get; }
-
- public ProjectExtensibilityAssembly MvcAssembly { get; }
-
- public override bool Equals(ProjectExtensibilityConfiguration other)
- {
- if (other == null)
- {
- return false;
- }
-
- // We're intentionally ignoring the 'Kind' here. That's mostly for diagnostics and doesn't influence any behavior.
- return LanguageVersion == other.LanguageVersion &&
- Enumerable.SequenceEqual(
- Assemblies.OrderBy(a => a.Identity.Name).Select(a => a.Identity),
- other.Assemblies.OrderBy(a => a.Identity.Name).Select(a => a.Identity),
- AssemblyIdentityEqualityComparer.NameAndVersion);
- }
-
- public override int GetHashCode()
- {
- var hash = new HashCodeCombiner();
- foreach (var assembly in Assemblies.OrderBy(a => a.Identity.Name))
- {
- hash.Add(assembly);
- }
-
- hash.Add(LanguageVersion);
-
- return hash;
- }
-
- public override string ToString()
- {
- return DisplayName;
- }
- }
-}
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectExtensibilityConfiguration.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectExtensibilityConfiguration.cs
deleted file mode 100644
index a868c4781d..0000000000
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectExtensibilityConfiguration.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-// 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 Microsoft.AspNetCore.Razor.Language;
-
-namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
-{
- internal abstract class ProjectExtensibilityConfiguration : IEquatable
- {
- public abstract IReadOnlyList Assemblies { get; }
-
- public abstract string DisplayName { get; }
-
- public abstract ProjectExtensibilityConfigurationKind Kind { get; }
-
- public abstract ProjectExtensibilityAssembly RazorAssembly { get; }
-
- public abstract RazorLanguageVersion LanguageVersion { get; }
-
- public abstract bool Equals(ProjectExtensibilityConfiguration other);
-
- public abstract override int GetHashCode();
-
- public override bool Equals(object obj)
- {
- return Equals(obj as ProjectExtensibilityConfiguration);
- }
- }
-}
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectExtensibilityConfigurationFactory.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectExtensibilityConfigurationFactory.cs
deleted file mode 100644
index 71d929dd29..0000000000
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectExtensibilityConfigurationFactory.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-// 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.Threading;
-using System.Threading.Tasks;
-using Microsoft.CodeAnalysis.Host;
-
-namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
-{
- internal abstract class ProjectExtensibilityConfigurationFactory : ILanguageService
- {
- public abstract Task GetConfigurationAsync(Project project, CancellationToken cancellationToken = default(CancellationToken));
- }
-}
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectExtensibilityConfigurationKind.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectExtensibilityConfigurationKind.cs
deleted file mode 100644
index 0efc5e5e37..0000000000
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectExtensibilityConfigurationKind.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-// 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.
-
-namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
-{
- ///
- /// Describes how closely the configuration of Razor tooling matches the actual project dependencies.
- ///
- internal enum ProjectExtensibilityConfigurationKind
- {
- ApproximateMatch,
- Fallback,
- }
-}
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshot.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshot.cs
index f28e477d5d..b32a917ecd 100644
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshot.cs
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshot.cs
@@ -1,15 +1,20 @@
// 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;
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
internal abstract class ProjectSnapshot
{
- public abstract ProjectExtensibilityConfiguration Configuration { get; }
+ public abstract RazorConfiguration Configuration { get; }
- public abstract Project UnderlyingProject { get; }
+ public abstract string FilePath { get; }
+
+ public abstract bool IsInitialized { get; }
+
+ public abstract VersionStamp Version { get; }
+
+ public abstract Project WorkspaceProject { get; }
}
-}
+}
\ No newline at end of file
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotManagerBase.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotManagerBase.cs
index 4dd392076b..b156868fa0 100644
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotManagerBase.cs
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotManagerBase.cs
@@ -9,20 +9,26 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
public abstract Workspace Workspace { get; }
- public abstract void ProjectAdded(Project underlyingProject);
-
- public abstract void ProjectChanged(Project underlyingProject);
-
public abstract void ProjectUpdated(ProjectSnapshotUpdateContext update);
- public abstract void ProjectRemoved(Project underlyingProject);
+ public abstract void HostProjectAdded(HostProject hostProject);
- public abstract void ProjectBuildComplete(Project underlyingProject);
+ public abstract void HostProjectChanged(HostProject hostProject);
- public abstract void ProjectsCleared();
+ public abstract void HostProjectRemoved(HostProject hostProject);
+
+ public abstract void WorkspaceProjectAdded(Project workspaceProject);
+
+ public abstract void WorkspaceProjectChanged(Project workspaceProject);
+
+ public abstract void WorkspaceProjectRemoved(Project workspaceProject);
public abstract void ReportError(Exception exception);
+
+ public abstract void ReportError(Exception exception, ProjectSnapshot project);
- public abstract void ReportError(Exception exception, Project project);
+ public abstract void ReportError(Exception exception, HostProject hostProject);
+
+ public abstract void ReportError(Exception exception, Project workspaceProject);
}
-}
+}
\ No newline at end of file
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotManagerExtensions.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotManagerExtensions.cs
index c926021eb0..579a42ffcb 100644
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotManagerExtensions.cs
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotManagerExtensions.cs
@@ -13,7 +13,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
for (var i = 0; i< projects.Count; i++)
{
var project = projects[i];
- if (string.Equals(filePath, project.UnderlyingProject.FilePath, StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(filePath, project.WorkspaceProject.FilePath, StringComparison.OrdinalIgnoreCase))
{
return project;
}
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotUpdateContext.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotUpdateContext.cs
index 83357e9968..23cc5066da 100644
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotUpdateContext.cs
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotUpdateContext.cs
@@ -9,18 +9,35 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
internal class ProjectSnapshotUpdateContext
{
- public ProjectSnapshotUpdateContext(Project underlyingProject)
+ public ProjectSnapshotUpdateContext(string filePath, HostProject hostProject, Project workspaceProject, VersionStamp version)
{
- if (underlyingProject == null)
+ if (filePath == null)
{
- throw new ArgumentNullException(nameof(underlyingProject));
+ throw new ArgumentNullException(nameof(filePath));
}
- UnderlyingProject = underlyingProject;
+ if (hostProject == null)
+ {
+ throw new ArgumentNullException(nameof(hostProject));
+ }
+
+ if (workspaceProject == null)
+ {
+ throw new ArgumentNullException(nameof(workspaceProject));
+ }
+
+ FilePath = filePath;
+ HostProject = hostProject;
+ WorkspaceProject = workspaceProject;
+ Version = version;
}
- public Project UnderlyingProject { get; }
+ public string FilePath { get; }
- public ProjectExtensibilityConfiguration Configuration { get; set; }
+ public HostProject HostProject { get; }
+
+ public Project WorkspaceProject { get; }
+
+ public VersionStamp Version { get; }
}
}
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotWorkerQueue.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotWorkerQueue.cs
index 0dcbe91284..69c0062f05 100644
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotWorkerQueue.cs
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotWorkerQueue.cs
@@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -16,7 +15,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
private readonly DefaultProjectSnapshotManager _projectManager;
private readonly ProjectSnapshotWorker _projectWorker;
- private readonly Dictionary _projects;
+ private readonly Dictionary _projects;
private Timer _timer;
public ProjectSnapshotWorkerQueue(ForegroundDispatcher foregroundDispatcher, DefaultProjectSnapshotManager projectManager, ProjectSnapshotWorker projectWorker)
@@ -40,7 +39,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
_projectManager = projectManager;
_projectWorker = projectWorker;
- _projects = new Dictionary();
+ _projects = new Dictionary(FilePathComparer.Instance);
}
public bool HasPendingNotifications
@@ -93,11 +92,11 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
}
}
- public void Enqueue(Project project)
+ public void Enqueue(ProjectSnapshotUpdateContext context)
{
- if (project == null)
+ if (context == null)
{
- throw new ArgumentNullException();
+ throw new ArgumentNullException(nameof(context));
}
_foregroundDispatcher.AssertForegroundThread();
@@ -106,7 +105,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
// We only want to store the last 'seen' version of any given project. That way when we pick one to process
// it's always the best version to use.
- _projects[project.Id] = project;
+ _projects[context.FilePath] = context;
StartWorker();
}
@@ -133,7 +132,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
OnStartingBackgroundWork();
- Project[] work;
+ ProjectSnapshotUpdateContext[] work;
lock (_projects)
{
work = _projects.Values.ToArray();
@@ -145,7 +144,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
try
{
- updates[i] = (new ProjectSnapshotUpdateContext(work[i]), null);
+ updates[i] = (work[i], null);
await _projectWorker.ProcessUpdateAsync(updates[i].context);
}
catch (Exception projectException)
@@ -196,7 +195,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
}
else
{
- _projectManager.ReportError(update.exception, update.context?.UnderlyingProject);
+ _projectManager.ReportError(update.exception, update.context?.WorkspaceProject);
}
}
}
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSystemRazorConfiguration.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSystemRazorConfiguration.cs
new file mode 100644
index 0000000000..43dfebe864
--- /dev/null
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSystemRazorConfiguration.cs
@@ -0,0 +1,43 @@
+// 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 Microsoft.AspNetCore.Razor.Language;
+
+namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
+{
+ internal class ProjectSystemRazorConfiguration : RazorConfiguration
+ {
+ public ProjectSystemRazorConfiguration(
+ RazorLanguageVersion languageVersion,
+ string configurationName,
+ RazorExtension[] extensions)
+ {
+ if (languageVersion == null)
+ {
+ throw new ArgumentNullException(nameof(languageVersion));
+ }
+
+ if (configurationName == null)
+ {
+ throw new ArgumentNullException(nameof(configurationName));
+ }
+
+ if (extensions == null)
+ {
+ throw new ArgumentNullException(nameof(extensions));
+ }
+
+ LanguageVersion = languageVersion;
+ ConfigurationName = configurationName;
+ Extensions = extensions;
+ }
+
+ public override string ConfigurationName { get; }
+
+ public override IReadOnlyList Extensions { get; }
+
+ public override RazorLanguageVersion LanguageVersion { get; }
+ }
+}
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSystemRazorExtension.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSystemRazorExtension.cs
new file mode 100644
index 0000000000..77f742c563
--- /dev/null
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSystemRazorExtension.cs
@@ -0,0 +1,23 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.Razor.Language;
+
+namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
+{
+ internal class ProjectSystemRazorExtension : RazorExtension
+ {
+ public ProjectSystemRazorExtension(string extensionName)
+ {
+ if (extensionName == null)
+ {
+ throw new ArgumentNullException(nameof(extensionName));
+ }
+
+ ExtensionName = extensionName;
+ }
+
+ public override string ExtensionName { get; }
+ }
+}
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/WorkspaceProjectSnapshotChangeTrigger.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/WorkspaceProjectSnapshotChangeTrigger.cs
index 1769da0197..fb2deeec32 100644
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/WorkspaceProjectSnapshotChangeTrigger.cs
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/WorkspaceProjectSnapshotChangeTrigger.cs
@@ -23,51 +23,43 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
Debug.Assert(solution != null);
- _projectManager.ProjectsCleared();
-
foreach (var project in solution.Projects)
{
- if (project.Language == LanguageNames.CSharp)
- {
- _projectManager.ProjectAdded(project);
- }
+ _projectManager.WorkspaceProjectAdded(project);
}
}
// Internal for testing
internal void Workspace_WorkspaceChanged(object sender, WorkspaceChangeEventArgs e)
{
- Project underlyingProject;
+ Project project;
switch (e.Kind)
{
case WorkspaceChangeKind.ProjectAdded:
{
- underlyingProject = e.NewSolution.GetProject(e.ProjectId);
- Debug.Assert(underlyingProject != null);
+ project = e.NewSolution.GetProject(e.ProjectId);
+ Debug.Assert(project != null);
- if (underlyingProject.Language == LanguageNames.CSharp)
- {
- _projectManager.ProjectAdded(underlyingProject);
- }
+ _projectManager.WorkspaceProjectAdded(project);
break;
}
case WorkspaceChangeKind.ProjectChanged:
case WorkspaceChangeKind.ProjectReloaded:
{
- underlyingProject = e.NewSolution.GetProject(e.ProjectId);
- Debug.Assert(underlyingProject != null);
+ project = e.NewSolution.GetProject(e.ProjectId);
+ Debug.Assert(project != null);
- _projectManager.ProjectChanged(underlyingProject);
+ _projectManager.WorkspaceProjectChanged(project);
break;
}
case WorkspaceChangeKind.ProjectRemoved:
{
- underlyingProject = e.OldSolution.GetProject(e.ProjectId);
- Debug.Assert(underlyingProject != null);
+ project = e.OldSolution.GetProject(e.ProjectId);
+ Debug.Assert(project != null);
- _projectManager.ProjectRemoved(underlyingProject);
+ _projectManager.WorkspaceProjectRemoved(project);
break;
}
@@ -76,6 +68,15 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
case WorkspaceChangeKind.SolutionCleared:
case WorkspaceChangeKind.SolutionReloaded:
case WorkspaceChangeKind.SolutionRemoved:
+
+ if (e.OldSolution != null)
+ {
+ foreach (var p in e.OldSolution.Projects)
+ {
+ _projectManager.WorkspaceProjectRemoved(p);
+ }
+ }
+
InitializeSolution(e.NewSolution);
break;
}
diff --git a/src/Microsoft.CodeAnalysis.Razor/FilePathComparer.cs b/src/Microsoft.CodeAnalysis.Razor/FilePathComparer.cs
new file mode 100644
index 0000000000..a0ca3cb9a3
--- /dev/null
+++ b/src/Microsoft.CodeAnalysis.Razor/FilePathComparer.cs
@@ -0,0 +1,30 @@
+// 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.Runtime.InteropServices;
+
+namespace Microsoft.CodeAnalysis.Razor
+{
+ internal static class FilePathComparer
+ {
+ private static StringComparer _instance;
+
+ public static StringComparer Instance
+ {
+ get
+ {
+ if (_instance == null && RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ _instance = StringComparer.Ordinal;
+ }
+ else if (_instance == null)
+ {
+ _instance = StringComparer.OrdinalIgnoreCase;
+ }
+
+ return _instance;
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.VisualStudio.Editor.Razor/DefaultProjectEngineFactoryService.cs b/src/Microsoft.VisualStudio.Editor.Razor/DefaultProjectEngineFactoryService.cs
index f97abf0a3b..4f949d7ecd 100644
--- a/src/Microsoft.VisualStudio.Editor.Razor/DefaultProjectEngineFactoryService.cs
+++ b/src/Microsoft.VisualStudio.Editor.Razor/DefaultProjectEngineFactoryService.cs
@@ -4,7 +4,6 @@
using System;
using System.IO;
using Microsoft.AspNetCore.Razor.Language;
-using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Razor;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using Mvc1_X = Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X;
@@ -14,11 +13,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
{
internal class DefaultProjectEngineFactoryService : RazorProjectEngineFactoryService
{
- private readonly static MvcExtensibilityConfiguration DefaultConfiguration = new MvcExtensibilityConfiguration(
- RazorLanguageVersion.Version_2_0,
- ProjectExtensibilityConfigurationKind.Fallback,
- new ProjectExtensibilityAssembly(new AssemblyIdentity("Microsoft.AspNetCore.Razor.Language", new Version("2.0.0.0"))),
- new ProjectExtensibilityAssembly(new AssemblyIdentity("Microsoft.AspNetCore.Mvc.Razor", new Version("2.0.0.0"))));
+ private readonly static RazorConfiguration DefaultConfiguration = FallbackRazorConfiguration.MVC_2_0;
private readonly ProjectSnapshotManager _projectManager;
@@ -41,22 +36,19 @@ namespace Microsoft.VisualStudio.Editor.Razor
// In 15.5 we expect projectPath to be a directory, NOT the path to the csproj.
var project = FindProject(projectPath);
- var configuration = (project?.Configuration as MvcExtensibilityConfiguration) ?? DefaultConfiguration;
- var razorLanguageVersion = configuration.LanguageVersion;
-
- var razorConfiguration = new RazorConfiguration(razorLanguageVersion, "unnamed", Array.Empty());
+ var configuration = project?.Configuration ?? DefaultConfiguration;
var fileSystem = RazorProjectFileSystem.Create(projectPath);
RazorProjectEngine projectEngine;
- if (razorLanguageVersion.Major == 1)
+ if (configuration.LanguageVersion.Major == 1)
{
- projectEngine = RazorProjectEngine.Create(razorConfiguration, fileSystem, b =>
+ projectEngine = RazorProjectEngine.Create(configuration, fileSystem, b =>
{
configure?.Invoke(b);
Mvc1_X.RazorExtensions.Register(b);
- if (configuration.MvcAssembly.Identity.Version.Minor >= 1)
+ if (configuration.LanguageVersion.Minor >= 1)
{
Mvc1_X.RazorExtensions.RegisterViewComponentTagHelpers(b);
}
@@ -64,7 +56,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
}
else
{
- projectEngine = RazorProjectEngine.Create(razorConfiguration, fileSystem, b =>
+ projectEngine = RazorProjectEngine.Create(configuration, fileSystem, b =>
{
configure?.Invoke(b);
@@ -83,9 +75,9 @@ namespace Microsoft.VisualStudio.Editor.Razor
for (var i = 0; i < projects.Count; i++)
{
var project = projects[i];
- if (project.UnderlyingProject.FilePath != null)
+ if (project.WorkspaceProject?.FilePath != null)
{
- if (string.Equals(directory, NormalizeDirectoryPath(Path.GetDirectoryName(project.UnderlyingProject.FilePath)), StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(directory, NormalizeDirectoryPath(Path.GetDirectoryName(project.WorkspaceProject.FilePath)), StringComparison.OrdinalIgnoreCase))
{
return project;
}
diff --git a/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioDocumentTracker.cs b/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioDocumentTracker.cs
index eb771205d1..f55ee5f70e 100644
--- a/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioDocumentTracker.cs
+++ b/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioDocumentTracker.cs
@@ -77,7 +77,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
_textViews = new List();
}
- internal override ProjectExtensibilityConfiguration Configuration => _project.Configuration;
+ public override RazorConfiguration Configuration => _project.Configuration;
public override EditorSettings EditorSettings => _editorSettingsManager.Current;
@@ -85,7 +85,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
public override bool IsSupportedProject => _isSupportedProject;
- public override Project Project => _workspace.CurrentSolution.GetProject(_project.UnderlyingProject.Id);
+ public override Project Project => _workspace.CurrentSolution.GetProject(_project.WorkspaceProject.Id);
public override ITextBuffer TextBuffer => _textBuffer;
@@ -206,7 +206,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
private void ProjectManager_Changed(object sender, ProjectChangeEventArgs e)
{
if (_projectPath != null &&
- string.Equals(_projectPath, e.Project.UnderlyingProject.FilePath, StringComparison.OrdinalIgnoreCase))
+ string.Equals(_projectPath, e.Project.FilePath, StringComparison.OrdinalIgnoreCase))
{
if (e.Kind == ProjectChangeKind.TagHelpersChanged)
{
diff --git a/src/Microsoft.VisualStudio.Editor.Razor/VisualStudioDocumentTracker.cs b/src/Microsoft.VisualStudio.Editor.Razor/VisualStudioDocumentTracker.cs
index efd1016bd1..7cde4da716 100644
--- a/src/Microsoft.VisualStudio.Editor.Razor/VisualStudioDocumentTracker.cs
+++ b/src/Microsoft.VisualStudio.Editor.Razor/VisualStudioDocumentTracker.cs
@@ -6,7 +6,6 @@ using System.Collections.Generic;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Razor.Editor;
-using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
@@ -16,7 +15,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
{
public abstract event EventHandler ContextChanged;
- internal abstract ProjectExtensibilityConfiguration Configuration { get; }
+ public abstract RazorConfiguration Configuration { get; }
public abstract EditorSettings EditorSettings { get; }
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/Microsoft.VisualStudio.LanguageServices.Razor.csproj b/src/Microsoft.VisualStudio.LanguageServices.Razor/Microsoft.VisualStudio.LanguageServices.Razor.csproj
index 5f058e97e1..d2dcdd7ecc 100644
--- a/src/Microsoft.VisualStudio.LanguageServices.Razor/Microsoft.VisualStudio.LanguageServices.Razor.csproj
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/Microsoft.VisualStudio.LanguageServices.Razor.csproj
@@ -11,6 +11,8 @@
+
+
@@ -26,7 +28,7 @@
-
+
@@ -35,4 +37,82 @@
+
+
+
+
+
+
+
+
+
+
+
+ RazorConfiguration.xaml
+
+
+ RazorExtension.xaml
+
+
+ RazorGeneral.xaml
+
+
+ XamlRuleToCode:RazorConfiguration.xaml
+
+
+ XamlRuleToCode:RazorExtension.xaml
+
+
+ XamlRuleToCode:RazorGeneral.xaml
+
+
+
+
+ Designer
+ MSBuild:GenerateRuleSourceFromXaml
+ Microsoft.CodeAnalysis.Razor.ProjectSystem.Rules
+ RazorProjectProperties
+
+ ProjectSystem\Rules\
+
+
+ Designer
+ MSBuild:GenerateRuleSourceFromXaml
+ Microsoft.CodeAnalysis.Razor.ProjectSystem.Rules
+ RazorProjectProperties
+ ProjectSystem\Rules\
+
+
+ Designer
+ MSBuild:GenerateRuleSourceFromXaml
+ Microsoft.CodeAnalysis.Razor.ProjectSystem.Rules
+ RazorProjectProperties
+ ProjectSystem\Rules\
+
+
+ RazorGeneral.xaml
+
+
+ RazorGeneral.xaml
+
+
+ RazorGeneral.xaml
+
+
+
+
+
+
+
+
+
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/DefaultRazorProjectHost.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/DefaultRazorProjectHost.cs
new file mode 100644
index 0000000000..f68ae2cd11
--- /dev/null
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/DefaultRazorProjectHost.cs
@@ -0,0 +1,125 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.ComponentModel.Composition;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Threading.Tasks.Dataflow;
+using Microsoft.AspNetCore.Razor.Language;
+using Microsoft.VisualStudio.LanguageServices;
+using Microsoft.VisualStudio.ProjectSystem;
+
+namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
+{
+ // Somewhat similar to https://github.com/dotnet/project-system/blob/fa074d228dcff6dae9e48ce43dd4a3a5aa22e8f0/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/LanguageServices/LanguageServiceHost.cs
+ //
+ // This class is responsible for intializing the Razor ProjectSnapshotManager for cases where
+ // MSBuild provides configuration support (>= 2.1).
+ [AppliesTo("DotNetCoreRazor & DotNetCoreRazorConfiguration")]
+ [Export(ExportContractNames.Scopes.UnconfiguredProject, typeof(IProjectDynamicLoadComponent))]
+ internal class DefaultRazorProjectHost : RazorProjectHostBase
+ {
+ private IDisposable _subscription;
+
+ [ImportingConstructor]
+ public DefaultRazorProjectHost(
+ IUnconfiguredProjectCommonServices commonServices,
+ [Import(typeof(VisualStudioWorkspace))] Workspace workspace)
+ : base(commonServices, workspace)
+ {
+ }
+
+ // Internal for testing
+ internal DefaultRazorProjectHost(
+ IUnconfiguredProjectCommonServices commonServices,
+ Workspace workspace,
+ ProjectSnapshotManagerBase projectManager)
+ : base(commonServices, workspace, projectManager)
+ {
+ }
+
+ protected override async Task InitializeCoreAsync(CancellationToken cancellationToken)
+ {
+ await base.InitializeCoreAsync(cancellationToken).ConfigureAwait(false);
+
+ // Don't try to evaluate any properties here since the project is still loading and we require access
+ // to the UI thread to push our updates.
+ //
+ // Just subscribe and handle the notification later.
+ // Don't try to evaluate any properties here since the project is still loading and we require access
+ // to the UI thread to push our updates.
+ //
+ // Just subscribe and handle the notification later.
+ var receiver = new ActionBlock>(OnProjectChanged);
+ _subscription = CommonServices.ActiveConfiguredProjectSubscription.JointRuleSource.SourceBlock.LinkTo(
+ receiver,
+ initialDataAsNew: true,
+ suppressVersionOnlyUpdates: true,
+ ruleNames: new string[] { Rules.RazorGeneral.SchemaName, Rules.RazorConfiguration.SchemaName, Rules.RazorExtension.SchemaName });
+ }
+
+ protected override async Task DisposeCoreAsync(bool initialized)
+ {
+ await base.DisposeCoreAsync(initialized).ConfigureAwait(false);
+
+ if (initialized)
+ {
+ _subscription.Dispose();
+ }
+ }
+
+ // Internal for testing
+ internal async Task OnProjectChanged(IProjectVersionedValue update)
+ {
+ await ExecuteWithLock(async () =>
+ {
+ if (IsDisposing || IsDisposed)
+ {
+ return;
+ }
+
+ var languageVersion = update.Value.CurrentState[Rules.RazorGeneral.SchemaName].Properties[Rules.RazorGeneral.RazorLangVersionProperty];
+ var defaultConfiguration = update.Value.CurrentState[Rules.RazorGeneral.SchemaName].Properties[Rules.RazorGeneral.RazorDefaultConfigurationProperty];
+
+ RazorConfiguration configuration = null;
+ if (!string.IsNullOrEmpty(languageVersion) && !string.IsNullOrEmpty(defaultConfiguration))
+ {
+ if (!RazorLanguageVersion.TryParse(languageVersion, out var parsedVersion))
+ {
+ parsedVersion = RazorLanguageVersion.Latest;
+ }
+
+ var extensions = update.Value.CurrentState[Rules.RazorExtension.PrimaryDataSourceItemType].Items.Select(e =>
+ {
+ return new ProjectSystemRazorExtension(e.Key);
+ }).ToArray();
+
+ var configurations = update.Value.CurrentState[Rules.RazorConfiguration.PrimaryDataSourceItemType].Items.Select(c =>
+ {
+ var includedExtensions = c.Value[Rules.RazorConfiguration.ExtensionsProperty]
+ .Split(';')
+ .Select(name => extensions.Where(e => e.ExtensionName == name).FirstOrDefault())
+ .Where(e => e != null)
+ .ToArray();
+
+ return new ProjectSystemRazorConfiguration(parsedVersion, c.Key, includedExtensions);
+ }).ToArray();
+
+ configuration = configurations.Where(c => c.ConfigurationName == defaultConfiguration).FirstOrDefault();
+ }
+
+ if (configuration == null)
+ {
+ // Ok we can't find a language version. Let's assume this project isn't using Razor then.
+ await UpdateProjectUnsafeAsync(null).ConfigureAwait(false);
+ return;
+ }
+
+ var hostProject = new HostProject(CommonServices.UnconfiguredProject.FullPath, configuration);
+ await UpdateProjectUnsafeAsync(hostProject).ConfigureAwait(false);
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/FallbackRazorProjectHost.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/FallbackRazorProjectHost.cs
new file mode 100644
index 0000000000..60dd780e51
--- /dev/null
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/FallbackRazorProjectHost.cs
@@ -0,0 +1,142 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.ComponentModel.Composition;
+using System.IO;
+using System.Reflection.Metadata;
+using System.Reflection.PortableExecutable;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Threading.Tasks.Dataflow;
+using Microsoft.VisualStudio.LanguageServices;
+using Microsoft.VisualStudio.ProjectSystem;
+using ResolvedCompilationReference = Microsoft.CodeAnalysis.Razor.ProjectSystem.ManageProjectSystemSchema.ResolvedCompilationReference;
+
+namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
+{
+ // Somewhat similar to https://github.com/dotnet/project-system/blob/fa074d228dcff6dae9e48ce43dd4a3a5aa22e8f0/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/LanguageServices/LanguageServiceHost.cs
+ //
+ // This class is responsible for intializing the Razor ProjectSnapshotManager for cases where
+ // MSBuild does not provides configuration support (SDK < 2.1).
+ [AppliesTo("(DotNetCoreRazor | DotNetCoreWeb) & !DotNetCoreRazorConfiguration")]
+ [Export(ExportContractNames.Scopes.UnconfiguredProject, typeof(IProjectDynamicLoadComponent))]
+ internal class FallbackRazorProjectHost : RazorProjectHostBase
+ {
+ private const string MvcAssemblyName = "Microsoft.AspNetCore.Mvc.Razor";
+ private const string MvcAssemblyFileName = "Microsoft.AspNetCore.Mvc.Razor.dll";
+
+ private IDisposable _subscription;
+
+ [ImportingConstructor]
+ public FallbackRazorProjectHost(
+ IUnconfiguredProjectCommonServices commonServices,
+ [Import(typeof(VisualStudioWorkspace))] Workspace workspace)
+ : base(commonServices, workspace)
+ {
+ }
+
+ // Internal for testing
+ internal FallbackRazorProjectHost(
+ IUnconfiguredProjectCommonServices commonServices,
+ Workspace workspace,
+ ProjectSnapshotManagerBase projectManager)
+ : base(commonServices, workspace, projectManager)
+ {
+ }
+
+ protected override async Task InitializeCoreAsync(CancellationToken cancellationToken)
+ {
+ await base.InitializeCoreAsync(cancellationToken).ConfigureAwait(false);
+
+ // Don't try to evaluate any properties here since the project is still loading and we require access
+ // to the UI thread to push our updates.
+ //
+ // Just subscribe and handle the notification later.
+ var receiver = new ActionBlock>(OnProjectChanged);
+ _subscription = CommonServices.ActiveConfiguredProjectSubscription.JointRuleSource.SourceBlock.LinkTo(
+ receiver,
+ initialDataAsNew: true,
+ suppressVersionOnlyUpdates: true,
+ ruleNames: new string[] { ResolvedCompilationReference.SchemaName });
+ }
+
+ protected override async Task DisposeCoreAsync(bool initialized)
+ {
+ await base.DisposeCoreAsync(initialized).ConfigureAwait(false);
+
+ if (initialized)
+ {
+ _subscription.Dispose();
+ }
+ }
+
+ // Internal for testing
+ internal async Task OnProjectChanged(IProjectVersionedValue update)
+ {
+ await ExecuteWithLock(async () =>
+ {
+ if (IsDisposing || IsDisposed)
+ {
+ return;
+ }
+
+ string mvcReferenceFullPath = null;
+ var references = update.Value.CurrentState[ResolvedCompilationReference.SchemaName].Items;
+ foreach (var reference in references)
+ {
+ if (reference.Key.EndsWith(MvcAssemblyFileName, StringComparison.OrdinalIgnoreCase))
+ {
+ mvcReferenceFullPath = reference.Key;
+ break;
+ }
+ }
+
+ if (mvcReferenceFullPath == null)
+ {
+ // Ok we can't find an MVC version. Let's assume this project isn't using Razor then.
+ await UpdateProjectUnsafeAsync(null).ConfigureAwait(false);
+ return;
+ }
+
+ var version = GetAssemblyVersion(mvcReferenceFullPath);
+ if (version == null)
+ {
+ // Ok we can't find an MVC version. Let's assume this project isn't using Razor then.
+ await UpdateProjectUnsafeAsync(null).ConfigureAwait(false);
+ return;
+ }
+
+ var configuration = FallbackRazorConfiguration.SelectConfiguration(version);
+ var hostProject = new HostProject(CommonServices.UnconfiguredProject.FullPath, configuration);
+ await UpdateProjectUnsafeAsync(hostProject).ConfigureAwait(false);
+ });
+ }
+
+ // virtual for overriding in tests
+ protected virtual Version GetAssemblyVersion(string filePath)
+ {
+ return ReadAssemblyVersion(filePath);
+ }
+
+ private static Version ReadAssemblyVersion(string filePath)
+ {
+ try
+ {
+ using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete))
+ using (var reader = new PEReader(stream))
+ {
+ var metadataReader = reader.GetMetadataReader();
+
+ var assemblyDefinition = metadataReader.GetAssemblyDefinition();
+ return assemblyDefinition.Version;
+ }
+ }
+ catch
+ {
+ // We're purposely silencing any kinds of I/O exceptions here, just in case something wacky is going on.
+ return null;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/IUnconfiguredProjectCommonServices.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/IUnconfiguredProjectCommonServices.cs
new file mode 100644
index 0000000000..1e98e9c732
--- /dev/null
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/IUnconfiguredProjectCommonServices.cs
@@ -0,0 +1,88 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.ComponentModel.Composition;
+using Microsoft.VisualStudio.ProjectSystem;
+using Microsoft.VisualStudio.ProjectSystem.References;
+
+namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
+{
+ [Export(typeof(IUnconfiguredProjectCommonServices))]
+ internal class UnconfiguredProjectCommonServices : IUnconfiguredProjectCommonServices
+ {
+ private readonly ActiveConfiguredProject _activeConfiguredProject;
+ private readonly ActiveConfiguredProject _activeConfiguredProjectAssemblyReferences;
+ private readonly ActiveConfiguredProject _activeConfiguredProjectPackageReferences;
+ private readonly ActiveConfiguredProject _activeConfiguredProjectProperties;
+
+ [ImportingConstructor]
+ public UnconfiguredProjectCommonServices(
+ IProjectThreadingService threadingService,
+ UnconfiguredProject unconfiguredProject,
+ IActiveConfiguredProjectSubscriptionService activeConfiguredProjectSubscription,
+ ActiveConfiguredProject activeConfiguredProject,
+ ActiveConfiguredProject activeConfiguredProjectAssemblyReferences,
+ ActiveConfiguredProject activeConfiguredProjectPackageReferences,
+ ActiveConfiguredProject activeConfiguredProjectRazorProperties)
+ {
+ if (threadingService == null)
+ {
+ throw new ArgumentNullException(nameof(threadingService));
+ }
+
+ if (unconfiguredProject == null)
+ {
+ throw new ArgumentNullException(nameof(unconfiguredProject));
+ }
+
+ if (activeConfiguredProjectSubscription == null)
+ {
+ throw new ArgumentNullException(nameof(ActiveConfiguredProjectSubscription));
+ }
+
+ if (activeConfiguredProject == null)
+ {
+ throw new ArgumentNullException(nameof(activeConfiguredProject));
+ }
+
+ if (activeConfiguredProjectAssemblyReferences == null)
+ {
+ throw new ArgumentNullException(nameof(activeConfiguredProjectAssemblyReferences));
+ }
+
+ if (activeConfiguredProjectPackageReferences == null)
+ {
+ throw new ArgumentNullException(nameof(activeConfiguredProjectPackageReferences));
+ }
+
+ if (activeConfiguredProjectRazorProperties == null)
+ {
+ throw new ArgumentNullException(nameof(activeConfiguredProjectRazorProperties));
+ }
+
+ ThreadingService = threadingService;
+ UnconfiguredProject = unconfiguredProject;
+ ActiveConfiguredProjectSubscription = activeConfiguredProjectSubscription;
+ _activeConfiguredProject = activeConfiguredProject;
+ _activeConfiguredProjectAssemblyReferences = activeConfiguredProjectAssemblyReferences;
+ _activeConfiguredProjectPackageReferences = activeConfiguredProjectPackageReferences;
+ _activeConfiguredProjectProperties = activeConfiguredProjectRazorProperties;
+ }
+
+ public ConfiguredProject ActiveConfiguredProject => _activeConfiguredProject.Value;
+
+ public IAssemblyReferencesService ActiveConfiguredProjectAssemblyReferences => _activeConfiguredProjectAssemblyReferences.Value;
+
+ public IPackageReferencesService ActiveConfiguredProjectPackageReferences => _activeConfiguredProjectPackageReferences.Value;
+
+ public Rules.RazorProjectProperties ActiveConfiguredProjectRazorProperties => _activeConfiguredProjectProperties.Value;
+
+ public IActiveConfiguredProjectSubscriptionService ActiveConfiguredProjectSubscription { get; }
+
+
+ public IProjectThreadingService ThreadingService { get; }
+
+ public UnconfiguredProject UnconfiguredProject { get; }
+ }
+}
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/ManageProjectSystemSchema.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/ManageProjectSystemSchema.cs
new file mode 100644
index 0000000000..79138c8ac6
--- /dev/null
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/ManageProjectSystemSchema.cs
@@ -0,0 +1,16 @@
+// 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.
+
+namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
+{
+ // Well-Known Schema and property names defined by the ManagedProjectSystem
+ internal static class ManageProjectSystemSchema
+ {
+ public static class ResolvedCompilationReference
+ {
+ public static readonly string SchemaName = "ResolvedCompilationReference";
+
+ public static readonly string ItemName = "ResolvedCompilationReference";
+ }
+ }
+}
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/RazorProjectHostBase.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/RazorProjectHostBase.cs
new file mode 100644
index 0000000000..5419eb73af
--- /dev/null
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/RazorProjectHostBase.cs
@@ -0,0 +1,190 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.ComponentModel.Composition;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.LanguageServices;
+using Microsoft.VisualStudio.ProjectSystem;
+using Microsoft.VisualStudio.Threading;
+
+namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
+{
+ internal abstract class RazorProjectHostBase : OnceInitializedOnceDisposedAsync, IProjectDynamicLoadComponent
+ {
+ private readonly Workspace _workspace;
+ private readonly AsyncSemaphore _lock;
+
+ private ProjectSnapshotManagerBase _projectManager;
+ private HostProject _current;
+
+ public RazorProjectHostBase(
+ IUnconfiguredProjectCommonServices commonServices,
+ [Import(typeof(VisualStudioWorkspace))] Workspace workspace)
+ : base(commonServices.ThreadingService.JoinableTaskContext)
+ {
+ if (commonServices == null)
+ {
+ throw new ArgumentNullException(nameof(commonServices));
+ }
+
+ if (workspace == null)
+ {
+ throw new ArgumentNullException(nameof(workspace));
+ }
+
+ CommonServices = commonServices;
+ _workspace = workspace;
+
+ _lock = new AsyncSemaphore(initialCount: 1);
+ }
+
+ // Internal for testing
+ protected RazorProjectHostBase(
+ IUnconfiguredProjectCommonServices commonServices,
+ Workspace workspace,
+ ProjectSnapshotManagerBase projectManager)
+ : base(commonServices.ThreadingService.JoinableTaskContext)
+ {
+ if (commonServices == null)
+ {
+ throw new ArgumentNullException(nameof(commonServices));
+ }
+
+ if (workspace == null)
+ {
+ throw new ArgumentNullException(nameof(workspace));
+ }
+
+ if (projectManager == null)
+ {
+ throw new ArgumentNullException(nameof(projectManager));
+ }
+
+ CommonServices = commonServices;
+ _workspace = workspace;
+ _projectManager = projectManager;
+
+ _lock = new AsyncSemaphore(initialCount: 1);
+ }
+
+ protected IUnconfiguredProjectCommonServices CommonServices { get; }
+
+ // internal for tests. The product will call through the IProjectDynamicLoadComponent interface.
+ internal Task LoadAsync()
+ {
+ return InitializeAsync();
+ }
+
+ protected override Task InitializeCoreAsync(CancellationToken cancellationToken)
+ {
+ CommonServices.UnconfiguredProject.ProjectRenaming += UnconfiguredProject_ProjectRenaming;
+
+ return Task.CompletedTask;
+ }
+
+ protected override async Task DisposeCoreAsync(bool initialized)
+ {
+ if (initialized)
+ {
+ CommonServices.UnconfiguredProject.ProjectRenaming -= UnconfiguredProject_ProjectRenaming;
+
+ await ExecuteWithLock(async () =>
+ {
+ if (_current != null)
+ {
+ await UpdateProjectUnsafeAsync(null).ConfigureAwait(false);
+ }
+ });
+ }
+ }
+
+ // Internal for tests
+ internal async Task OnProjectRenamingAsync()
+ {
+ // When a project gets renamed we expect any rules watched by the derived class to fire.
+ //
+ // However, the project snapshot manager uses the project Fullpath as the key. We want to just
+ // reinitialize the HostProject with the same configuration and settings here, but the updated
+ // FilePath.
+ await ExecuteWithLock(async () =>
+ {
+ if (_current != null)
+ {
+ var old = _current;
+ await UpdateProjectUnsafeAsync(null).ConfigureAwait(false);
+
+ var filePath = CommonServices.UnconfiguredProject.FullPath;
+ await UpdateProjectUnsafeAsync(new HostProject(filePath, old.Configuration)).ConfigureAwait(false);
+ }
+ });
+ }
+
+ // Should only be called from the UI thread.
+ private ProjectSnapshotManagerBase GetProjectManager()
+ {
+ CommonServices.ThreadingService.VerifyOnUIThread();
+
+ if (_projectManager == null)
+ {
+ _projectManager = (ProjectSnapshotManagerBase)_workspace.Services.GetLanguageServices(RazorLanguage.Name).GetRequiredService();
+ }
+
+ return _projectManager;
+ }
+
+ // Must be called inside the lock.
+ protected async Task UpdateProjectUnsafeAsync(HostProject project)
+ {
+ await CommonServices.ThreadingService.SwitchToUIThread();
+ var projectManager = GetProjectManager();
+
+ if (_current == null && project == null)
+ {
+ // This is a no-op. This project isn't using Razor.
+ }
+ else if (_current == null && project != null)
+ {
+ projectManager.HostProjectAdded(project);
+ }
+ else if (_current != null && project == null)
+ {
+ projectManager.HostProjectRemoved(_current);
+ }
+ else
+ {
+ projectManager.HostProjectChanged(project);
+ }
+
+ _current = project;
+ }
+
+ protected async Task ExecuteWithLock(Func func)
+ {
+ using (JoinableCollection.Join())
+ {
+ using (await _lock.EnterAsync().ConfigureAwait(false))
+ {
+ var task = JoinableFactory.RunAsync(func);
+ await task.Task.ConfigureAwait(false);
+ }
+ }
+ }
+
+ Task IProjectDynamicLoadComponent.LoadAsync()
+ {
+ return InitializeAsync();
+ }
+
+ Task IProjectDynamicLoadComponent.UnloadAsync()
+ {
+ return DisposeAsync();
+ }
+
+ private async Task UnconfiguredProject_ProjectRenaming(object sender, ProjectRenamedEventArgs args)
+ {
+ await OnProjectRenamingAsync().ConfigureAwait(false);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/Rules/RazorConfiguration.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/Rules/RazorConfiguration.cs
new file mode 100644
index 0000000000..85912aed47
--- /dev/null
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/Rules/RazorConfiguration.cs
@@ -0,0 +1,212 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace Microsoft.CodeAnalysis.Razor.ProjectSystem.Rules {
+
+
+ internal partial class RazorConfiguration {
+
+ /// Backing field for deserialized rule..
+ private static Microsoft.Build.Framework.XamlTypes.Rule deserializedFallbackRule;
+
+ /// The name of the schema to look for at runtime to fulfill property access.
+ internal const string SchemaName = "RazorConfiguration";
+
+ /// The ItemType given in the Rule.DataSource property. May not apply to every Property's individual DataSource.
+ internal const string PrimaryDataSourceItemType = "RazorConfiguration";
+
+ /// The Label given in the Rule.DataSource property. May not apply to every Property's individual DataSource.
+ internal const string PrimaryDataSourceLabel = "";
+
+ /// Razor Extensions (The "Extensions" property).
+ internal const string ExtensionsProperty = "Extensions";
+
+ /// Backing field for the property.
+ private Microsoft.VisualStudio.ProjectSystem.Properties.IRule rule;
+
+ /// Backing field for the file name of the rule property.
+ private string file;
+
+ /// Backing field for the ItemType property.
+ private string itemType;
+
+ /// Backing field for the ItemName property.
+ private string itemName;
+
+ /// Configured Project
+ private Microsoft.VisualStudio.ProjectSystem.ConfiguredProject configuredProject;
+
+ /// The dictionary of named catalogs.
+ private System.Collections.Immutable.IImmutableDictionary catalogs;
+
+ /// Backing field for the property.
+ private Microsoft.VisualStudio.ProjectSystem.Properties.IRule fallbackRule;
+
+ /// Thread locking object
+ private object locker = new object();
+
+ /// Initializes a new instance of the RazorConfiguration class.
+ internal RazorConfiguration(Microsoft.VisualStudio.ProjectSystem.Properties.IRule rule) {
+ this.rule = rule;
+ }
+
+ /// Initializes a new instance of the RazorConfiguration class.
+ internal RazorConfiguration(Microsoft.VisualStudio.ProjectSystem.ConfiguredProject configuredProject, System.Collections.Immutable.IImmutableDictionary catalogs, string context, string file, string itemType, string itemName) :
+ this(GetRule(System.Collections.Immutable.ImmutableDictionary.GetValueOrDefault(catalogs, context), file, itemType, itemName)) {
+ if ((configuredProject == null)) {
+ throw new System.ArgumentNullException("configuredProject");
+ }
+ this.configuredProject = configuredProject;
+ this.catalogs = catalogs;
+ this.file = file;
+ this.itemType = itemType;
+ this.itemName = itemName;
+ }
+
+ /// Initializes a new instance of the RazorConfiguration class.
+ internal RazorConfiguration(Microsoft.VisualStudio.ProjectSystem.Properties.IRule rule, Microsoft.VisualStudio.ProjectSystem.ConfiguredProject configuredProject) :
+ this(rule) {
+ if ((rule == null)) {
+ throw new System.ArgumentNullException("rule");
+ }
+ if ((configuredProject == null)) {
+ throw new System.ArgumentNullException("configuredProject");
+ }
+ this.configuredProject = configuredProject;
+ this.rule = rule;
+ this.file = this.rule.File;
+ this.itemType = this.rule.ItemType;
+ this.itemName = this.rule.ItemName;
+ }
+
+ /// Initializes a new instance of the RazorConfiguration class.
+ internal RazorConfiguration(Microsoft.VisualStudio.ProjectSystem.ConfiguredProject configuredProject, System.Collections.Immutable.IImmutableDictionary catalogs, string context, Microsoft.VisualStudio.ProjectSystem.Properties.IProjectPropertiesContext propertyContext) :
+ this(configuredProject, catalogs, context, GetContextFile(propertyContext), propertyContext.ItemType, propertyContext.ItemName) {
+ }
+
+ /// Initializes a new instance of the RazorConfiguration class that assumes a project context (neither property sheet nor items).
+ internal RazorConfiguration(Microsoft.VisualStudio.ProjectSystem.ConfiguredProject configuredProject, System.Collections.Immutable.IImmutableDictionary catalogs) :
+ this(configuredProject, catalogs, "Project", null, null, null) {
+ }
+
+ /// Gets the IRule used to get and set properties.
+ public Microsoft.VisualStudio.ProjectSystem.Properties.IRule Rule {
+ get {
+ return this.rule;
+ }
+ }
+
+ /// Razor Extensions
+ internal Microsoft.VisualStudio.ProjectSystem.Properties.IEvaluatedProperty Extensions {
+ get {
+ Microsoft.VisualStudio.ProjectSystem.Properties.IRule localRule = this.rule;
+ if ((localRule == null)) {
+ localRule = this.GeneratedFallbackRule;
+ }
+ if ((localRule == null)) {
+ return null;
+ }
+ Microsoft.VisualStudio.ProjectSystem.Properties.IEvaluatedProperty property = ((Microsoft.VisualStudio.ProjectSystem.Properties.IEvaluatedProperty)(localRule.GetProperty(ExtensionsProperty)));
+ if (((property == null)
+ && (this.GeneratedFallbackRule != null))) {
+ localRule = this.GeneratedFallbackRule;
+ property = ((Microsoft.VisualStudio.ProjectSystem.Properties.IEvaluatedProperty)(localRule.GetProperty(ExtensionsProperty)));
+ }
+ return property;
+ }
+ }
+
+ /// Get the fallback rule if the current rule on disk is missing or a property in the rule on disk is missing
+ private Microsoft.VisualStudio.ProjectSystem.Properties.IRule GeneratedFallbackRule {
+ get {
+ if (((this.fallbackRule == null)
+ && (this.configuredProject != null))) {
+ System.Threading.Monitor.Enter(this.locker);
+ try {
+ if ((this.fallbackRule == null)) {
+ this.InitializeFallbackRule();
+ }
+ }
+ finally {
+ System.Threading.Monitor.Exit(this.locker);
+ }
+ }
+ return this.fallbackRule;
+ }
+ }
+
+ private static Microsoft.VisualStudio.ProjectSystem.Properties.IRule GetRule(Microsoft.VisualStudio.ProjectSystem.Properties.IPropertyPagesCatalog catalog, string file, string itemType, string itemName) {
+ if ((catalog == null)) {
+ return null;
+ }
+ return catalog.BindToContext(SchemaName, file, itemType, itemName);
+ }
+
+ private static string GetContextFile(Microsoft.VisualStudio.ProjectSystem.Properties.IProjectPropertiesContext propertiesContext) {
+ if ((propertiesContext.IsProjectFile == true)) {
+ return null;
+ }
+ else {
+ return propertiesContext.File;
+ }
+ }
+
+ private void InitializeFallbackRule() {
+ if ((this.configuredProject == null)) {
+ return;
+ }
+ Microsoft.Build.Framework.XamlTypes.Rule unboundRule = RazorConfiguration.deserializedFallbackRule;
+ if ((unboundRule == null)) {
+ System.IO.Stream xamlStream = null;
+ System.Reflection.Assembly thisAssembly = System.Reflection.Assembly.GetExecutingAssembly();
+ try {
+ xamlStream = thisAssembly.GetManifestResourceStream("XamlRuleToCode:RazorConfiguration.xaml");
+ Microsoft.Build.Framework.XamlTypes.IProjectSchemaNode root = ((Microsoft.Build.Framework.XamlTypes.IProjectSchemaNode)(System.Xaml.XamlServices.Load(xamlStream)));
+ System.Collections.Generic.IEnumerator ruleEnumerator = root.GetSchemaObjects(typeof(Microsoft.Build.Framework.XamlTypes.Rule)).GetEnumerator();
+ for (
+ ; ((unboundRule == null)
+ && ruleEnumerator.MoveNext());
+ ) {
+ Microsoft.Build.Framework.XamlTypes.Rule t = ((Microsoft.Build.Framework.XamlTypes.Rule)(ruleEnumerator.Current));
+ if (System.StringComparer.OrdinalIgnoreCase.Equals(t.Name, SchemaName)) {
+ unboundRule = t;
+ unboundRule.Name = "843bc0bc-5265-4864-9b06-f5d5503b0484";
+ RazorConfiguration.deserializedFallbackRule = unboundRule;
+ }
+ }
+ }
+ finally {
+ if ((xamlStream != null)) {
+ ((System.IDisposable)(xamlStream)).Dispose();
+ }
+ }
+ }
+ this.configuredProject.Services.AdditionalRuleDefinitions.AddRuleDefinition(unboundRule, "FallbackRuleCodeGenerationContext");
+ Microsoft.VisualStudio.ProjectSystem.Properties.IPropertyPagesCatalog catalog = this.configuredProject.Services.PropertyPagesCatalog.GetMemoryOnlyCatalog("FallbackRuleCodeGenerationContext");
+ this.fallbackRule = catalog.BindToContext(unboundRule.Name, this.file, this.itemType, this.itemName);
+ }
+ }
+
+ internal partial class RazorProjectProperties {
+
+ private static System.Func>, object, RazorConfiguration> CreateRazorConfigurationPropertiesDelegate = new System.Func>, object, RazorConfiguration>(CreateRazorConfigurationProperties);
+
+ private static RazorConfiguration CreateRazorConfigurationProperties(System.Threading.Tasks.Task> namedCatalogs, object state) {
+ RazorProjectProperties that = ((RazorProjectProperties)(state));
+ return new RazorConfiguration(that.ConfiguredProject, namedCatalogs.Result, "Project", that.File, that.ItemType, that.ItemName);
+ }
+
+ /// Gets the strongly-typed property accessor used to get and set Configuration Properties properties.
+ internal System.Threading.Tasks.Task GetRazorConfigurationPropertiesAsync() {
+ System.Threading.Tasks.Task> namedCatalogsTask = this.GetNamedCatalogsAsync();
+ return namedCatalogsTask.ContinueWith(CreateRazorConfigurationPropertiesDelegate, this, System.Threading.CancellationToken.None, System.Threading.Tasks.TaskContinuationOptions.ExecuteSynchronously, System.Threading.Tasks.TaskScheduler.Default);
+ }
+ }
+}
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/Rules/RazorConfiguration.xaml b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/Rules/RazorConfiguration.xaml
new file mode 100644
index 0000000000..9632054a75
--- /dev/null
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/Rules/RazorConfiguration.xaml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/Rules/RazorExtension.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/Rules/RazorExtension.cs
new file mode 100644
index 0000000000..f73cc3fdb1
--- /dev/null
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/Rules/RazorExtension.cs
@@ -0,0 +1,235 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace Microsoft.CodeAnalysis.Razor.ProjectSystem.Rules {
+
+
+ internal partial class RazorExtension {
+
+ /// Backing field for deserialized rule..
+ private static Microsoft.Build.Framework.XamlTypes.Rule deserializedFallbackRule;
+
+ /// The name of the schema to look for at runtime to fulfill property access.
+ internal const string SchemaName = "RazorExtension";
+
+ /// The ItemType given in the Rule.DataSource property. May not apply to every Property's individual DataSource.
+ internal const string PrimaryDataSourceItemType = "RazorExtension";
+
+ /// The Label given in the Rule.DataSource property. May not apply to every Property's individual DataSource.
+ internal const string PrimaryDataSourceLabel = "";
+
+ /// Razor Extension Assembly Name (The "AssemblyName" property).
+ internal const string AssemblyNameProperty = "AssemblyName";
+
+ /// Razor Extension Assembly File Path (The "AssemblyFilePath" property).
+ internal const string AssemblyFilePathProperty = "AssemblyFilePath";
+
+ /// Backing field for the property.
+ private Microsoft.VisualStudio.ProjectSystem.Properties.IRule rule;
+
+ /// Backing field for the file name of the rule property.
+ private string file;
+
+ /// Backing field for the ItemType property.
+ private string itemType;
+
+ /// Backing field for the ItemName property.
+ private string itemName;
+
+ /// Configured Project
+ private Microsoft.VisualStudio.ProjectSystem.ConfiguredProject configuredProject;
+
+ /// The dictionary of named catalogs.
+ private System.Collections.Immutable.IImmutableDictionary catalogs;
+
+ /// Backing field for the property.
+ private Microsoft.VisualStudio.ProjectSystem.Properties.IRule fallbackRule;
+
+ /// Thread locking object
+ private object locker = new object();
+
+ /// Initializes a new instance of the RazorExtension class.
+ internal RazorExtension(Microsoft.VisualStudio.ProjectSystem.Properties.IRule rule) {
+ this.rule = rule;
+ }
+
+ /// Initializes a new instance of the RazorExtension class.
+ internal RazorExtension(Microsoft.VisualStudio.ProjectSystem.ConfiguredProject configuredProject, System.Collections.Immutable.IImmutableDictionary catalogs, string context, string file, string itemType, string itemName) :
+ this(GetRule(System.Collections.Immutable.ImmutableDictionary.GetValueOrDefault(catalogs, context), file, itemType, itemName)) {
+ if ((configuredProject == null)) {
+ throw new System.ArgumentNullException("configuredProject");
+ }
+ this.configuredProject = configuredProject;
+ this.catalogs = catalogs;
+ this.file = file;
+ this.itemType = itemType;
+ this.itemName = itemName;
+ }
+
+ /// Initializes a new instance of the RazorExtension class.
+ internal RazorExtension(Microsoft.VisualStudio.ProjectSystem.Properties.IRule rule, Microsoft.VisualStudio.ProjectSystem.ConfiguredProject configuredProject) :
+ this(rule) {
+ if ((rule == null)) {
+ throw new System.ArgumentNullException("rule");
+ }
+ if ((configuredProject == null)) {
+ throw new System.ArgumentNullException("configuredProject");
+ }
+ this.configuredProject = configuredProject;
+ this.rule = rule;
+ this.file = this.rule.File;
+ this.itemType = this.rule.ItemType;
+ this.itemName = this.rule.ItemName;
+ }
+
+ /// Initializes a new instance of the RazorExtension class.
+ internal RazorExtension(Microsoft.VisualStudio.ProjectSystem.ConfiguredProject configuredProject, System.Collections.Immutable.IImmutableDictionary catalogs, string context, Microsoft.VisualStudio.ProjectSystem.Properties.IProjectPropertiesContext propertyContext) :
+ this(configuredProject, catalogs, context, GetContextFile(propertyContext), propertyContext.ItemType, propertyContext.ItemName) {
+ }
+
+ /// Initializes a new instance of the RazorExtension class that assumes a project context (neither property sheet nor items).
+ internal RazorExtension(Microsoft.VisualStudio.ProjectSystem.ConfiguredProject configuredProject, System.Collections.Immutable.IImmutableDictionary catalogs) :
+ this(configuredProject, catalogs, "Project", null, null, null) {
+ }
+
+ /// Gets the IRule used to get and set properties.
+ public Microsoft.VisualStudio.ProjectSystem.Properties.IRule Rule {
+ get {
+ return this.rule;
+ }
+ }
+
+ /// Razor Extension Assembly Name
+ internal Microsoft.VisualStudio.ProjectSystem.Properties.IEvaluatedProperty AssemblyName {
+ get {
+ Microsoft.VisualStudio.ProjectSystem.Properties.IRule localRule = this.rule;
+ if ((localRule == null)) {
+ localRule = this.GeneratedFallbackRule;
+ }
+ if ((localRule == null)) {
+ return null;
+ }
+ Microsoft.VisualStudio.ProjectSystem.Properties.IEvaluatedProperty property = ((Microsoft.VisualStudio.ProjectSystem.Properties.IEvaluatedProperty)(localRule.GetProperty(AssemblyNameProperty)));
+ if (((property == null)
+ && (this.GeneratedFallbackRule != null))) {
+ localRule = this.GeneratedFallbackRule;
+ property = ((Microsoft.VisualStudio.ProjectSystem.Properties.IEvaluatedProperty)(localRule.GetProperty(AssemblyNameProperty)));
+ }
+ return property;
+ }
+ }
+
+ /// Razor Extension Assembly File Path
+ internal Microsoft.VisualStudio.ProjectSystem.Properties.IEvaluatedProperty AssemblyFilePath {
+ get {
+ Microsoft.VisualStudio.ProjectSystem.Properties.IRule localRule = this.rule;
+ if ((localRule == null)) {
+ localRule = this.GeneratedFallbackRule;
+ }
+ if ((localRule == null)) {
+ return null;
+ }
+ Microsoft.VisualStudio.ProjectSystem.Properties.IEvaluatedProperty property = ((Microsoft.VisualStudio.ProjectSystem.Properties.IEvaluatedProperty)(localRule.GetProperty(AssemblyFilePathProperty)));
+ if (((property == null)
+ && (this.GeneratedFallbackRule != null))) {
+ localRule = this.GeneratedFallbackRule;
+ property = ((Microsoft.VisualStudio.ProjectSystem.Properties.IEvaluatedProperty)(localRule.GetProperty(AssemblyFilePathProperty)));
+ }
+ return property;
+ }
+ }
+
+ /// Get the fallback rule if the current rule on disk is missing or a property in the rule on disk is missing
+ private Microsoft.VisualStudio.ProjectSystem.Properties.IRule GeneratedFallbackRule {
+ get {
+ if (((this.fallbackRule == null)
+ && (this.configuredProject != null))) {
+ System.Threading.Monitor.Enter(this.locker);
+ try {
+ if ((this.fallbackRule == null)) {
+ this.InitializeFallbackRule();
+ }
+ }
+ finally {
+ System.Threading.Monitor.Exit(this.locker);
+ }
+ }
+ return this.fallbackRule;
+ }
+ }
+
+ private static Microsoft.VisualStudio.ProjectSystem.Properties.IRule GetRule(Microsoft.VisualStudio.ProjectSystem.Properties.IPropertyPagesCatalog catalog, string file, string itemType, string itemName) {
+ if ((catalog == null)) {
+ return null;
+ }
+ return catalog.BindToContext(SchemaName, file, itemType, itemName);
+ }
+
+ private static string GetContextFile(Microsoft.VisualStudio.ProjectSystem.Properties.IProjectPropertiesContext propertiesContext) {
+ if ((propertiesContext.IsProjectFile == true)) {
+ return null;
+ }
+ else {
+ return propertiesContext.File;
+ }
+ }
+
+ private void InitializeFallbackRule() {
+ if ((this.configuredProject == null)) {
+ return;
+ }
+ Microsoft.Build.Framework.XamlTypes.Rule unboundRule = RazorExtension.deserializedFallbackRule;
+ if ((unboundRule == null)) {
+ System.IO.Stream xamlStream = null;
+ System.Reflection.Assembly thisAssembly = System.Reflection.Assembly.GetExecutingAssembly();
+ try {
+ xamlStream = thisAssembly.GetManifestResourceStream("XamlRuleToCode:RazorExtension.xaml");
+ Microsoft.Build.Framework.XamlTypes.IProjectSchemaNode root = ((Microsoft.Build.Framework.XamlTypes.IProjectSchemaNode)(System.Xaml.XamlServices.Load(xamlStream)));
+ System.Collections.Generic.IEnumerator ruleEnumerator = root.GetSchemaObjects(typeof(Microsoft.Build.Framework.XamlTypes.Rule)).GetEnumerator();
+ for (
+ ; ((unboundRule == null)
+ && ruleEnumerator.MoveNext());
+ ) {
+ Microsoft.Build.Framework.XamlTypes.Rule t = ((Microsoft.Build.Framework.XamlTypes.Rule)(ruleEnumerator.Current));
+ if (System.StringComparer.OrdinalIgnoreCase.Equals(t.Name, SchemaName)) {
+ unboundRule = t;
+ unboundRule.Name = "10b26d1c-5ab7-4ca2-ab64-182fe18d53dd";
+ RazorExtension.deserializedFallbackRule = unboundRule;
+ }
+ }
+ }
+ finally {
+ if ((xamlStream != null)) {
+ ((System.IDisposable)(xamlStream)).Dispose();
+ }
+ }
+ }
+ this.configuredProject.Services.AdditionalRuleDefinitions.AddRuleDefinition(unboundRule, "FallbackRuleCodeGenerationContext");
+ Microsoft.VisualStudio.ProjectSystem.Properties.IPropertyPagesCatalog catalog = this.configuredProject.Services.PropertyPagesCatalog.GetMemoryOnlyCatalog("FallbackRuleCodeGenerationContext");
+ this.fallbackRule = catalog.BindToContext(unboundRule.Name, this.file, this.itemType, this.itemName);
+ }
+ }
+
+ internal partial class RazorProjectProperties {
+
+ private static System.Func>, object, RazorExtension> CreateRazorExtensionPropertiesDelegate = new System.Func>, object, RazorExtension>(CreateRazorExtensionProperties);
+
+ private static RazorExtension CreateRazorExtensionProperties(System.Threading.Tasks.Task> namedCatalogs, object state) {
+ RazorProjectProperties that = ((RazorProjectProperties)(state));
+ return new RazorExtension(that.ConfiguredProject, namedCatalogs.Result, "Project", that.File, that.ItemType, that.ItemName);
+ }
+
+ /// Gets the strongly-typed property accessor used to get and set Extension Properties properties.
+ internal System.Threading.Tasks.Task GetRazorExtensionPropertiesAsync() {
+ System.Threading.Tasks.Task> namedCatalogsTask = this.GetNamedCatalogsAsync();
+ return namedCatalogsTask.ContinueWith(CreateRazorExtensionPropertiesDelegate, this, System.Threading.CancellationToken.None, System.Threading.Tasks.TaskContinuationOptions.ExecuteSynchronously, System.Threading.Tasks.TaskScheduler.Default);
+ }
+ }
+}
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/Rules/RazorExtension.xaml b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/Rules/RazorExtension.xaml
new file mode 100644
index 0000000000..f68eef7107
--- /dev/null
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/Rules/RazorExtension.xaml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/Rules/RazorGeneral.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/Rules/RazorGeneral.cs
new file mode 100644
index 0000000000..f28d4a90c3
--- /dev/null
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/Rules/RazorGeneral.cs
@@ -0,0 +1,235 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace Microsoft.CodeAnalysis.Razor.ProjectSystem.Rules {
+
+
+ internal partial class RazorGeneral {
+
+ /// Backing field for deserialized rule..
+ private static Microsoft.Build.Framework.XamlTypes.Rule deserializedFallbackRule;
+
+ /// The name of the schema to look for at runtime to fulfill property access.
+ internal const string SchemaName = "RazorGeneral";
+
+ /// The ItemType given in the Rule.DataSource property. May not apply to every Property's individual DataSource.
+ internal const string PrimaryDataSourceItemType = null;
+
+ /// The Label given in the Rule.DataSource property. May not apply to every Property's individual DataSource.
+ internal const string PrimaryDataSourceLabel = "";
+
+ /// Razor Language Version (The "RazorLangVersion" property).
+ internal const string RazorLangVersionProperty = "RazorLangVersion";
+
+ /// Razor Configuration Name (The "RazorDefaultConfiguration" property).
+ internal const string RazorDefaultConfigurationProperty = "RazorDefaultConfiguration";
+
+ /// Backing field for the property.
+ private Microsoft.VisualStudio.ProjectSystem.Properties.IRule rule;
+
+ /// Backing field for the file name of the rule property.
+ private string file;
+
+ /// Backing field for the ItemType property.
+ private string itemType;
+
+ /// Backing field for the ItemName property.
+ private string itemName;
+
+ /// Configured Project
+ private Microsoft.VisualStudio.ProjectSystem.ConfiguredProject configuredProject;
+
+ /// The dictionary of named catalogs.
+ private System.Collections.Immutable.IImmutableDictionary catalogs;
+
+ /// Backing field for the property.
+ private Microsoft.VisualStudio.ProjectSystem.Properties.IRule fallbackRule;
+
+ /// Thread locking object
+ private object locker = new object();
+
+ /// Initializes a new instance of the RazorGeneral class.
+ internal RazorGeneral(Microsoft.VisualStudio.ProjectSystem.Properties.IRule rule) {
+ this.rule = rule;
+ }
+
+ /// Initializes a new instance of the RazorGeneral class.
+ internal RazorGeneral(Microsoft.VisualStudio.ProjectSystem.ConfiguredProject configuredProject, System.Collections.Immutable.IImmutableDictionary catalogs, string context, string file, string itemType, string itemName) :
+ this(GetRule(System.Collections.Immutable.ImmutableDictionary.GetValueOrDefault(catalogs, context), file, itemType, itemName)) {
+ if ((configuredProject == null)) {
+ throw new System.ArgumentNullException("configuredProject");
+ }
+ this.configuredProject = configuredProject;
+ this.catalogs = catalogs;
+ this.file = file;
+ this.itemType = itemType;
+ this.itemName = itemName;
+ }
+
+ /// Initializes a new instance of the RazorGeneral class.
+ internal RazorGeneral(Microsoft.VisualStudio.ProjectSystem.Properties.IRule rule, Microsoft.VisualStudio.ProjectSystem.ConfiguredProject configuredProject) :
+ this(rule) {
+ if ((rule == null)) {
+ throw new System.ArgumentNullException("rule");
+ }
+ if ((configuredProject == null)) {
+ throw new System.ArgumentNullException("configuredProject");
+ }
+ this.configuredProject = configuredProject;
+ this.rule = rule;
+ this.file = this.rule.File;
+ this.itemType = this.rule.ItemType;
+ this.itemName = this.rule.ItemName;
+ }
+
+ /// Initializes a new instance of the RazorGeneral class.
+ internal RazorGeneral(Microsoft.VisualStudio.ProjectSystem.ConfiguredProject configuredProject, System.Collections.Immutable.IImmutableDictionary catalogs, string context, Microsoft.VisualStudio.ProjectSystem.Properties.IProjectPropertiesContext propertyContext) :
+ this(configuredProject, catalogs, context, GetContextFile(propertyContext), propertyContext.ItemType, propertyContext.ItemName) {
+ }
+
+ /// Initializes a new instance of the RazorGeneral class that assumes a project context (neither property sheet nor items).
+ internal RazorGeneral(Microsoft.VisualStudio.ProjectSystem.ConfiguredProject configuredProject, System.Collections.Immutable.IImmutableDictionary catalogs) :
+ this(configuredProject, catalogs, "Project", null, null, null) {
+ }
+
+ /// Gets the IRule used to get and set properties.
+ public Microsoft.VisualStudio.ProjectSystem.Properties.IRule Rule {
+ get {
+ return this.rule;
+ }
+ }
+
+ /// Razor Language Version
+ internal Microsoft.VisualStudio.ProjectSystem.Properties.IEvaluatedProperty RazorLangVersion {
+ get {
+ Microsoft.VisualStudio.ProjectSystem.Properties.IRule localRule = this.rule;
+ if ((localRule == null)) {
+ localRule = this.GeneratedFallbackRule;
+ }
+ if ((localRule == null)) {
+ return null;
+ }
+ Microsoft.VisualStudio.ProjectSystem.Properties.IEvaluatedProperty property = ((Microsoft.VisualStudio.ProjectSystem.Properties.IEvaluatedProperty)(localRule.GetProperty(RazorLangVersionProperty)));
+ if (((property == null)
+ && (this.GeneratedFallbackRule != null))) {
+ localRule = this.GeneratedFallbackRule;
+ property = ((Microsoft.VisualStudio.ProjectSystem.Properties.IEvaluatedProperty)(localRule.GetProperty(RazorLangVersionProperty)));
+ }
+ return property;
+ }
+ }
+
+ /// Razor Configuration Name
+ internal Microsoft.VisualStudio.ProjectSystem.Properties.IEvaluatedProperty RazorDefaultConfiguration {
+ get {
+ Microsoft.VisualStudio.ProjectSystem.Properties.IRule localRule = this.rule;
+ if ((localRule == null)) {
+ localRule = this.GeneratedFallbackRule;
+ }
+ if ((localRule == null)) {
+ return null;
+ }
+ Microsoft.VisualStudio.ProjectSystem.Properties.IEvaluatedProperty property = ((Microsoft.VisualStudio.ProjectSystem.Properties.IEvaluatedProperty)(localRule.GetProperty(RazorDefaultConfigurationProperty)));
+ if (((property == null)
+ && (this.GeneratedFallbackRule != null))) {
+ localRule = this.GeneratedFallbackRule;
+ property = ((Microsoft.VisualStudio.ProjectSystem.Properties.IEvaluatedProperty)(localRule.GetProperty(RazorDefaultConfigurationProperty)));
+ }
+ return property;
+ }
+ }
+
+ /// Get the fallback rule if the current rule on disk is missing or a property in the rule on disk is missing
+ private Microsoft.VisualStudio.ProjectSystem.Properties.IRule GeneratedFallbackRule {
+ get {
+ if (((this.fallbackRule == null)
+ && (this.configuredProject != null))) {
+ System.Threading.Monitor.Enter(this.locker);
+ try {
+ if ((this.fallbackRule == null)) {
+ this.InitializeFallbackRule();
+ }
+ }
+ finally {
+ System.Threading.Monitor.Exit(this.locker);
+ }
+ }
+ return this.fallbackRule;
+ }
+ }
+
+ private static Microsoft.VisualStudio.ProjectSystem.Properties.IRule GetRule(Microsoft.VisualStudio.ProjectSystem.Properties.IPropertyPagesCatalog catalog, string file, string itemType, string itemName) {
+ if ((catalog == null)) {
+ return null;
+ }
+ return catalog.BindToContext(SchemaName, file, itemType, itemName);
+ }
+
+ private static string GetContextFile(Microsoft.VisualStudio.ProjectSystem.Properties.IProjectPropertiesContext propertiesContext) {
+ if ((propertiesContext.IsProjectFile == true)) {
+ return null;
+ }
+ else {
+ return propertiesContext.File;
+ }
+ }
+
+ private void InitializeFallbackRule() {
+ if ((this.configuredProject == null)) {
+ return;
+ }
+ Microsoft.Build.Framework.XamlTypes.Rule unboundRule = RazorGeneral.deserializedFallbackRule;
+ if ((unboundRule == null)) {
+ System.IO.Stream xamlStream = null;
+ System.Reflection.Assembly thisAssembly = System.Reflection.Assembly.GetExecutingAssembly();
+ try {
+ xamlStream = thisAssembly.GetManifestResourceStream("XamlRuleToCode:RazorGeneral.xaml");
+ Microsoft.Build.Framework.XamlTypes.IProjectSchemaNode root = ((Microsoft.Build.Framework.XamlTypes.IProjectSchemaNode)(System.Xaml.XamlServices.Load(xamlStream)));
+ System.Collections.Generic.IEnumerator ruleEnumerator = root.GetSchemaObjects(typeof(Microsoft.Build.Framework.XamlTypes.Rule)).GetEnumerator();
+ for (
+ ; ((unboundRule == null)
+ && ruleEnumerator.MoveNext());
+ ) {
+ Microsoft.Build.Framework.XamlTypes.Rule t = ((Microsoft.Build.Framework.XamlTypes.Rule)(ruleEnumerator.Current));
+ if (System.StringComparer.OrdinalIgnoreCase.Equals(t.Name, SchemaName)) {
+ unboundRule = t;
+ unboundRule.Name = "15acc140-184e-44be-a4d3-62505276a0bb";
+ RazorGeneral.deserializedFallbackRule = unboundRule;
+ }
+ }
+ }
+ finally {
+ if ((xamlStream != null)) {
+ ((System.IDisposable)(xamlStream)).Dispose();
+ }
+ }
+ }
+ this.configuredProject.Services.AdditionalRuleDefinitions.AddRuleDefinition(unboundRule, "FallbackRuleCodeGenerationContext");
+ Microsoft.VisualStudio.ProjectSystem.Properties.IPropertyPagesCatalog catalog = this.configuredProject.Services.PropertyPagesCatalog.GetMemoryOnlyCatalog("FallbackRuleCodeGenerationContext");
+ this.fallbackRule = catalog.BindToContext(unboundRule.Name, this.file, this.itemType, this.itemName);
+ }
+ }
+
+ internal partial class RazorProjectProperties {
+
+ private static System.Func>, object, RazorGeneral> CreateRazorGeneralPropertiesDelegate = new System.Func>, object, RazorGeneral>(CreateRazorGeneralProperties);
+
+ private static RazorGeneral CreateRazorGeneralProperties(System.Threading.Tasks.Task> namedCatalogs, object state) {
+ RazorProjectProperties that = ((RazorProjectProperties)(state));
+ return new RazorGeneral(that.ConfiguredProject, namedCatalogs.Result, "Project", that.File, that.ItemType, that.ItemName);
+ }
+
+ /// Gets the strongly-typed property accessor used to get and set Razor Properties properties.
+ internal System.Threading.Tasks.Task GetRazorGeneralPropertiesAsync() {
+ System.Threading.Tasks.Task> namedCatalogsTask = this.GetNamedCatalogsAsync();
+ return namedCatalogsTask.ContinueWith(CreateRazorGeneralPropertiesDelegate, this, System.Threading.CancellationToken.None, System.Threading.Tasks.TaskContinuationOptions.ExecuteSynchronously, System.Threading.Tasks.TaskScheduler.Default);
+ }
+ }
+}
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/Rules/RazorGeneral.xaml b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/Rules/RazorGeneral.xaml
new file mode 100644
index 0000000000..a2e5150160
--- /dev/null
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/Rules/RazorGeneral.xaml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/Rules/RazorProjectProperties.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/Rules/RazorProjectProperties.cs
new file mode 100644
index 0000000000..cc8f28a6f1
--- /dev/null
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/Rules/RazorProjectProperties.cs
@@ -0,0 +1,34 @@
+// 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.ComponentModel.Composition;
+using Microsoft.VisualStudio.ProjectSystem;
+using Microsoft.VisualStudio.ProjectSystem.Properties;
+
+namespace Microsoft.CodeAnalysis.Razor.ProjectSystem.Rules
+{
+ [Export]
+ internal partial class RazorProjectProperties : StronglyTypedPropertyAccess
+ {
+ [ImportingConstructor]
+ public RazorProjectProperties(ConfiguredProject configuredProject)
+ : base(configuredProject)
+ {
+ }
+
+ public RazorProjectProperties(ConfiguredProject configuredProject, UnconfiguredProject unconfiguredProject)
+ : base(configuredProject, unconfiguredProject)
+ {
+ }
+
+ public RazorProjectProperties(ConfiguredProject configuredProject, IProjectPropertiesContext projectPropertiesContext)
+ : base(configuredProject, projectPropertiesContext)
+ {
+ }
+
+ public RazorProjectProperties(ConfiguredProject configuredProject, string file, string itemType, string itemName)
+ : base(configuredProject, file, itemType, itemName)
+ {
+ }
+ }
+}
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/UnconfiguredProjectCommonServices.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/UnconfiguredProjectCommonServices.cs
new file mode 100644
index 0000000000..9e235012d2
--- /dev/null
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/UnconfiguredProjectCommonServices.cs
@@ -0,0 +1,30 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using Microsoft.VisualStudio.ProjectSystem;
+using Microsoft.VisualStudio.ProjectSystem.References;
+
+namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
+{
+ // This defines the set of services that we frequently need for working with UnconfiguredProject.
+ //
+ // We're following a somewhat common pattern for code that uses CPS. It's really easy to end up
+ // relying on service location inside CPS, which can be hard to test. This approach makes it easy
+ // for us to build reusable mocks instead.
+ internal interface IUnconfiguredProjectCommonServices
+ {
+ ConfiguredProject ActiveConfiguredProject { get; }
+
+ IAssemblyReferencesService ActiveConfiguredProjectAssemblyReferences { get; }
+
+ IPackageReferencesService ActiveConfiguredProjectPackageReferences { get; }
+
+ Rules.RazorProjectProperties ActiveConfiguredProjectRazorProperties { get; }
+
+ IActiveConfiguredProjectSubscriptionService ActiveConfiguredProjectSubscription { get; }
+
+ IProjectThreadingService ThreadingService { get; }
+
+ UnconfiguredProject UnconfiguredProject { get; }
+ }
+}
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/VisualStudioErrorReporter.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/VisualStudioErrorReporter.cs
index 8a847f5a3a..766494cf1b 100644
--- a/src/Microsoft.VisualStudio.LanguageServices.Razor/VisualStudioErrorReporter.cs
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/VisualStudioErrorReporter.cs
@@ -4,6 +4,7 @@
using System;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Razor;
+using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using Microsoft.VisualStudio.Shell.Interop;
namespace Microsoft.VisualStudio.LanguageServices.Razor
@@ -40,7 +41,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
}
}
- public override void ReportError(Exception exception, Project project)
+ public override void ReportError(Exception exception, ProjectSnapshot project)
{
if (exception == null)
{
@@ -53,7 +54,25 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
var hr = activityLog.LogEntry(
(uint)__ACTIVITYLOG_ENTRYTYPE.ALE_ERROR,
"Razor Language Services",
- $"Error encountered from project '{project?.Name}':{Environment.NewLine}{exception}");
+ $"Error encountered from project '{project?.FilePath}':{Environment.NewLine}{exception}");
+ ErrorHandler.ThrowOnFailure(hr);
+ }
+ }
+
+ public override void ReportError(Exception exception, Project workspaceProject)
+ {
+ if (exception == null)
+ {
+ return;
+ }
+
+ var activityLog = GetActivityLog();
+ if (activityLog != null)
+ {
+ var hr = activityLog.LogEntry(
+ (uint)__ACTIVITYLOG_ENTRYTYPE.ALE_ERROR,
+ "Razor Language Services",
+ $"Error encountered from project '{workspaceProject?.Name}' '{workspaceProject?.FilePath}':{Environment.NewLine}{exception}");
ErrorHandler.ThrowOnFailure(hr);
}
}
diff --git a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/Microsoft.CodeAnalysis.Razor.Workspaces.Test.csproj b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/Microsoft.CodeAnalysis.Razor.Workspaces.Test.csproj
index d1ad2a9fab..1967009f1f 100644
--- a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/Microsoft.CodeAnalysis.Razor.Workspaces.Test.csproj
+++ b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/Microsoft.CodeAnalysis.Razor.Workspaces.Test.csproj
@@ -19,6 +19,7 @@
+
diff --git a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultProjectExtensibilityConfigurationFactoryTest.cs b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultProjectExtensibilityConfigurationFactoryTest.cs
deleted file mode 100644
index 633ac9f797..0000000000
--- a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultProjectExtensibilityConfigurationFactoryTest.cs
+++ /dev/null
@@ -1,245 +0,0 @@
-// Copyright (c) .NET Foundation. All rights reserved.
-// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-
-using System;
-using Microsoft.AspNetCore.Razor.Language;
-using Xunit;
-
-namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
-{
- public class DefaultProjectExtensibilityConfigurationFactoryTest
- {
- public static TheoryData LanguageVersionMappingData
- {
- get
- {
- return new TheoryData
- {
- { new AssemblyIdentity("Microsoft.AspNetCore.Razor", new Version("1.0.0.0")), RazorLanguageVersion.Version_1_0 },
- { new AssemblyIdentity("Microsoft.AspNetCore.Razor", new Version("1.1.0.0")), RazorLanguageVersion.Version_1_1 },
- { new AssemblyIdentity("Microsoft.AspNetCore.Razor", new Version("2.0.0.0")), RazorLanguageVersion.Version_2_0 },
- { new AssemblyIdentity("Microsoft.AspNetCore.Razor", new Version("2.1.0.0")), RazorLanguageVersion.Version_2_1 },
- };
- }
- }
-
- [Theory]
- [MemberData(nameof(LanguageVersionMappingData))]
- public void GetLanguageVersion_MapsExactVersionsCorrectly(AssemblyIdentity assemblyIdentity, RazorLanguageVersion expectedVersion)
- {
- // Act
- var languageVersion = DefaultProjectExtensibilityConfigurationFactory.GetLanguageVersion(assemblyIdentity);
-
- // Assert
- Assert.Same(expectedVersion, languageVersion);
- }
-
- [Fact]
- public void GetLanguageVersion_MapsFuture_1_0_VersionsCorrectly()
- {
- // Arrange
- var assemblyIdentity = new AssemblyIdentity("Microsoft.AspNetCore.Razor", new Version("1.3.0.0"));
-
- // Act
- var languageVersion = DefaultProjectExtensibilityConfigurationFactory.GetLanguageVersion(assemblyIdentity);
-
- // Assert
- Assert.Same(RazorLanguageVersion.Version_1_1, languageVersion);
- }
-
- [Fact]
- public void GetLanguageVersion_MapsFuture_2_0_VersionsCorrectly()
- {
- // Arrange
- var assemblyIdentity = new AssemblyIdentity("Microsoft.AspNetCore.Razor", new Version("2.3.0.0"));
-
- // Act
- var languageVersion = DefaultProjectExtensibilityConfigurationFactory.GetLanguageVersion(assemblyIdentity);
-
- // Assert
- Assert.Same(RazorLanguageVersion.Latest, languageVersion);
- }
-
- [Theory]
- [InlineData("1.0.0.0", "1.0.0.0")]
- [InlineData("1.1.0.0", "1.1.0.0")]
- [InlineData("2.0.0.0", "2.0.0.0")]
- [InlineData("2.0.2.0", "2.0.2.0")]
- public void GetConfiguration_FindsSupportedConfiguration_ForNewRazor(string razorVersion, string mvcVersion)
- {
- // Arrange
- var references = new AssemblyIdentity[]
- {
- new AssemblyIdentity("Microsoft.AspNetCore.Razor.Language", new Version(razorVersion)),
- new AssemblyIdentity("Microsoft.AspNetCore.Mvc.Razor", new Version(mvcVersion)),
- };
-
- var factory = new DefaultProjectExtensibilityConfigurationFactory();
-
- // Act
- var result = factory.GetConfiguration(references);
-
- // Assert
- var configuration = Assert.IsType(result);
- Assert.Equal(ProjectExtensibilityConfigurationKind.ApproximateMatch, configuration.Kind);
- Assert.Equal(razorVersion, configuration.RazorAssembly.Identity.Version.ToString());
- Assert.Equal(mvcVersion, configuration.MvcAssembly.Identity.Version.ToString());
- }
-
- [Theory]
- [InlineData("1.0.0.0", "1.0.0.0")]
- [InlineData("1.1.0.0", "1.1.0.0")]
- [InlineData("1.9.9.9", "2.0.0.0")] // MVC version is ignored
- public void GetConfiguration_FindsSupportedConfiguration_ForOldRazor(string razorVersion, string mvcVersion)
- {
- // Arrange
- var references = new AssemblyIdentity[]
- {
- new AssemblyIdentity("Microsoft.AspNetCore.Razor", new Version(razorVersion)),
- new AssemblyIdentity("Microsoft.AspNetCore.Mvc.Razor", new Version(mvcVersion)),
- };
-
- var factory = new DefaultProjectExtensibilityConfigurationFactory();
-
- // Act
- var result = factory.GetConfiguration(references);
-
- // Assert
- var configuration = Assert.IsType(result);
- Assert.Equal(ProjectExtensibilityConfigurationKind.ApproximateMatch, configuration.Kind);
- Assert.Equal(razorVersion, configuration.RazorAssembly.Identity.Version.ToString());
- Assert.Equal(mvcVersion, configuration.MvcAssembly.Identity.Version.ToString());
- }
-
- [Fact]
- public void GetConfiguration_RazorVersion_NewAssemblyWinsOverOld()
- {
- // Arrange
- var references = new AssemblyIdentity[]
- {
- new AssemblyIdentity("Microsoft.AspNetCore.Razor", new Version("1.0.0.0")),
- new AssemblyIdentity("Microsoft.AspNetCore.Razor.Language", new Version("2.0.0.0")),
- new AssemblyIdentity("Microsoft.AspNetCore.Mvc.Razor", new Version("2.0.0.0")),
- };
-
- var factory = new DefaultProjectExtensibilityConfigurationFactory();
-
- // Act
- var result = factory.GetConfiguration(references);
-
- // Assert
- var configuration = Assert.IsType(result);
- Assert.Equal(ProjectExtensibilityConfigurationKind.ApproximateMatch, configuration.Kind);
- Assert.Equal("2.0.0.0", configuration.RazorAssembly.Identity.Version.ToString());
- Assert.Equal("2.0.0.0", configuration.MvcAssembly.Identity.Version.ToString());
- }
-
- [Fact]
- public void GetConfiguration_RazorVersion_OldAssemblyIgnoredPastV1()
- {
- // Arrange
- var references = new AssemblyIdentity[]
- {
- new AssemblyIdentity("Microsoft.AspNetCore.Razor", new Version("2.0.0.0")),
- new AssemblyIdentity("Microsoft.AspNetCore.Mvc.Razor", new Version("2.0.0.0")),
- };
-
- var factory = new DefaultProjectExtensibilityConfigurationFactory();
-
- // Act
- var result = factory.GetConfiguration(references);
-
- // Assert
- var configuration = Assert.IsType(result);
- Assert.Equal(ProjectExtensibilityConfigurationKind.Fallback, configuration.Kind);
- Assert.Equal("2.0.0.0", configuration.RazorAssembly.Identity.Version.ToString());
- Assert.Equal("2.0.0.0", configuration.MvcAssembly.Identity.Version.ToString());
- }
-
- [Fact]
- public void GetConfiguration_NoRazorVersion_ChoosesDefault()
- {
- // Arrange
- var references = new AssemblyIdentity[]
- {
- new AssemblyIdentity("Microsoft.AspNetCore.Mvc.Razor", new Version("2.0.0.0")),
- };
-
- var factory = new DefaultProjectExtensibilityConfigurationFactory();
-
- // Act
- var result = factory.GetConfiguration(references);
-
- // Assert
- var configuration = Assert.IsType(result);
- Assert.Equal(ProjectExtensibilityConfigurationKind.Fallback, configuration.Kind);
- Assert.Equal("2.0.0.0", configuration.RazorAssembly.Identity.Version.ToString());
- Assert.Equal("2.0.0.0", configuration.MvcAssembly.Identity.Version.ToString());
- }
-
- [Fact]
- public void GetConfiguration_UnsupportedRazorVersion_ChoosesDefault()
- {
- // Arrange
- var references = new AssemblyIdentity[]
- {
- new AssemblyIdentity("Microsoft.AspNetCore.Razor.Language", new Version("3.0.0.0")),
- new AssemblyIdentity("Microsoft.AspNetCore.Mvc.Razor", new Version("2.0.0.0")),
- };
-
- var factory = new DefaultProjectExtensibilityConfigurationFactory();
-
- // Act
- var result = factory.GetConfiguration(references);
-
- // Assert
- var configuration = Assert.IsType(result);
- Assert.Equal(ProjectExtensibilityConfigurationKind.Fallback, configuration.Kind);
- Assert.Equal("2.0.0.0", configuration.RazorAssembly.Identity.Version.ToString());
- Assert.Equal("2.0.0.0", configuration.MvcAssembly.Identity.Version.ToString());
- }
-
- [Fact]
- public void GetConfiguration_NoMvcVersion_ChoosesDefault()
- {
- // Arrange
- var references = new AssemblyIdentity[]
- {
- new AssemblyIdentity("Microsoft.AspNetCore.Razor.Language", new Version("2.0.0.0")),
- };
-
- var factory = new DefaultProjectExtensibilityConfigurationFactory();
-
- // Act
- var result = factory.GetConfiguration(references);
-
- // Assert
- var configuration = Assert.IsType(result);
- Assert.Equal(ProjectExtensibilityConfigurationKind.Fallback, configuration.Kind);
- Assert.Equal("2.0.0.0", configuration.RazorAssembly.Identity.Version.ToString());
- Assert.Equal("2.0.0.0", configuration.MvcAssembly.Identity.Version.ToString());
- }
-
- [Fact]
- public void GetConfiguration_UnsupportedMvcVersion_ChoosesDefault()
- {
- // Arrange
- var references = new AssemblyIdentity[]
- {
- new AssemblyIdentity("Microsoft.AspNetCore.Razor.Language", new Version("2.0.0.0")),
- new AssemblyIdentity("Microsoft.AspNetCore.Mvc.Razor", new Version("3.0.0.0")),
- };
-
- var factory = new DefaultProjectExtensibilityConfigurationFactory();
-
- // Act
- var result = factory.GetConfiguration(references);
-
- // Assert
- var configuration = Assert.IsType(result);
- Assert.Equal(ProjectExtensibilityConfigurationKind.Fallback, configuration.Kind);
- Assert.Equal("2.0.0.0", configuration.RazorAssembly.Identity.Version.ToString());
- Assert.Equal("2.0.0.0", configuration.MvcAssembly.Identity.Version.ToString());
- }
- }
-}
diff --git a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultProjectSnapshotManagerTest.cs b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultProjectSnapshotManagerTest.cs
deleted file mode 100644
index eda57ab8ba..0000000000
--- a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultProjectSnapshotManagerTest.cs
+++ /dev/null
@@ -1,331 +0,0 @@
-// Copyright (c) .NET Foundation. All rights reserved.
-// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-
-using System.Collections.Generic;
-using System.Linq;
-using Moq;
-using Xunit;
-
-namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
-{
- public class DefaultProjectSnapshotManagerTest
- {
- public DefaultProjectSnapshotManagerTest()
- {
- Workspace = TestWorkspace.Create();
- ProjectManager = new TestProjectSnapshotManager(Enumerable.Empty(), Workspace);
- }
-
- private TestProjectSnapshotManager ProjectManager { get; }
-
- private Workspace Workspace { get; }
-
- [Fact]
- public void ProjectAdded_AddsProject_NotifiesListeners_AndStartsBackgroundWorker()
- {
- // Arrange
- var project = Workspace.CurrentSolution.AddProject("Test", "Test", LanguageNames.CSharp);
-
- // Act
- ProjectManager.ProjectAdded(project);
-
- // Assert
- var snapshot = ProjectManager.GetSnapshot(project.Id);
- Assert.True(snapshot.IsDirty);
-
- Assert.True(ProjectManager.ListenersNotified);
- Assert.True(ProjectManager.WorkerStarted);
- }
-
- [Fact]
- public void ProjectChanged_MadeDirty_RetainsComputedState_NotifiesListeners_AndStartsBackgroundWorker()
- {
- // Arrange
- var project = Workspace.CurrentSolution.AddProject("Test", "Test", LanguageNames.CSharp);
- ProjectManager.ProjectAdded(project);
- ProjectManager.Reset();
-
- // Adding some computed state
- var configuration = Mock.Of();
- ProjectManager.ProjectUpdated(new ProjectSnapshotUpdateContext(project) { Configuration = configuration });
- ProjectManager.Reset();
-
- project = project.WithAssemblyName("Test1"); // Simulate a project change
-
- // Act
- ProjectManager.ProjectChanged(project);
-
- // Assert
- var snapshot = ProjectManager.GetSnapshot(project.Id);
- Assert.True(snapshot.IsDirty);
- Assert.Same(configuration, snapshot.Configuration);
-
- Assert.False(ProjectManager.ListenersNotified);
- Assert.True(ProjectManager.WorkerStarted);
- }
-
- [Fact]
- public void ProjectChanged_BackgroundUpdate_MadeClean_WithSignificantChanges_NotifiesListeners_AndDoesNotStartBackgroundWorker()
- {
- // Arrange
- var project = Workspace.CurrentSolution.AddProject("Test", "Test", LanguageNames.CSharp);
- ProjectManager.ProjectAdded(project);
- ProjectManager.Reset();
-
- var configuration = Mock.Of();
-
- // Act
- ProjectManager.ProjectUpdated(new ProjectSnapshotUpdateContext(project) { Configuration = configuration });
-
- // Assert
- var snapshot = ProjectManager.GetSnapshot(project.Id);
- Assert.False(snapshot.IsDirty);
- Assert.Same(configuration, snapshot.Configuration);
-
- Assert.True(ProjectManager.ListenersNotified);
- Assert.False(ProjectManager.WorkerStarted);
- }
-
- [Fact]
- public void ProjectChanged_BackgroundUpdate_MadeClean_WithoutSignificantChanges_NotifiesListeners_AndDoesNotStartBackgroundWorker()
- {
- // Arrange
- var project = Workspace.CurrentSolution.AddProject("Test", "Test", LanguageNames.CSharp);
- ProjectManager.ProjectAdded(project);
- ProjectManager.Reset();
-
- var configuration = Mock.Of();
- ProjectManager.ProjectUpdated(new ProjectSnapshotUpdateContext(project) { Configuration = configuration });
- ProjectManager.Reset();
-
- project = project.WithAssemblyName("Test1"); // Simulate a project change
- ProjectManager.ProjectChanged(project);
- ProjectManager.Reset();
-
- // Act
- ProjectManager.ProjectUpdated(new ProjectSnapshotUpdateContext(project) { Configuration = configuration });
-
- // Assert
- var snapshot = ProjectManager.GetSnapshot(project.Id);
- Assert.False(snapshot.IsDirty);
- Assert.Same(configuration, snapshot.Configuration);
-
- Assert.False(ProjectManager.ListenersNotified);
- Assert.False(ProjectManager.WorkerStarted);
- }
-
- [Fact]
- public void ProjectChanged_BackgroundUpdate_StillDirty_WithSignificantChanges_NotifiesListeners_AndStartsBackgroundWorker()
- {
- // Arrange
- var project = Workspace.CurrentSolution.AddProject("Test", "Test", LanguageNames.CSharp);
- ProjectManager.ProjectAdded(project);
- ProjectManager.Reset();
-
- var configuration = Mock.Of();
-
- // Compute an update for "Test"
- var update = new ProjectSnapshotUpdateContext(project) { Configuration = configuration };
-
- project = project.WithAssemblyName("Test1"); // Simulate a project change
- ProjectManager.ProjectChanged(project);
- ProjectManager.Reset();
-
- // Act
- ProjectManager.ProjectUpdated(update);
-
- // Assert
- var snapshot = ProjectManager.GetSnapshot(project.Id);
- Assert.True(snapshot.IsDirty);
- Assert.Same(configuration, snapshot.Configuration);
-
- Assert.True(ProjectManager.ListenersNotified);
- Assert.True(ProjectManager.WorkerStarted);
- }
-
- [Fact]
- public void ProjectChanged_BackgroundUpdate_StillDirty_WithoutSignificantChanges_NotifiesListeners_AndStartsBackgroundWorker()
- {
- // Arrange
- var project = Workspace.CurrentSolution.AddProject("Test", "Test", LanguageNames.CSharp);
- ProjectManager.ProjectAdded(project);
- ProjectManager.Reset();
-
- var configuration = Mock.Of();
- ProjectManager.ProjectUpdated(new ProjectSnapshotUpdateContext(project) { Configuration = configuration });
-
- project = project.WithAssemblyName("Test1"); // Simulate a project change
- ProjectManager.ProjectChanged(project);
- ProjectManager.Reset();
-
- // Compute an update for "Test1"
- var update = new ProjectSnapshotUpdateContext(project) { Configuration = configuration };
-
- project = project.WithAssemblyName("Test2"); // Simulate a project change
- ProjectManager.ProjectChanged(project);
- ProjectManager.Reset();
-
- // Act
- ProjectManager.ProjectUpdated(update); // Still dirty because the project changed while computing the update
-
- // Assert
- var snapshot = ProjectManager.GetSnapshot(project.Id);
- Assert.True(snapshot.IsDirty);
- Assert.Same(configuration, snapshot.Configuration);
-
- Assert.False(ProjectManager.ListenersNotified);
- Assert.True(ProjectManager.WorkerStarted);
- }
-
- [Fact]
- public void ProjectChanged_IgnoresUnknownProject()
- {
- // Arrange
- var project = Workspace.CurrentSolution.AddProject("Test", "Test", LanguageNames.CSharp);
-
- // Act
- ProjectManager.ProjectChanged(project);
-
- // Assert
- Assert.Empty(ProjectManager.Projects);
-
- Assert.False(ProjectManager.ListenersNotified);
- Assert.False(ProjectManager.WorkerStarted);
- }
-
- [Fact]
- public void ProjectChanged_WithComputedState_IgnoresUnknownProject()
- {
- // Arrange
- var project = Workspace.CurrentSolution.AddProject("Test", "Test", LanguageNames.CSharp);
-
- // Act
- ProjectManager.ProjectUpdated(new ProjectSnapshotUpdateContext(project));
-
- // Assert
- Assert.Empty(ProjectManager.Projects);
-
- Assert.False(ProjectManager.ListenersNotified);
- Assert.False(ProjectManager.WorkerStarted);
- }
-
- [Fact]
- public void ProjectBuildComplete_KnownProject_NotifiesBackgroundWorker()
- {
- // Arrange
- var project = Workspace.CurrentSolution.AddProject("Test", "Test", LanguageNames.CSharp);
- ProjectManager.ProjectAdded(project);
- ProjectManager.Reset();
-
- // Act
- ProjectManager.ProjectBuildComplete(project);
-
- // Assert
- Assert.False(ProjectManager.ListenersNotified);
- Assert.True(ProjectManager.WorkerStarted);
- }
-
- [Fact]
- public void ProjectBuildComplete_IgnoresUnknownProject()
- {
- // Arrange
- var project = Workspace.CurrentSolution.AddProject("Test", "Test", LanguageNames.CSharp);
-
- // Act
- ProjectManager.ProjectBuildComplete(project);
-
- // Assert
- Assert.Empty(ProjectManager.Projects);
-
- Assert.False(ProjectManager.ListenersNotified);
- Assert.False(ProjectManager.WorkerStarted);
- }
-
- [Fact]
- public void ProjectRemoved_RemovesProject_NotifiesListeners_DoesNotStartBackgroundWorker()
- {
- // Arrange
- var project = Workspace.CurrentSolution.AddProject("Test", "Test", LanguageNames.CSharp);
-
- ProjectManager.ProjectAdded(project);
- ProjectManager.Reset();
-
- // Act
- ProjectManager.ProjectRemoved(project);
-
- // Assert
- Assert.Empty(ProjectManager.Projects);
-
- Assert.True(ProjectManager.ListenersNotified);
- Assert.False(ProjectManager.WorkerStarted);
- }
-
- [Fact]
- public void ProjectRemoved_IgnoresUnknownProject()
- {
- // Arrange
- var project = Workspace.CurrentSolution.AddProject("Test", "Test", LanguageNames.CSharp);
-
- // Act
- ProjectManager.ProjectRemoved(project);
-
- // Assert
- Assert.Empty(ProjectManager.Projects);
-
- Assert.False(ProjectManager.ListenersNotified);
- Assert.False(ProjectManager.WorkerStarted);
- }
-
- [Fact]
- public void ProjectsCleared_RemovesProject_NotifiesListeners_DoesNotStartBackgroundWorker()
- {
- // Arrange
- var project = Workspace.CurrentSolution.AddProject("Test", "Test", LanguageNames.CSharp);
-
- ProjectManager.ProjectAdded(project);
- ProjectManager.Reset();
-
- // Act
- ProjectManager.ProjectsCleared();
-
- // Assert
- Assert.Empty(ProjectManager.Projects);
-
- Assert.True(ProjectManager.ListenersNotified);
- Assert.False(ProjectManager.WorkerStarted);
- }
-
- private class TestProjectSnapshotManager : DefaultProjectSnapshotManager
- {
- public TestProjectSnapshotManager(IEnumerable triggers, Workspace workspace)
- : base(Mock.Of(), Mock.Of(), Mock.Of(), triggers, workspace)
- {
- }
-
- public bool ListenersNotified { get; private set; }
-
- public bool WorkerStarted { get; private set; }
-
- public DefaultProjectSnapshot GetSnapshot(ProjectId id)
- {
- return Projects.Cast().FirstOrDefault(s => s.UnderlyingProject.Id == id);
- }
-
- public void Reset()
- {
- ListenersNotified = false;
- WorkerStarted = false;
- }
-
- protected override void NotifyListeners(ProjectChangeEventArgs e)
- {
- ListenersNotified = true;
- }
-
- protected override void NotifyBackgroundWorker(Project project)
- {
- WorkerStarted = true;
- }
- }
- }
-}
diff --git a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultProjectSnapshotTest.cs b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultProjectSnapshotTest.cs
index 2a72f46a6c..4a491971a8 100644
--- a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultProjectSnapshotTest.cs
+++ b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultProjectSnapshotTest.cs
@@ -1,13 +1,6 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using Microsoft.AspNetCore.Razor.Language;
-using Moq;
using Xunit;
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
@@ -15,19 +8,20 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
public class DefaultProjectSnapshotTest
{
[Fact]
- public void WithProjectChange_WithProject_CreatesSnapshot_UpdatesUnderlyingProject()
+ public void WithWorkspaceProject_CreatesSnapshot_UpdatesUnderlyingProject()
{
// Arrange
- var underlyingProject = GetProject("Test1");
- var original = new DefaultProjectSnapshot(underlyingProject);
+ var hostProject = new HostProject("Test.cshtml", FallbackRazorConfiguration.MVC_2_0);
+ var workspaceProject = GetWorkspaceProject("Test1");
+ var original = new DefaultProjectSnapshot(hostProject, workspaceProject);
- var anotherProject = GetProject("Test1");
+ var anotherProject = GetWorkspaceProject("Test1");
// Act
- var snapshot = original.WithProjectChange(anotherProject);
+ var snapshot = original.WithWorkspaceProject(anotherProject);
// Assert
- Assert.Same(anotherProject, snapshot.UnderlyingProject);
+ Assert.Same(anotherProject, snapshot.WorkspaceProject);
Assert.Equal(original.ComputedVersion, snapshot.ComputedVersion);
Assert.Equal(original.Configuration, snapshot.Configuration);
}
@@ -36,25 +30,21 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
public void WithProjectChange_WithProject_CreatesSnapshot_UpdatesValues()
{
// Arrange
- var underlyingProject = GetProject("Test1");
- var original = new DefaultProjectSnapshot(underlyingProject);
+ var hostProject = new HostProject("Test.cshtml", FallbackRazorConfiguration.MVC_2_0);
+ var workspaceProject = GetWorkspaceProject("Test1");
+ var original = new DefaultProjectSnapshot(hostProject, workspaceProject);
- var anotherProject = GetProject("Test1");
- var update = new ProjectSnapshotUpdateContext(anotherProject)
- {
- Configuration = Mock.Of(),
- };
+ var anotherProject = GetWorkspaceProject("Test1");
+ var update = new ProjectSnapshotUpdateContext(original.FilePath, hostProject, anotherProject, original.Version);
// Act
- var snapshot = original.WithProjectChange(update);
+ var snapshot = original.WithComputedUpdate(update);
// Assert
- Assert.Same(original.UnderlyingProject, snapshot.UnderlyingProject);
- Assert.Equal(update.UnderlyingProject.Version, snapshot.ComputedVersion);
- Assert.Same(update.Configuration, snapshot.Configuration);
+ Assert.Same(original.WorkspaceProject, snapshot.WorkspaceProject);
}
- private Project GetProject(string name)
+ private Project GetWorkspaceProject(string name)
{
Project project = null;
TestWorkspace.Create(workspace =>
diff --git a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/WorkspaceProjectSnapshotChangeTriggerTest.cs b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/WorkspaceProjectSnapshotChangeTriggerTest.cs
index 293f6e8bf0..86872c08ea 100644
--- a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/WorkspaceProjectSnapshotChangeTriggerTest.cs
+++ b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/WorkspaceProjectSnapshotChangeTriggerTest.cs
@@ -14,33 +14,53 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
public WorkspaceProjectSnapshotChangeTriggerTest()
{
- Solution emptySolution = null;
- Project project1 = null;
- Project project2 = null;
- Project project3 = null;
- Solution solutionWithTwoProjects = null;
- Solution solutionWithOneProject = null;
+ Workspace = TestWorkspace.Create();
+ EmptySolution = Workspace.CurrentSolution.GetIsolatedSolution();
- Workspace = TestWorkspace.Create(ws =>
- {
- emptySolution = ws.CurrentSolution.GetIsolatedSolution();
- project1 = ws.CurrentSolution.AddProject("One", "One", LanguageNames.CSharp);
- project2 = project1.Solution.AddProject("Two", "Two", LanguageNames.CSharp);
- solutionWithTwoProjects = project2.Solution;
+ var projectId1 = ProjectId.CreateNewId("One");
+ var projectId2 = ProjectId.CreateNewId("Two");
+ var projectId3 = ProjectId.CreateNewId("Three");
- project3 = emptySolution.GetIsolatedSolution().AddProject("Three", "Three", LanguageNames.CSharp);
- solutionWithOneProject = project3.Solution;
- });
+ SolutionWithTwoProjects = Workspace.CurrentSolution
+ .AddProject(ProjectInfo.Create(
+ projectId1,
+ VersionStamp.Default,
+ "One",
+ "One",
+ LanguageNames.CSharp,
+ filePath: "One.csproj"))
+ .AddProject(ProjectInfo.Create(
+ projectId2,
+ VersionStamp.Default,
+ "Two",
+ "Two",
+ LanguageNames.CSharp,
+ filePath: "Two.csproj"));
- EmptySolution = emptySolution;
- ProjectNumberOne = project1;
- ProjectNumberTwo = project2;
- ProjectNumberThree = project3;
- SolutionWithTwoProjects = solutionWithTwoProjects;
- SolutionWithOneProject = solutionWithOneProject;
+ SolutionWithOneProject = EmptySolution.GetIsolatedSolution()
+ .AddProject(ProjectInfo.Create(
+ projectId3,
+ VersionStamp.Default,
+ "Three",
+ "Three",
+ LanguageNames.CSharp,
+ filePath: "Three.csproj"));
+ ProjectNumberOne = SolutionWithTwoProjects.GetProject(projectId1);
+ ProjectNumberTwo = SolutionWithTwoProjects.GetProject(projectId2);
+ ProjectNumberThree = SolutionWithOneProject.GetProject(projectId3);
+
+ HostProjectOne = new HostProject("One.csproj", FallbackRazorConfiguration.MVC_1_1);
+ HostProjectTwo = new HostProject("Two.csproj", FallbackRazorConfiguration.MVC_1_1);
+ HostProjectThree = new HostProject("Three.csproj", FallbackRazorConfiguration.MVC_1_1);
}
+ private HostProject HostProjectOne { get; }
+
+ private HostProject HostProjectTwo { get; }
+
+ private HostProject HostProjectThree { get; }
+
private Solution EmptySolution { get; }
private Solution SolutionWithOneProject { get; }
@@ -66,7 +86,9 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Arrange
var trigger = new WorkspaceProjectSnapshotChangeTrigger();
var projectManager = new TestProjectSnapshotManager(new[] { trigger }, Workspace);
-
+ projectManager.HostProjectAdded(HostProjectOne);
+ projectManager.HostProjectAdded(HostProjectTwo);
+
var e = new WorkspaceChangeEventArgs(kind, oldSolution: EmptySolution, newSolution: SolutionWithTwoProjects);
// Act
@@ -74,9 +96,9 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Assert
Assert.Collection(
- projectManager.Projects.OrderBy(p => p.UnderlyingProject.Name),
- p => Assert.Equal(ProjectNumberOne.Id, p.UnderlyingProject.Id),
- p => Assert.Equal(ProjectNumberTwo.Id, p.UnderlyingProject.Id));
+ projectManager.Projects.OrderBy(p => p.WorkspaceProject.Name),
+ p => Assert.Equal(ProjectNumberOne.Id, p.WorkspaceProject.Id),
+ p => Assert.Equal(ProjectNumberTwo.Id, p.WorkspaceProject.Id));
}
[Theory]
@@ -90,21 +112,25 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Arrange
var trigger = new WorkspaceProjectSnapshotChangeTrigger();
var projectManager = new TestProjectSnapshotManager(new[] { trigger }, Workspace);
+ projectManager.HostProjectAdded(HostProjectOne);
+ projectManager.HostProjectAdded(HostProjectTwo);
+ projectManager.HostProjectAdded(HostProjectThree);
// Initialize with a project. This will get removed.
var e = new WorkspaceChangeEventArgs(WorkspaceChangeKind.SolutionAdded, oldSolution: EmptySolution, newSolution: SolutionWithOneProject);
trigger.Workspace_WorkspaceChanged(Workspace, e);
- e = new WorkspaceChangeEventArgs(kind, oldSolution: EmptySolution, newSolution: SolutionWithTwoProjects);
+ e = new WorkspaceChangeEventArgs(kind, oldSolution: SolutionWithOneProject, newSolution: SolutionWithTwoProjects);
// Act
trigger.Workspace_WorkspaceChanged(Workspace, e);
// Assert
Assert.Collection(
- projectManager.Projects.OrderBy(p => p.UnderlyingProject.Name),
- p => Assert.Equal(ProjectNumberOne.Id, p.UnderlyingProject.Id),
- p => Assert.Equal(ProjectNumberTwo.Id, p.UnderlyingProject.Id));
+ projectManager.Projects.OrderBy(p => p.WorkspaceProject?.Name),
+ p => Assert.Null(p.WorkspaceProject),
+ p => Assert.Equal(ProjectNumberOne.Id, p.WorkspaceProject.Id),
+ p => Assert.Equal(ProjectNumberTwo.Id, p.WorkspaceProject.Id));
}
[Theory]
@@ -115,6 +141,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Arrange
var trigger = new WorkspaceProjectSnapshotChangeTrigger();
var projectManager = new TestProjectSnapshotManager(new[] { trigger }, Workspace);
+ projectManager.HostProjectAdded(HostProjectOne);
+ projectManager.HostProjectAdded(HostProjectTwo);
// Initialize with some projects.
var e = new WorkspaceChangeEventArgs(WorkspaceChangeKind.SolutionAdded, oldSolution: EmptySolution, newSolution: SolutionWithTwoProjects);
@@ -128,13 +156,13 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Assert
Assert.Collection(
- projectManager.Projects.OrderBy(p => p.UnderlyingProject.Name),
+ projectManager.Projects.OrderBy(p => p.WorkspaceProject.Name),
p =>
{
- Assert.Equal(ProjectNumberOne.Id, p.UnderlyingProject.Id);
- Assert.Equal("Changed", p.UnderlyingProject.AssemblyName);
+ Assert.Equal(ProjectNumberOne.Id, p.WorkspaceProject.Id);
+ Assert.Equal("Changed", p.WorkspaceProject.AssemblyName);
},
- p => Assert.Equal(ProjectNumberTwo.Id, p.UnderlyingProject.Id));
+ p => Assert.Equal(ProjectNumberTwo.Id, p.WorkspaceProject.Id));
}
[Fact]
@@ -143,6 +171,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Arrange
var trigger = new WorkspaceProjectSnapshotChangeTrigger();
var projectManager = new TestProjectSnapshotManager(new[] { trigger }, Workspace);
+ projectManager.HostProjectAdded(HostProjectOne);
+ projectManager.HostProjectAdded(HostProjectTwo);
// Initialize with some projects project.
var e = new WorkspaceChangeEventArgs(WorkspaceChangeKind.SolutionAdded, oldSolution: EmptySolution, newSolution: SolutionWithTwoProjects);
@@ -156,8 +186,9 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Assert
Assert.Collection(
- projectManager.Projects.OrderBy(p => p.UnderlyingProject.Name),
- p => Assert.Equal(ProjectNumberTwo.Id, p.UnderlyingProject.Id));
+ projectManager.Projects.OrderBy(p => p.WorkspaceProject?.Name),
+ p => Assert.Null(p.WorkspaceProject),
+ p => Assert.Equal(ProjectNumberTwo.Id, p.WorkspaceProject.Id));
}
[Fact]
@@ -166,6 +197,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Arrange
var trigger = new WorkspaceProjectSnapshotChangeTrigger();
var projectManager = new TestProjectSnapshotManager(new[] { trigger }, Workspace);
+ projectManager.HostProjectAdded(HostProjectThree);
var solution = SolutionWithOneProject;
var e = new WorkspaceChangeEventArgs(WorkspaceChangeKind.ProjectAdded, oldSolution: EmptySolution, newSolution: solution, projectId: ProjectNumberThree.Id);
@@ -175,19 +207,21 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Assert
Assert.Collection(
- projectManager.Projects.OrderBy(p => p.UnderlyingProject.Name),
- p => Assert.Equal(ProjectNumberThree.Id, p.UnderlyingProject.Id));
+ projectManager.Projects.OrderBy(p => p.WorkspaceProject.Name),
+ p => Assert.Equal(ProjectNumberThree.Id, p.WorkspaceProject.Id));
}
private class TestProjectSnapshotManager : DefaultProjectSnapshotManager
{
- public TestProjectSnapshotManager(IEnumerable triggers, Workspace workspace)
+ public TestProjectSnapshotManager(IEnumerable triggers, Workspace workspace)
: base(Mock.Of(), Mock.Of(), new TestProjectSnapshotWorker(), triggers, workspace)
{
}
- protected override void NotifyBackgroundWorker(Project project)
+ protected override void NotifyBackgroundWorker(ProjectSnapshotUpdateContext context)
{
+ Assert.NotNull(context.HostProject);
+ Assert.NotNull(context.WorkspaceProject);
}
}
diff --git a/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultProjectEngineFactoryServiceTest.cs b/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultProjectEngineFactoryServiceTest.cs
index 74ede949ac..12ecf74efe 100644
--- a/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultProjectEngineFactoryServiceTest.cs
+++ b/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultProjectEngineFactoryServiceTest.cs
@@ -26,28 +26,31 @@ namespace Microsoft.VisualStudio.Editor.Razor
project = workspace.CurrentSolution.AddProject(info).GetProject(info.Id);
});
- Project = project;
+ WorkspaceProject = project;
+
+ HostProject_For_1_0 = new HostProject("/TestPath/SomePath/Test.csproj", FallbackRazorConfiguration.MVC_1_0);
+ HostProject_For_1_1 = new HostProject("/TestPath/SomePath/Test.csproj", FallbackRazorConfiguration.MVC_1_1);
+ HostProject_For_2_0 = new HostProject("/TestPath/SomePath/Test.csproj", FallbackRazorConfiguration.MVC_2_0);
}
- // We don't actually look at the project, we rely on the ProjectStateManager
- public Project Project { get; }
+ private HostProject HostProject_For_1_0 { get; }
- public Workspace Workspace { get; }
+ private HostProject HostProject_For_1_1 { get; }
+
+ private HostProject HostProject_For_2_0 { get; }
+
+ // We don't actually look at the project, we rely on the ProjectStateManager
+ private Project WorkspaceProject { get; }
+
+ private Workspace Workspace { get; }
[Fact]
public void Create_CreatesTemplateEngine_ForLatest()
{
// Arrange
var projectManager = new TestProjectSnapshotManager(Workspace);
- projectManager.ProjectAdded(Project);
- projectManager.ProjectUpdated(new ProjectSnapshotUpdateContext(Project)
- {
- Configuration = new MvcExtensibilityConfiguration(
- RazorLanguageVersion.Version_2_0,
- ProjectExtensibilityConfigurationKind.ApproximateMatch,
- new ProjectExtensibilityAssembly(new AssemblyIdentity("Microsoft.AspNetCore.Mvc.Razor", new Version("2.0.0.0"))),
- new ProjectExtensibilityAssembly(new AssemblyIdentity("Microsoft.AspNetCore.Razor", new Version("2.0.0.0")))),
- });
+ projectManager.HostProjectAdded(HostProject_For_2_0);
+ projectManager.WorkspaceProjectAdded(WorkspaceProject);
var factoryService = new DefaultProjectEngineFactoryService(projectManager);
@@ -68,15 +71,8 @@ namespace Microsoft.VisualStudio.Editor.Razor
{
// Arrange
var projectManager = new TestProjectSnapshotManager(Workspace);
- projectManager.ProjectAdded(Project);
- projectManager.ProjectUpdated(new ProjectSnapshotUpdateContext(Project)
- {
- Configuration = new MvcExtensibilityConfiguration(
- RazorLanguageVersion.Version_1_1,
- ProjectExtensibilityConfigurationKind.ApproximateMatch,
- new ProjectExtensibilityAssembly(new AssemblyIdentity("Microsoft.AspNetCore.Mvc.Razor", new Version("1.1.3.0"))),
- new ProjectExtensibilityAssembly(new AssemblyIdentity("Microsoft.AspNetCore.Razor", new Version("1.1.3.0")))),
- });
+ projectManager.HostProjectAdded(HostProject_For_1_1);
+ projectManager.WorkspaceProjectAdded(WorkspaceProject);
var factoryService = new DefaultProjectEngineFactoryService(projectManager);
@@ -97,15 +93,8 @@ namespace Microsoft.VisualStudio.Editor.Razor
{
// Arrange
var projectManager = new TestProjectSnapshotManager(Workspace);
- projectManager.ProjectAdded(Project);
- projectManager.ProjectUpdated(new ProjectSnapshotUpdateContext(Project)
- {
- Configuration = new MvcExtensibilityConfiguration(
- RazorLanguageVersion.Version_1_0,
- ProjectExtensibilityConfigurationKind.ApproximateMatch,
- new ProjectExtensibilityAssembly(new AssemblyIdentity("Microsoft.AspNetCore.Mvc.Razor", new Version("1.0.0.0"))),
- new ProjectExtensibilityAssembly(new AssemblyIdentity("Microsoft.AspNetCore.Razor", new Version("1.0.0.0")))),
- });
+ projectManager.HostProjectAdded(HostProject_For_1_0);
+ projectManager.WorkspaceProjectAdded(WorkspaceProject);
var factoryService = new DefaultProjectEngineFactoryService(projectManager);
@@ -121,35 +110,6 @@ namespace Microsoft.VisualStudio.Editor.Razor
Assert.Empty(engine.Engine.Features.OfType());
}
- [Fact]
- public void Create_HigherMvcVersion_UsesLatest()
- {
- // Arrange
- var projectManager = new TestProjectSnapshotManager(Workspace);
- projectManager.ProjectAdded(Project);
- projectManager.ProjectUpdated(new ProjectSnapshotUpdateContext(Project)
- {
- Configuration = new MvcExtensibilityConfiguration(
- RazorLanguageVersion.Latest,
- ProjectExtensibilityConfigurationKind.ApproximateMatch,
- new ProjectExtensibilityAssembly(new AssemblyIdentity("Microsoft.AspNetCore.Mvc.Razor", new Version("3.0.0.0"))),
- new ProjectExtensibilityAssembly(new AssemblyIdentity("Microsoft.AspNetCore.Razor", new Version("3.0.0.0")))),
- });
-
- var factoryService = new DefaultProjectEngineFactoryService(projectManager);
-
- // Act
- var engine = factoryService.Create("/TestPath/SomePath/", b =>
- {
- b.Features.Add(new MyCoolNewFeature());
- });
-
- // Assert
- Assert.Single(engine.Engine.Features.OfType());
- Assert.Single(engine.Engine.Features.OfType());
- Assert.Single(engine.Engine.Features.OfType());
- }
-
[Fact]
public void Create_UnknownProjectPath_UsesLatest()
{
@@ -175,7 +135,8 @@ namespace Microsoft.VisualStudio.Editor.Razor
{
// Arrange
var projectManager = new TestProjectSnapshotManager(Workspace);
- projectManager.ProjectAdded(Project);
+ projectManager.HostProjectAdded(HostProject_For_2_0);
+ projectManager.WorkspaceProjectAdded(WorkspaceProject);
var factoryService = new DefaultProjectEngineFactoryService(projectManager);
diff --git a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/DefaultProjectSnapshotManagerTest.cs b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/DefaultProjectSnapshotManagerTest.cs
new file mode 100644
index 0000000000..ef13312831
--- /dev/null
+++ b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/DefaultProjectSnapshotManagerTest.cs
@@ -0,0 +1,815 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using System.Linq;
+using Moq;
+using Xunit;
+
+namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
+{
+ public class DefaultProjectSnapshotManagerTest : ForegroundDispatcherTestBase
+ {
+ public DefaultProjectSnapshotManagerTest()
+ {
+ HostProject = new HostProject("Test.csproj", FallbackRazorConfiguration.MVC_2_0);
+
+ Workspace = TestWorkspace.Create();
+ ProjectManager = new TestProjectSnapshotManager(Dispatcher, Enumerable.Empty(), Workspace);
+
+ var projectId = ProjectId.CreateNewId("Test");
+ var solution = Workspace.CurrentSolution.AddProject(ProjectInfo.Create(
+ projectId,
+ VersionStamp.Default,
+ "Test",
+ "Test",
+ LanguageNames.CSharp,
+ "Test.csproj"));
+ WorkspaceProject = solution.GetProject(projectId);
+
+ var vbProjectId = ProjectId.CreateNewId("VB");
+ solution = solution.AddProject(ProjectInfo.Create(
+ vbProjectId,
+ VersionStamp.Default,
+ "VB",
+ "VB",
+ LanguageNames.VisualBasic,
+ "VB.vbproj"));
+ VBWorkspaceProject = solution.GetProject(vbProjectId);
+
+ var projectWithoutFilePathId = ProjectId.CreateNewId("NoFile");
+ solution = solution.AddProject(ProjectInfo.Create(
+ projectWithoutFilePathId,
+ VersionStamp.Default,
+ "NoFile",
+ "NoFile",
+ LanguageNames.CSharp));
+ WorkspaceProjectWithoutFilePath = solution.GetProject(projectWithoutFilePathId);
+
+ // Approximates a project with multi-targeting
+ var projectIdWithDifferentTfm = ProjectId.CreateNewId("TestWithDifferentTfm");
+ solution = Workspace.CurrentSolution.AddProject(ProjectInfo.Create(
+ projectIdWithDifferentTfm,
+ VersionStamp.Default,
+ "Test (Different TFM)",
+ "Test",
+ LanguageNames.CSharp,
+ "Test.csproj"));
+ WorkspaceProjectWithDifferentTfm = solution.GetProject(projectIdWithDifferentTfm);
+ }
+
+ private HostProject HostProject { get; }
+
+ private Project WorkspaceProject { get; }
+
+ private Project WorkspaceProjectWithDifferentTfm { get; }
+
+ private Project WorkspaceProjectWithoutFilePath { get; }
+
+ private Project VBWorkspaceProject { get; }
+
+ private TestProjectSnapshotManager ProjectManager { get; }
+
+ private Workspace Workspace { get; }
+
+ [ForegroundFact]
+ public void HostProjectAdded_WithoutWorkspaceProject_NotifiesListeners()
+ {
+ // Arrange
+
+ // Act
+ ProjectManager.HostProjectAdded(HostProject);
+
+ // Assert
+ var snapshot = ProjectManager.GetSnapshot(HostProject);
+ Assert.True(snapshot.IsDirty);
+ Assert.False(snapshot.IsInitialized);
+
+ Assert.True(ProjectManager.ListenersNotified);
+ Assert.False(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void HostProjectAdded_FindsWorkspaceProject_NotifiesListeners_AndStartsBackgroundWorker()
+ {
+ // Arrange
+ Assert.True(Workspace.TryApplyChanges(WorkspaceProject.Solution));
+
+ // Act
+ ProjectManager.HostProjectAdded(HostProject);
+
+ // Assert
+ var snapshot = ProjectManager.GetSnapshot(HostProject);
+ Assert.True(snapshot.IsDirty);
+ Assert.True(snapshot.IsInitialized);
+
+ Assert.True(ProjectManager.ListenersNotified);
+ Assert.True(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void HostProjectChanged_WithoutWorkspaceProject_NotifiesListeners_AndDoesNotStartBackgroundWorker()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.Reset();
+
+ var project = new HostProject(HostProject.FilePath, FallbackRazorConfiguration.MVC_1_0); // Simulate a project change
+
+ // Act
+ ProjectManager.HostProjectChanged(project);
+
+ // Assert
+ var snapshot = ProjectManager.GetSnapshot(HostProject);
+ Assert.True(snapshot.IsDirty);
+ Assert.False(snapshot.IsInitialized);
+
+ Assert.True(ProjectManager.ListenersNotified);
+ Assert.False(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void HostProjectChanged_WithWorkspaceProject_RetainsComputedState_NotifiesListeners_AndStartsBackgroundWorker()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProject);
+ ProjectManager.Reset();
+
+ // Adding some computed state
+ var snapshot = ProjectManager.GetSnapshot(HostProject);
+ var updateContext = snapshot.CreateUpdateContext();
+ ProjectManager.ProjectUpdated(updateContext);
+ ProjectManager.Reset();
+
+ var project = new HostProject(HostProject.FilePath, FallbackRazorConfiguration.MVC_1_0); // Simulate a project change
+
+ // Act
+ ProjectManager.HostProjectChanged(project);
+
+ // Assert
+ snapshot = ProjectManager.GetSnapshot(project);
+ Assert.True(snapshot.IsDirty);
+ Assert.True(snapshot.IsInitialized);
+
+ Assert.True(ProjectManager.ListenersNotified);
+ Assert.True(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void HostProjectChanged_IgnoresUnknownProject()
+ {
+ // Arrange
+
+ // Act
+ ProjectManager.HostProjectChanged(HostProject);
+
+ // Assert
+ Assert.Empty(ProjectManager.Projects);
+
+ Assert.False(ProjectManager.ListenersNotified);
+ Assert.False(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void HostProjectRemoved_RemovesProject_NotifiesListeners()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.Reset();
+
+ // Act
+ ProjectManager.HostProjectRemoved(HostProject);
+
+ // Assert
+ Assert.Empty(ProjectManager.Projects);
+
+ Assert.True(ProjectManager.ListenersNotified);
+ Assert.False(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void ProjectUpdated_WithComputedState_IgnoresUnknownProject()
+ {
+ // Arrange
+
+ // Act
+ ProjectManager.ProjectUpdated(new ProjectSnapshotUpdateContext("Test", HostProject, WorkspaceProject, VersionStamp.Default));
+
+ // Assert
+ Assert.Empty(ProjectManager.Projects);
+
+ Assert.False(ProjectManager.ListenersNotified);
+ Assert.False(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void ProjectUpdated_WhenHostProjectChanged_MadeClean_NotifiesListeners_AndDoesNotStartBackgroundWorker()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProject);
+ ProjectManager.Reset();
+
+ var project = new HostProject(HostProject.FilePath, FallbackRazorConfiguration.MVC_1_0); // Simulate a project change
+ ProjectManager.HostProjectChanged(project);
+ ProjectManager.Reset();
+
+ // Generate the update
+ var snapshot = ProjectManager.GetSnapshot(HostProject);
+ var updateContext = snapshot.CreateUpdateContext();
+
+ // Act
+ ProjectManager.ProjectUpdated(updateContext);
+
+ // Assert
+ snapshot = ProjectManager.GetSnapshot(project);
+ Assert.False(snapshot.IsDirty);
+
+ Assert.True(ProjectManager.ListenersNotified);
+ Assert.False(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void ProjectUpdated_WhenWorkspaceProjectChanged_MadeClean_NotifiesListeners_AndDoesNotStartBackgroundWorker()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProject);
+ ProjectManager.Reset();
+
+ var project = WorkspaceProject.WithAssemblyName("Test1"); // Simulate a project change
+ ProjectManager.WorkspaceProjectChanged(project);
+ ProjectManager.Reset();
+
+ // Generate the update
+ var snapshot = ProjectManager.GetSnapshot(WorkspaceProject);
+ var updateContext = snapshot.CreateUpdateContext();
+
+ // Act
+ ProjectManager.ProjectUpdated(updateContext);
+
+ // Assert
+ snapshot = ProjectManager.GetSnapshot(project);
+ Assert.False(snapshot.IsDirty);
+
+ Assert.True(ProjectManager.ListenersNotified);
+ Assert.False(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void ProjectUpdated_WhenHostProjectChanged_StillDirty_WithSignificantChanges_NotifiesListeners_AndStartsBackgroundWorker()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProject);
+ ProjectManager.Reset();
+
+ // Generate the update
+ var snapshot = ProjectManager.GetSnapshot(HostProject);
+ var updateContext = snapshot.CreateUpdateContext();
+
+ var project = new HostProject(HostProject.FilePath, FallbackRazorConfiguration.MVC_1_0); // Simulate a project change
+ ProjectManager.HostProjectChanged(project);
+ ProjectManager.Reset();
+
+ // Act
+ ProjectManager.ProjectUpdated(updateContext);
+
+ // Assert
+ snapshot = ProjectManager.GetSnapshot(project);
+ Assert.True(snapshot.IsDirty);
+
+ Assert.True(ProjectManager.ListenersNotified);
+ Assert.True(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void WorkspaceProjectChanged_BackgroundUpdate_StillDirty_WithSignificantChanges_NotifiesListeners_AndStartsBackgroundWorker()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProject);
+ ProjectManager.Reset();
+
+ // Generate the update
+ var snapshot = ProjectManager.GetSnapshot(WorkspaceProject);
+ var updateContext = snapshot.CreateUpdateContext();
+
+ var project = WorkspaceProject.WithAssemblyName("Test1"); // Simulate a project change
+ ProjectManager.WorkspaceProjectChanged(project);
+ ProjectManager.Reset();
+
+ // Act
+ ProjectManager.ProjectUpdated(updateContext);
+
+ // Assert
+ snapshot = ProjectManager.GetSnapshot(project);
+ Assert.True(snapshot.IsDirty);
+
+ Assert.True(ProjectManager.ListenersNotified);
+ Assert.True(ProjectManager.WorkerStarted);
+ }
+
+ [Fact(Skip = "We no longer have any background-computed state")]
+ public void ProjectUpdated_WhenHostProjectChanged_StillDirty_WithoutSignificantChanges_DoesNotNotifyListeners_AndStartsBackgroundWorker()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProject);
+ ProjectManager.Reset();
+
+ // Generate an update based on the original state
+ var snapshot = ProjectManager.GetSnapshot(HostProject);
+ var updateContext = snapshot.CreateUpdateContext();
+ ProjectManager.ProjectUpdated(updateContext);
+ ProjectManager.Reset();
+
+ var project = new HostProject(HostProject.FilePath, FallbackRazorConfiguration.MVC_1_0); // Simulate a project change
+ ProjectManager.HostProjectChanged(project);
+ ProjectManager.Reset();
+
+ // Now start computing another update
+ snapshot = ProjectManager.GetSnapshot(HostProject);
+ updateContext = snapshot.CreateUpdateContext();
+
+ project = new HostProject(HostProject.FilePath, FallbackRazorConfiguration.MVC_1_1); // Simulate a project change
+ ProjectManager.HostProjectChanged(project);
+ ProjectManager.Reset();
+
+ // Act
+ ProjectManager.ProjectUpdated(updateContext); // Still dirty because the project changed while computing the update
+
+ // Assert
+ snapshot = ProjectManager.GetSnapshot(project);
+ Assert.True(snapshot.IsDirty);
+
+ Assert.False(ProjectManager.ListenersNotified);
+ Assert.True(ProjectManager.WorkerStarted);
+ }
+
+ [Fact(Skip = "We no longer have any background-computed state")]
+ public void ProjectUpdated_WhenWorkspaceProjectChanged_StillDirty_WithoutSignificantChanges_DoesNotNotifyListeners_AndStartsBackgroundWorker()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProject);
+ ProjectManager.Reset();
+
+ // Generate an update based on the original state
+ var snapshot = ProjectManager.GetSnapshot(HostProject);
+ var updateContext = snapshot.CreateUpdateContext();
+ ProjectManager.ProjectUpdated(updateContext);
+ ProjectManager.Reset();
+
+ var project = WorkspaceProject.WithAssemblyName("Test1"); // Simulate a project change
+ ProjectManager.WorkspaceProjectChanged(project);
+ ProjectManager.Reset();
+
+ // Now start computing another update
+ snapshot = ProjectManager.GetSnapshot(HostProject);
+ updateContext = snapshot.CreateUpdateContext();
+
+ project = project.WithAssemblyName("Test2"); // Simulate a project change
+ ProjectManager.WorkspaceProjectChanged(project);
+ ProjectManager.Reset();
+
+ // Act
+ ProjectManager.ProjectUpdated(updateContext); // Still dirty because the project changed while computing the update
+
+ // Assert
+ snapshot = ProjectManager.GetSnapshot(project);
+ Assert.True(snapshot.IsDirty);
+
+ Assert.False(ProjectManager.ListenersNotified);
+ Assert.True(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void ProjectUpdated_WhenHostProjectRemoved_DiscardsUpdate()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProject);
+ ProjectManager.Reset();
+
+ // Generate the update
+ var snapshot = ProjectManager.GetSnapshot(HostProject);
+ var updateContext = snapshot.CreateUpdateContext();
+
+ ProjectManager.HostProjectRemoved(HostProject);
+ ProjectManager.Reset();
+
+ // Act
+ ProjectManager.ProjectUpdated(updateContext);
+
+ // Assert
+ snapshot = ProjectManager.GetSnapshot(HostProject);
+ Assert.Null(snapshot);
+ }
+
+ [ForegroundFact]
+ public void ProjectUpdated_WhenWorkspaceProjectRemoved_DiscardsUpdate()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProject);
+ ProjectManager.Reset();
+
+ // Generate the update
+ var snapshot = ProjectManager.GetSnapshot(WorkspaceProject);
+ var updateContext = snapshot.CreateUpdateContext();
+
+ ProjectManager.WorkspaceProjectRemoved(WorkspaceProject);
+ ProjectManager.Reset();
+
+ // Act
+ ProjectManager.ProjectUpdated(updateContext);
+
+ // Assert
+ snapshot = ProjectManager.GetSnapshot(WorkspaceProject);
+ Assert.True(snapshot.IsDirty);
+
+ Assert.False(ProjectManager.ListenersNotified);
+ Assert.False(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void ProjectUpdated_BackgroundUpdate_MadeClean_WithSignificantChanges_NotifiesListeners_AndDoesNotStartBackgroundWorker()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProject);
+ ProjectManager.Reset();
+
+ // Generate the update
+ var snapshot = ProjectManager.GetSnapshot(HostProject);
+ var updateContext = snapshot.CreateUpdateContext();
+
+ // Act
+ ProjectManager.ProjectUpdated(updateContext);
+
+ // Assert
+ snapshot = ProjectManager.GetSnapshot(WorkspaceProject);
+ Assert.False(snapshot.IsDirty);
+
+ Assert.True(ProjectManager.ListenersNotified);
+ Assert.False(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void WorkspaceProjectAdded_WithoutHostProject_IgnoresWorkspaceProject()
+ {
+ // Arrange
+
+ // Act
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProject);
+
+ // Assert
+ Assert.Empty(ProjectManager.Projects);
+
+ Assert.False(ProjectManager.ListenersNotified);
+ Assert.False(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void WorkspaceProjectAdded_IgnoresNonCSharpProject()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.Reset();
+
+ // Act
+ ProjectManager.WorkspaceProjectAdded(VBWorkspaceProject);
+
+ // Assert
+ var snapshot = ProjectManager.GetSnapshot(WorkspaceProject);
+ Assert.False(snapshot.IsInitialized);
+
+ Assert.False(ProjectManager.ListenersNotified);
+ Assert.False(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void WorkspaceProjectAdded_IgnoresSecondProjectWithSameFilePath()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProject);
+ ProjectManager.Reset();
+
+ // Act
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProjectWithDifferentTfm);
+
+ // Assert
+ var snapshot = ProjectManager.GetSnapshot(WorkspaceProject);
+ Assert.Same(WorkspaceProject, snapshot.WorkspaceProject);
+
+ Assert.False(ProjectManager.ListenersNotified);
+ Assert.False(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void WorkspaceProjectAdded_IgnoresProjectWithoutFilePath()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.Reset();
+
+ // Act
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProjectWithoutFilePath);
+
+ // Assert
+ var snapshot = ProjectManager.GetSnapshot(WorkspaceProject);
+ Assert.False(snapshot.IsInitialized);
+
+ Assert.False(ProjectManager.ListenersNotified);
+ Assert.False(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void WorkspaceProjectAdded_WithHostProject_NotifiesListenters_AndStartsBackgroundWorker()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.Reset();
+
+ // Act
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProject);
+
+ // Assert
+ var snapshot = ProjectManager.GetSnapshot(WorkspaceProject);
+ Assert.True(snapshot.IsDirty);
+ Assert.True(snapshot.IsInitialized);
+
+ Assert.True(ProjectManager.ListenersNotified);
+ Assert.True(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void WorkspaceProjectChanged_WithoutHostProject_IgnoresWorkspaceProject()
+ {
+ // Arrange
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProject);
+ ProjectManager.Reset();
+
+ var project = WorkspaceProject.WithAssemblyName("Test1"); // Simulate a project change
+
+ // Act
+ ProjectManager.WorkspaceProjectChanged(project);
+
+ // Assert
+ Assert.Empty(ProjectManager.Projects);
+
+ Assert.False(ProjectManager.ListenersNotified);
+ Assert.False(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void WorkspaceProjectChanged_IgnoresNonCSharpProject()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.WorkspaceProjectAdded(VBWorkspaceProject);
+ ProjectManager.Reset();
+
+ var project = VBWorkspaceProject.WithAssemblyName("Test1"); // Simulate a project change
+
+ // Act
+ ProjectManager.WorkspaceProjectChanged(project);
+
+ // Assert
+ var snapshot = ProjectManager.GetSnapshot(WorkspaceProject);
+ Assert.False(snapshot.IsInitialized);
+
+ Assert.False(ProjectManager.ListenersNotified);
+ Assert.False(ProjectManager.WorkerStarted);
+ }
+
+
+ [ForegroundFact]
+ public void WorkspaceProjectChanged_IgnoresProjectWithoutFilePath()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProjectWithoutFilePath);
+ ProjectManager.Reset();
+
+ var project = WorkspaceProjectWithoutFilePath.WithAssemblyName("Test1"); // Simulate a project change
+
+ // Act
+ ProjectManager.WorkspaceProjectChanged(project);
+
+ // Assert
+ var snapshot = ProjectManager.GetSnapshot(WorkspaceProject);
+ Assert.False(snapshot.IsInitialized);
+
+ Assert.False(ProjectManager.ListenersNotified);
+ Assert.False(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void WorkspaceProjectChanged_IgnoresSecondProjectWithSameFilePath()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProject);
+ ProjectManager.Reset();
+
+ // Act
+ ProjectManager.WorkspaceProjectChanged(WorkspaceProjectWithDifferentTfm);
+
+ // Assert
+ var snapshot = ProjectManager.GetSnapshot(WorkspaceProject);
+ Assert.Same(WorkspaceProject, snapshot.WorkspaceProject);
+
+ Assert.False(ProjectManager.ListenersNotified);
+ Assert.False(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void WorkspaceProjectChanged_MadeDirty_RetainsComputedState_NotifiesListeners_AndStartsBackgroundWorker()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProject);
+ ProjectManager.Reset();
+
+ // Generate the update
+ var snapshot = ProjectManager.GetSnapshot(HostProject);
+ var updateContext = snapshot.CreateUpdateContext();
+ ProjectManager.ProjectUpdated(updateContext);
+ ProjectManager.Reset();
+
+ var project = WorkspaceProject.WithAssemblyName("Test1"); // Simulate a project change
+
+ // Act
+ ProjectManager.WorkspaceProjectChanged(project);
+
+ // Assert
+ snapshot = ProjectManager.GetSnapshot(project);
+ Assert.True(snapshot.IsDirty);
+
+ Assert.False(ProjectManager.ListenersNotified);
+ Assert.True(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void WorkspaceProjectRemoved_WithHostProject_DoesNotRemoveProject()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProject);
+ ProjectManager.Reset();
+
+ // Act
+ ProjectManager.WorkspaceProjectRemoved(WorkspaceProject);
+
+ // Assert
+ var snapshot = ProjectManager.GetSnapshot(WorkspaceProject);
+ Assert.True(snapshot.IsDirty);
+ Assert.False(snapshot.IsInitialized);
+
+ Assert.True(ProjectManager.ListenersNotified);
+ Assert.False(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void WorkspaceProjectRemoved_WithHostProject_FallsBackToSecondProject()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProject);
+ ProjectManager.Reset();
+
+ // Sets up a solution where the which has WorkspaceProjectWithDifferentTfm but not WorkspaceProject
+ // This will enable us to fall back and find the WorkspaceProjectWithDifferentTfm
+ Assert.True(Workspace.TryApplyChanges(WorkspaceProjectWithDifferentTfm.Solution));
+
+ // Act
+ ProjectManager.WorkspaceProjectRemoved(WorkspaceProject);
+
+ // Assert
+ var snapshot = ProjectManager.GetSnapshot(WorkspaceProject);
+ Assert.True(snapshot.IsDirty);
+ Assert.True(snapshot.IsInitialized);
+ Assert.Equal(WorkspaceProjectWithDifferentTfm.Id, snapshot.WorkspaceProject.Id);
+
+ Assert.True(ProjectManager.ListenersNotified);
+ Assert.True(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void WorkspaceProjectRemoved_IgnoresSecondProjectWithSameFilePath()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProject);
+ ProjectManager.Reset();
+
+ // Act
+ ProjectManager.WorkspaceProjectRemoved(WorkspaceProjectWithDifferentTfm);
+
+ // Assert
+ var snapshot = ProjectManager.GetSnapshot(WorkspaceProject);
+ Assert.Same(WorkspaceProject, snapshot.WorkspaceProject);
+
+ Assert.False(ProjectManager.ListenersNotified);
+ Assert.False(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void WorkspaceProjectRemoved_IgnoresNonCSharpProject()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.WorkspaceProjectAdded(VBWorkspaceProject);
+ ProjectManager.Reset();
+
+ // Act
+ ProjectManager.WorkspaceProjectRemoved(VBWorkspaceProject);
+
+ // Assert
+ var snapshot = ProjectManager.GetSnapshot(WorkspaceProject);
+ Assert.False(snapshot.IsInitialized);
+
+ Assert.False(ProjectManager.ListenersNotified);
+ Assert.False(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void WorkspaceProjectRemoved_IgnoresProjectWithoutFilePath()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProjectWithoutFilePath);
+ ProjectManager.Reset();
+
+ // Act
+ ProjectManager.WorkspaceProjectRemoved(WorkspaceProjectWithoutFilePath);
+
+ // Assert
+ var snapshot = ProjectManager.GetSnapshot(WorkspaceProject);
+ Assert.False(snapshot.IsInitialized);
+
+ Assert.False(ProjectManager.ListenersNotified);
+ Assert.False(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void WorkspaceProjectRemoved_IgnoresUnknownProject()
+ {
+ // Arrange
+
+ // Act
+ ProjectManager.WorkspaceProjectRemoved(WorkspaceProject);
+
+ // Assert
+ Assert.Empty(ProjectManager.Projects);
+
+ Assert.False(ProjectManager.ListenersNotified);
+ Assert.False(ProjectManager.WorkerStarted);
+ }
+
+ private class TestProjectSnapshotManager : DefaultProjectSnapshotManager
+ {
+ public TestProjectSnapshotManager(ForegroundDispatcher dispatcher, IEnumerable triggers, Workspace workspace)
+ : base(dispatcher, Mock.Of(), Mock.Of(), triggers, workspace)
+ {
+ }
+
+ public bool ListenersNotified { get; private set; }
+
+ public bool WorkerStarted { get; private set; }
+
+ public DefaultProjectSnapshot GetSnapshot(HostProject hostProject)
+ {
+ return Projects.Cast().FirstOrDefault(s => s.FilePath == hostProject.FilePath);
+ }
+
+ public DefaultProjectSnapshot GetSnapshot(Project workspaceProject)
+ {
+ return Projects.Cast().FirstOrDefault(s => s.FilePath == workspaceProject.FilePath);
+ }
+
+ public void Reset()
+ {
+ ListenersNotified = false;
+ WorkerStarted = false;
+ }
+
+ protected override void NotifyListeners(ProjectChangeEventArgs e)
+ {
+ ListenersNotified = true;
+ }
+
+ protected override void NotifyBackgroundWorker(ProjectSnapshotUpdateContext context)
+ {
+ Assert.NotNull(context.HostProject);
+ Assert.NotNull(context.WorkspaceProject);
+
+ WorkerStarted = true;
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/DefaultRazorProjectHostTest.cs b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/DefaultRazorProjectHostTest.cs
new file mode 100644
index 0000000000..6234bfb557
--- /dev/null
+++ b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/DefaultRazorProjectHostTest.cs
@@ -0,0 +1,456 @@
+// 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.Threading.Tasks;
+using Microsoft.AspNetCore.Razor.Language;
+using Microsoft.VisualStudio.LanguageServices.Razor;
+using Microsoft.VisualStudio.ProjectSystem;
+using Moq;
+using Xunit;
+
+namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
+{
+ public class DefaultRazorProjectHostTest : ForegroundDispatcherTestBase
+ {
+ public DefaultRazorProjectHostTest()
+ {
+ Workspace = new AdhocWorkspace();
+ ProjectManager = new TestProjectSnapshotManager(Dispatcher, Workspace);
+ }
+
+ private TestProjectSnapshotManager ProjectManager { get; }
+
+ private Workspace Workspace { get; }
+
+ [ForegroundFact]
+ public async Task DefaultRazorProjectHost_ForegroundThread_CreateAndDispose_Succeeds()
+ {
+ // Arrange
+ var services = new TestProjectSystemServices("Test.csproj");
+ var host = new DefaultRazorProjectHost(services, Workspace, ProjectManager);
+
+ // Act & Assert
+ await host.LoadAsync();
+ Assert.Empty(ProjectManager.Projects);
+
+ await host.DisposeAsync();
+ Assert.Empty(ProjectManager.Projects);
+ }
+
+ [ForegroundFact]
+ public async Task DefaultRazorProjectHost_BackgroundThread_CreateAndDispose_Succeeds()
+ {
+ // Arrange
+ var services = new TestProjectSystemServices("Test.csproj");
+ var host = new DefaultRazorProjectHost(services, Workspace, ProjectManager);
+
+ // Act & Assert
+ await Task.Run(async () => await host.LoadAsync());
+ Assert.Empty(ProjectManager.Projects);
+
+ await Task.Run(async () => await host.DisposeAsync());
+ Assert.Empty(ProjectManager.Projects);
+ }
+
+ [ForegroundFact]
+ public async Task OnProjectChanged_ReadsProperties_InitializesProject()
+ {
+ // Arrange
+ var changes = new TestProjectChangeDescription[]
+ {
+ new TestProjectChangeDescription()
+ {
+ RuleName = Rules.RazorGeneral.SchemaName,
+ After = TestProjectRuleSnapshot.CreateProperties(Rules.RazorGeneral.SchemaName, new Dictionary()
+ {
+ { Rules.RazorGeneral.RazorLangVersionProperty, "2.1" },
+ { Rules.RazorGeneral.RazorDefaultConfigurationProperty, "MVC-2.1" },
+ }),
+ },
+ new TestProjectChangeDescription()
+ {
+ RuleName = Rules.RazorConfiguration.SchemaName,
+ After = TestProjectRuleSnapshot.CreateItems(Rules.RazorConfiguration.SchemaName, new Dictionary>()
+ {
+ { "MVC-2.1", new Dictionary() { { "Extensions", "MVC-2.1;Another-Thing" }, } },
+ })
+ },
+ new TestProjectChangeDescription()
+ {
+ RuleName = Rules.RazorExtension.SchemaName,
+ After = TestProjectRuleSnapshot.CreateItems(Rules.RazorExtension.SchemaName, new Dictionary>()
+ {
+ { "MVC-2.1", new Dictionary(){ } },
+ { "Another-Thing", new Dictionary(){ } },
+ })
+ }
+ };
+
+ var services = new TestProjectSystemServices("Test.csproj");
+
+ var host = new DefaultRazorProjectHost(services, Workspace, ProjectManager);
+
+ await Task.Run(async () => await host.LoadAsync());
+ Assert.Empty(ProjectManager.Projects);
+
+ // Act
+ await Task.Run(async () => await host.OnProjectChanged(services.CreateUpdate(changes)));
+
+ // Assert
+ var snapshot = Assert.Single(ProjectManager.Projects);
+ Assert.Equal("Test.csproj", snapshot.FilePath);
+
+ Assert.Equal(RazorLanguageVersion.Version_2_1, snapshot.Configuration.LanguageVersion);
+ Assert.Equal("MVC-2.1", snapshot.Configuration.ConfigurationName);
+ Assert.Collection(
+ snapshot.Configuration.Extensions,
+ e => Assert.Equal("MVC-2.1", e.ExtensionName),
+ e => Assert.Equal("Another-Thing", e.ExtensionName));
+
+ await Task.Run(async () => await host.DisposeAsync());
+ Assert.Empty(ProjectManager.Projects);
+ }
+
+ [ForegroundFact]
+ public async Task OnProjectChanged_NoVersionFound_DoesNotIniatializeProject()
+ {
+ // Arrange
+ var changes = new TestProjectChangeDescription[]
+ {
+ new TestProjectChangeDescription()
+ {
+ RuleName = Rules.RazorGeneral.SchemaName,
+ After = TestProjectRuleSnapshot.CreateProperties(Rules.RazorGeneral.SchemaName, new Dictionary()
+ {
+ { Rules.RazorGeneral.RazorLangVersionProperty, "" },
+ { Rules.RazorGeneral.RazorDefaultConfigurationProperty, "" },
+ }),
+ },
+ new TestProjectChangeDescription()
+ {
+ RuleName = Rules.RazorConfiguration.SchemaName,
+ After = TestProjectRuleSnapshot.CreateItems(Rules.RazorConfiguration.SchemaName, new Dictionary>()
+ {
+ })
+ },
+ new TestProjectChangeDescription()
+ {
+ RuleName = Rules.RazorExtension.SchemaName,
+ After = TestProjectRuleSnapshot.CreateItems(Rules.RazorExtension.SchemaName, new Dictionary>()
+ {
+ })
+ }
+ };
+
+ var services = new TestProjectSystemServices("Test.csproj");
+
+ var host = new DefaultRazorProjectHost(services, Workspace, ProjectManager);
+
+ await Task.Run(async () => await host.LoadAsync());
+ Assert.Empty(ProjectManager.Projects);
+
+ // Act
+ await Task.Run(async () => await host.OnProjectChanged(services.CreateUpdate(changes)));
+
+ // Assert
+ Assert.Empty(ProjectManager.Projects);
+
+ await Task.Run(async () => await host.DisposeAsync());
+ Assert.Empty(ProjectManager.Projects);
+ }
+
+ [ForegroundFact]
+ public async Task OnProjectChanged_UpdateProject_Succeeds()
+ {
+ // Arrange
+ var changes = new TestProjectChangeDescription[]
+ {
+ new TestProjectChangeDescription()
+ {
+ RuleName = Rules.RazorGeneral.SchemaName,
+ After = TestProjectRuleSnapshot.CreateProperties(Rules.RazorGeneral.SchemaName, new Dictionary()
+ {
+ { Rules.RazorGeneral.RazorLangVersionProperty, "2.1" },
+ { Rules.RazorGeneral.RazorDefaultConfigurationProperty, "MVC-2.1" },
+ }),
+ },
+ new TestProjectChangeDescription()
+ {
+ RuleName = Rules.RazorConfiguration.SchemaName,
+ After = TestProjectRuleSnapshot.CreateItems(Rules.RazorConfiguration.SchemaName, new Dictionary>()
+ {
+ { "MVC-2.1", new Dictionary() { { "Extensions", "MVC-2.1;Another-Thing" }, } },
+ })
+ },
+ new TestProjectChangeDescription()
+ {
+ RuleName = Rules.RazorExtension.SchemaName,
+ After = TestProjectRuleSnapshot.CreateItems(Rules.RazorExtension.SchemaName, new Dictionary>()
+ {
+ { "MVC-2.1", new Dictionary(){ } },
+ { "Another-Thing", new Dictionary(){ } },
+ })
+ }
+ };
+
+ var services = new TestProjectSystemServices("Test.csproj");
+
+ var host = new DefaultRazorProjectHost(services, Workspace, ProjectManager);
+
+ await Task.Run(async () => await host.LoadAsync());
+ Assert.Empty(ProjectManager.Projects);
+
+ // Act - 1
+ await Task.Run(async () => await host.OnProjectChanged(services.CreateUpdate(changes)));
+
+ // Assert - 1
+ var snapshot = Assert.Single(ProjectManager.Projects);
+ Assert.Equal("Test.csproj", snapshot.FilePath);
+
+ Assert.Equal(RazorLanguageVersion.Version_2_1, snapshot.Configuration.LanguageVersion);
+ Assert.Equal("MVC-2.1", snapshot.Configuration.ConfigurationName);
+ Assert.Collection(
+ snapshot.Configuration.Extensions,
+ e => Assert.Equal("MVC-2.1", e.ExtensionName),
+ e => Assert.Equal("Another-Thing", e.ExtensionName));
+
+ // Act - 2
+ changes[0].After.SetProperty(Rules.RazorGeneral.RazorLangVersionProperty, "2.0");
+ changes[0].After.SetProperty(Rules.RazorGeneral.RazorDefaultConfigurationProperty, "MVC-2.0");
+ changes[1].After.SetItem("MVC-2.0", new Dictionary() { { "Extensions", "MVC-2.0;Another-Thing" }, });
+ changes[2].After.SetItem("MVC-2.0", new Dictionary());
+
+ await Task.Run(async () => await host.OnProjectChanged(services.CreateUpdate(changes)));
+
+ // Assert - 2
+ snapshot = Assert.Single(ProjectManager.Projects);
+ Assert.Equal("Test.csproj", snapshot.FilePath);
+
+ Assert.Equal(RazorLanguageVersion.Version_2_0, snapshot.Configuration.LanguageVersion);
+ Assert.Equal("MVC-2.0", snapshot.Configuration.ConfigurationName);
+ Assert.Collection(
+ snapshot.Configuration.Extensions,
+ e => Assert.Equal("MVC-2.0", e.ExtensionName),
+ e => Assert.Equal("Another-Thing", e.ExtensionName));
+
+ await Task.Run(async () => await host.DisposeAsync());
+ Assert.Empty(ProjectManager.Projects);
+ }
+
+ [ForegroundFact]
+ public async Task OnProjectChanged_VersionRemoved_DeinitializesProject()
+ {
+ // Arrange
+ var changes = new TestProjectChangeDescription[]
+ {
+ new TestProjectChangeDescription()
+ {
+ RuleName = Rules.RazorGeneral.SchemaName,
+ After = TestProjectRuleSnapshot.CreateProperties(Rules.RazorGeneral.SchemaName, new Dictionary()
+ {
+ { Rules.RazorGeneral.RazorLangVersionProperty, "2.1" },
+ { Rules.RazorGeneral.RazorDefaultConfigurationProperty, "MVC-2.1" },
+ }),
+ },
+ new TestProjectChangeDescription()
+ {
+ RuleName = Rules.RazorConfiguration.SchemaName,
+ After = TestProjectRuleSnapshot.CreateItems(Rules.RazorConfiguration.SchemaName, new Dictionary>()
+ {
+ { "MVC-2.1", new Dictionary() { { "Extensions", "MVC-2.1;Another-Thing" }, } },
+ })
+ },
+ new TestProjectChangeDescription()
+ {
+ RuleName = Rules.RazorExtension.SchemaName,
+ After = TestProjectRuleSnapshot.CreateItems(Rules.RazorExtension.SchemaName, new Dictionary>()
+ {
+ { "MVC-2.1", new Dictionary(){ } },
+ { "Another-Thing", new Dictionary(){ } },
+ })
+ }
+ };
+
+ var services = new TestProjectSystemServices("Test.csproj");
+
+ var host = new DefaultRazorProjectHost(services, Workspace, ProjectManager);
+
+ await Task.Run(async () => await host.LoadAsync());
+ Assert.Empty(ProjectManager.Projects);
+
+ // Act - 1
+ await Task.Run(async () => await host.OnProjectChanged(services.CreateUpdate(changes)));
+
+ // Assert - 1
+ var snapshot = Assert.Single(ProjectManager.Projects);
+ Assert.Equal("Test.csproj", snapshot.FilePath);
+
+ Assert.Equal(RazorLanguageVersion.Version_2_1, snapshot.Configuration.LanguageVersion);
+ Assert.Equal("MVC-2.1", snapshot.Configuration.ConfigurationName);
+ Assert.Collection(
+ snapshot.Configuration.Extensions,
+ e => Assert.Equal("MVC-2.1", e.ExtensionName),
+ e => Assert.Equal("Another-Thing", e.ExtensionName));
+
+ // Act - 2
+ changes[0].After.SetProperty(Rules.RazorGeneral.RazorLangVersionProperty, "");
+ changes[0].After.SetProperty(Rules.RazorGeneral.RazorDefaultConfigurationProperty, "");
+
+ await Task.Run(async () => await host.OnProjectChanged(services.CreateUpdate(changes)));
+
+ // Assert - 2
+ Assert.Empty(ProjectManager.Projects);
+
+ await Task.Run(async () => await host.DisposeAsync());
+ Assert.Empty(ProjectManager.Projects);
+ }
+
+ [ForegroundFact]
+ public async Task OnProjectChanged_AfterDispose_IgnoresUpdate()
+ {
+ // Arrange
+ var changes = new TestProjectChangeDescription[]
+ {
+ new TestProjectChangeDescription()
+ {
+ RuleName = Rules.RazorGeneral.SchemaName,
+ After = TestProjectRuleSnapshot.CreateProperties(Rules.RazorGeneral.SchemaName, new Dictionary()
+ {
+ { Rules.RazorGeneral.RazorLangVersionProperty, "2.1" },
+ { Rules.RazorGeneral.RazorDefaultConfigurationProperty, "MVC-2.1" },
+ }),
+ },
+ new TestProjectChangeDescription()
+ {
+ RuleName = Rules.RazorConfiguration.SchemaName,
+ After = TestProjectRuleSnapshot.CreateItems(Rules.RazorConfiguration.SchemaName, new Dictionary>()
+ {
+ { "MVC-2.1", new Dictionary() { { "Extensions", "MVC-2.1;Another-Thing" }, } },
+ })
+ },
+ new TestProjectChangeDescription()
+ {
+ RuleName = Rules.RazorExtension.SchemaName,
+ After = TestProjectRuleSnapshot.CreateItems(Rules.RazorExtension.SchemaName, new Dictionary>()
+ {
+ { "MVC-2.1", new Dictionary(){ } },
+ { "Another-Thing", new Dictionary(){ } },
+ })
+ }
+ };
+
+ var services = new TestProjectSystemServices("Test.csproj");
+
+ var host = new DefaultRazorProjectHost(services, Workspace, ProjectManager);
+
+ await Task.Run(async () => await host.LoadAsync());
+ Assert.Empty(ProjectManager.Projects);
+
+ // Act - 1
+ await Task.Run(async () => await host.OnProjectChanged(services.CreateUpdate(changes)));
+
+ // Assert - 1
+ var snapshot = Assert.Single(ProjectManager.Projects);
+ Assert.Equal("Test.csproj", snapshot.FilePath);
+
+ Assert.Equal(RazorLanguageVersion.Version_2_1, snapshot.Configuration.LanguageVersion);
+ Assert.Equal("MVC-2.1", snapshot.Configuration.ConfigurationName);
+ Assert.Collection(
+ snapshot.Configuration.Extensions,
+ e => Assert.Equal("MVC-2.1", e.ExtensionName),
+ e => Assert.Equal("Another-Thing", e.ExtensionName));
+
+ // Act - 2
+ await Task.Run(async () => await host.DisposeAsync());
+
+ // Assert - 2
+ Assert.Empty(ProjectManager.Projects);
+
+ // Act - 3
+ changes[0].After.SetProperty(Rules.RazorGeneral.RazorLangVersionProperty, "2.0");
+ changes[0].After.SetProperty(Rules.RazorGeneral.RazorDefaultConfigurationProperty, "MVC-2.0");
+ changes[1].After.SetItem("MVC-2.0", new Dictionary() { { "Extensions", "MVC-2.0;Another-Thing" }, });
+
+ await Task.Run(async () => await host.OnProjectChanged(services.CreateUpdate(changes)));
+
+ // Assert - 3
+ Assert.Empty(ProjectManager.Projects);
+ }
+
+ [ForegroundFact]
+ public async Task OnProjectRenamed_RemovesHostProject_CopiesConfiguration()
+ {
+ // Arrange
+ var changes = new TestProjectChangeDescription[]
+ {
+ new TestProjectChangeDescription()
+ {
+ RuleName = Rules.RazorGeneral.SchemaName,
+ After = TestProjectRuleSnapshot.CreateProperties(Rules.RazorGeneral.SchemaName, new Dictionary()
+ {
+ { Rules.RazorGeneral.RazorLangVersionProperty, "2.1" },
+ { Rules.RazorGeneral.RazorDefaultConfigurationProperty, "MVC-2.1" },
+ }),
+ },
+ new TestProjectChangeDescription()
+ {
+ RuleName = Rules.RazorConfiguration.SchemaName,
+ After = TestProjectRuleSnapshot.CreateItems(Rules.RazorConfiguration.SchemaName, new Dictionary>()
+ {
+ { "MVC-2.1", new Dictionary() { { "Extensions", "MVC-2.1;Another-Thing" }, } },
+ })
+ },
+ new TestProjectChangeDescription()
+ {
+ RuleName = Rules.RazorExtension.SchemaName,
+ After = TestProjectRuleSnapshot.CreateItems(Rules.RazorExtension.SchemaName, new Dictionary>()
+ {
+ { "MVC-2.1", new Dictionary(){ } },
+ { "Another-Thing", new Dictionary(){ } },
+ })
+ }
+ };
+
+ var services = new TestProjectSystemServices("Test.csproj");
+
+ var host = new DefaultRazorProjectHost(services, Workspace, ProjectManager);
+
+ await Task.Run(async () => await host.LoadAsync());
+ Assert.Empty(ProjectManager.Projects);
+
+ // Act - 1
+ await Task.Run(async () => await host.OnProjectChanged(services.CreateUpdate(changes)));
+
+ // Assert - 1
+ var snapshot = Assert.Single(ProjectManager.Projects);
+ Assert.Equal("Test.csproj", snapshot.FilePath);
+ Assert.Same("MVC-2.1", snapshot.Configuration.ConfigurationName);
+
+ // Act - 2
+ services.UnconfiguredProject.FullPath = "Test2.csproj";
+ await Task.Run(async () => await host.OnProjectRenamingAsync());
+
+ // Assert - 1
+ snapshot = Assert.Single(ProjectManager.Projects);
+ Assert.Equal("Test2.csproj", snapshot.FilePath);
+ Assert.Same("MVC-2.1", snapshot.Configuration.ConfigurationName);
+
+ await Task.Run(async () => await host.DisposeAsync());
+ Assert.Empty(ProjectManager.Projects);
+ }
+
+ private class TestProjectSnapshotManager : DefaultProjectSnapshotManager
+ {
+ public TestProjectSnapshotManager(ForegroundDispatcher dispatcher, Workspace workspace)
+ : base(dispatcher, Mock.Of(), Mock.Of(), Array.Empty(), workspace)
+ {
+ }
+
+ protected override void NotifyBackgroundWorker(ProjectSnapshotUpdateContext context)
+ {
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/FallbackRazorProjectHostTest.cs b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/FallbackRazorProjectHostTest.cs
new file mode 100644
index 0000000000..04c910800b
--- /dev/null
+++ b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/FallbackRazorProjectHostTest.cs
@@ -0,0 +1,373 @@
+// 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.Threading.Tasks;
+using Microsoft.VisualStudio.LanguageServices.Razor;
+using Microsoft.VisualStudio.ProjectSystem;
+using Moq;
+using Xunit;
+
+namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
+{
+ public class FallbackRazorProjectHostTest : ForegroundDispatcherTestBase
+ {
+ public FallbackRazorProjectHostTest()
+ {
+ Workspace = new AdhocWorkspace();
+ ProjectManager = new TestProjectSnapshotManager(Dispatcher, Workspace);
+ }
+
+ private TestProjectSnapshotManager ProjectManager { get; }
+
+ private Workspace Workspace { get; }
+
+ [ForegroundFact]
+ public async Task FallbackRazorProjectHost_ForegroundThread_CreateAndDispose_Succeeds()
+ {
+ // Arrange
+ var services = new TestProjectSystemServices("Test.csproj");
+ var host = new TestFallbackRazorProjectHost(services, Workspace, ProjectManager);
+
+ // Act & Assert
+ await host.LoadAsync();
+ Assert.Empty(ProjectManager.Projects);
+
+ await host.DisposeAsync();
+ Assert.Empty(ProjectManager.Projects);
+ }
+
+ [ForegroundFact]
+ public async Task FallbackRazorProjectHost_BackgroundThread_CreateAndDispose_Succeeds()
+ {
+ // Arrange
+ var services = new TestProjectSystemServices("Test.csproj");
+ var host = new TestFallbackRazorProjectHost(services, Workspace, ProjectManager);
+
+ // Act & Assert
+ await Task.Run(async () => await host.LoadAsync());
+ Assert.Empty(ProjectManager.Projects);
+
+ await Task.Run(async () => await host.DisposeAsync());
+ Assert.Empty(ProjectManager.Projects);
+ }
+
+ [ForegroundFact]
+ public async Task OnProjectChanged_ReadsProperties_InitializesProject()
+ {
+ // Arrange
+ var changes = new TestProjectChangeDescription[]
+ {
+ new TestProjectChangeDescription()
+ {
+ RuleName = ManageProjectSystemSchema.ResolvedCompilationReference.SchemaName,
+ After = TestProjectRuleSnapshot.CreateItems(ManageProjectSystemSchema.ResolvedCompilationReference.SchemaName, new Dictionary>()
+ {
+ { "c:\\nuget\\Microsoft.AspNetCore.Mvc.razor.dll", new Dictionary() },
+ }),
+ },
+ };
+
+ var services = new TestProjectSystemServices("Test.csproj");
+
+ var host = new TestFallbackRazorProjectHost(services, Workspace, ProjectManager)
+ {
+ AssemblyVersion = new Version(2, 0), // Mock for reading the assembly's version
+ };
+
+ await Task.Run(async () => await host.LoadAsync());
+ Assert.Empty(ProjectManager.Projects);
+
+ // Act
+ await Task.Run(async () => await host.OnProjectChanged(services.CreateUpdate(changes)));
+
+ // Assert
+ var snapshot = Assert.Single(ProjectManager.Projects);
+ Assert.Equal("Test.csproj", snapshot.FilePath);
+ Assert.Same(FallbackRazorConfiguration.MVC_2_0, snapshot.Configuration);
+
+ await Task.Run(async () => await host.DisposeAsync());
+ Assert.Empty(ProjectManager.Projects);
+ }
+
+ [ForegroundFact]
+ public async Task OnProjectChanged_NoAssemblyFound_DoesNotIniatializeProject()
+ {
+ // Arrange
+ var changes = new TestProjectChangeDescription[]
+ {
+ new TestProjectChangeDescription()
+ {
+ RuleName = ManageProjectSystemSchema.ResolvedCompilationReference.SchemaName,
+ After = TestProjectRuleSnapshot.CreateItems(ManageProjectSystemSchema.ResolvedCompilationReference.SchemaName, new Dictionary>()
+ {
+ }),
+ },
+
+ };
+ var services = new TestProjectSystemServices("Test.csproj");
+
+ var host = new TestFallbackRazorProjectHost(services, Workspace, ProjectManager);
+
+ await Task.Run(async () => await host.LoadAsync());
+ Assert.Empty(ProjectManager.Projects);
+
+ // Act
+ await Task.Run(async () => await host.OnProjectChanged(services.CreateUpdate(changes)));
+
+ // Assert
+ Assert.Empty(ProjectManager.Projects);
+
+ await Task.Run(async () => await host.DisposeAsync());
+ Assert.Empty(ProjectManager.Projects);
+ }
+
+ [ForegroundFact]
+ public async Task OnProjectChanged_AssemblyFoundButCannotReadVersion_DoesNotIniatializeProject()
+ {
+ // Arrange
+ var changes = new TestProjectChangeDescription[]
+ {
+ new TestProjectChangeDescription()
+ {
+ RuleName = ManageProjectSystemSchema.ResolvedCompilationReference.SchemaName,
+ After = TestProjectRuleSnapshot.CreateItems(ManageProjectSystemSchema.ResolvedCompilationReference.SchemaName, new Dictionary>()
+ {
+ { "c:\\nuget\\Microsoft.AspNetCore.Mvc.razor.dll", new Dictionary() },
+ }),
+ },
+ };
+
+ var services = new TestProjectSystemServices("Test.csproj");
+
+ var host = new TestFallbackRazorProjectHost(services, Workspace, ProjectManager);
+
+ await Task.Run(async () => await host.LoadAsync());
+ Assert.Empty(ProjectManager.Projects);
+
+ // Act
+ await Task.Run(async () => await host.OnProjectChanged(services.CreateUpdate(changes)));
+
+ // Assert
+ Assert.Empty(ProjectManager.Projects);
+
+ await Task.Run(async () => await host.DisposeAsync());
+ Assert.Empty(ProjectManager.Projects);
+ }
+
+ [ForegroundFact]
+ public async Task OnProjectChanged_UpdateProject_Succeeds()
+ {
+ // Arrange
+ var changes = new TestProjectChangeDescription[]
+ {
+ new TestProjectChangeDescription()
+ {
+ RuleName = ManageProjectSystemSchema.ResolvedCompilationReference.SchemaName,
+ After = TestProjectRuleSnapshot.CreateItems(ManageProjectSystemSchema.ResolvedCompilationReference.SchemaName, new Dictionary>()
+ {
+ { "c:\\nuget\\Microsoft.AspNetCore.Mvc.razor.dll", new Dictionary() },
+ }),
+ },
+ };
+
+ var services = new TestProjectSystemServices("Test.csproj");
+
+ var host = new TestFallbackRazorProjectHost(services, Workspace, ProjectManager)
+ {
+ AssemblyVersion = new Version(2, 0),
+ };
+
+ await Task.Run(async () => await host.LoadAsync());
+ Assert.Empty(ProjectManager.Projects);
+
+ // Act - 1
+ await Task.Run(async () => await host.OnProjectChanged(services.CreateUpdate(changes)));
+
+ // Assert - 1
+ var snapshot = Assert.Single(ProjectManager.Projects);
+ Assert.Equal("Test.csproj", snapshot.FilePath);
+ Assert.Same(FallbackRazorConfiguration.MVC_2_0, snapshot.Configuration);
+
+ // Act - 2
+ host.AssemblyVersion = new Version(1, 0);
+ await Task.Run(async () => await host.OnProjectChanged(services.CreateUpdate(changes)));
+
+ // Assert - 2
+ snapshot = Assert.Single(ProjectManager.Projects);
+ Assert.Equal("Test.csproj", snapshot.FilePath);
+ Assert.Same(FallbackRazorConfiguration.MVC_1_0, snapshot.Configuration);
+
+ await Task.Run(async () => await host.DisposeAsync());
+ Assert.Empty(ProjectManager.Projects);
+ }
+
+ [ForegroundFact]
+ public async Task OnProjectChanged_VersionRemoved_DeinitializesProject()
+ {
+ // Arrange
+ var changes = new TestProjectChangeDescription[]
+ {
+ new TestProjectChangeDescription()
+ {
+ RuleName = ManageProjectSystemSchema.ResolvedCompilationReference.SchemaName,
+ After = TestProjectRuleSnapshot.CreateItems(ManageProjectSystemSchema.ResolvedCompilationReference.SchemaName, new Dictionary>()
+ {
+ { "c:\\nuget\\Microsoft.AspNetCore.Mvc.razor.dll", new Dictionary() },
+ }),
+ },
+ };
+
+ var services = new TestProjectSystemServices("Test.csproj");
+
+ var host = new TestFallbackRazorProjectHost(services, Workspace, ProjectManager)
+ {
+ AssemblyVersion = new Version(2, 0),
+ };
+
+ await Task.Run(async () => await host.LoadAsync());
+ Assert.Empty(ProjectManager.Projects);
+
+ // Act - 1
+ await Task.Run(async () => await host.OnProjectChanged(services.CreateUpdate(changes)));
+
+ // Assert - 1
+ var snapshot = Assert.Single(ProjectManager.Projects);
+ Assert.Equal("Test.csproj", snapshot.FilePath);
+ Assert.Same(FallbackRazorConfiguration.MVC_2_0, snapshot.Configuration);
+
+ // Act - 2
+ host.AssemblyVersion= null;
+ await Task.Run(async () => await host.OnProjectChanged(services.CreateUpdate(changes)));
+
+ // Assert - 2
+ Assert.Empty(ProjectManager.Projects);
+
+ await Task.Run(async () => await host.DisposeAsync());
+ Assert.Empty(ProjectManager.Projects);
+ }
+
+ [ForegroundFact]
+ public async Task OnProjectChanged_AfterDispose_IgnoresUpdate()
+ {
+ // Arrange
+ var changes = new TestProjectChangeDescription[]
+ {
+ new TestProjectChangeDescription()
+ {
+ RuleName = ManageProjectSystemSchema.ResolvedCompilationReference.SchemaName,
+ After = TestProjectRuleSnapshot.CreateItems(ManageProjectSystemSchema.ResolvedCompilationReference.SchemaName, new Dictionary>()
+ {
+ { "c:\\nuget\\Microsoft.AspNetCore.Mvc.razor.dll", new Dictionary() },
+ }),
+ },
+ };
+
+ var services = new TestProjectSystemServices("Test.csproj");
+
+ var host = new TestFallbackRazorProjectHost(services, Workspace, ProjectManager)
+ {
+ AssemblyVersion = new Version(2, 0),
+ };
+
+ await Task.Run(async () => await host.LoadAsync());
+ Assert.Empty(ProjectManager.Projects);
+
+ // Act - 1
+ await Task.Run(async () => await host.OnProjectChanged(services.CreateUpdate(changes)));
+
+ // Assert - 1
+ var snapshot = Assert.Single(ProjectManager.Projects);
+ Assert.Equal("Test.csproj", snapshot.FilePath);
+ Assert.Same(FallbackRazorConfiguration.MVC_2_0, snapshot.Configuration);
+
+ // Act - 2
+ await Task.Run(async () => await host.DisposeAsync());
+
+ // Assert - 2
+ Assert.Empty(ProjectManager.Projects);
+
+ // Act - 3
+ host.AssemblyVersion = new Version(1, 1);
+ await Task.Run(async () => await host.OnProjectChanged(services.CreateUpdate(changes)));
+
+ // Assert - 3
+ Assert.Empty(ProjectManager.Projects);
+ }
+
+ [ForegroundFact]
+ public async Task OnProjectRenamed_RemovesHostProject_CopiesConfiguration()
+ {
+ // Arrange
+ var changes = new TestProjectChangeDescription[]
+ {
+ new TestProjectChangeDescription()
+ {
+ RuleName = ManageProjectSystemSchema.ResolvedCompilationReference.SchemaName,
+ After = TestProjectRuleSnapshot.CreateItems(ManageProjectSystemSchema.ResolvedCompilationReference.SchemaName, new Dictionary>()
+ {
+ { "c:\\nuget\\Microsoft.AspNetCore.Mvc.razor.dll", new Dictionary() },
+ }),
+ },
+ };
+
+ var services = new TestProjectSystemServices("Test.csproj");
+
+ var host = new TestFallbackRazorProjectHost(services, Workspace, ProjectManager)
+ {
+ AssemblyVersion = new Version(2, 0), // Mock for reading the assembly's version
+ };
+
+ await Task.Run(async () => await host.LoadAsync());
+ Assert.Empty(ProjectManager.Projects);
+
+ // Act - 1
+ await Task.Run(async () => await host.OnProjectChanged(services.CreateUpdate(changes)));
+
+ // Assert - 1
+ var snapshot = Assert.Single(ProjectManager.Projects);
+ Assert.Equal("Test.csproj", snapshot.FilePath);
+ Assert.Same(FallbackRazorConfiguration.MVC_2_0, snapshot.Configuration);
+
+ // Act - 2
+ services.UnconfiguredProject.FullPath = "Test2.csproj";
+ await Task.Run(async () => await host.OnProjectRenamingAsync());
+
+ // Assert - 1
+ snapshot = Assert.Single(ProjectManager.Projects);
+ Assert.Equal("Test2.csproj", snapshot.FilePath);
+ Assert.Same(FallbackRazorConfiguration.MVC_2_0, snapshot.Configuration);
+
+ await Task.Run(async () => await host.DisposeAsync());
+ Assert.Empty(ProjectManager.Projects);
+ }
+
+ private class TestFallbackRazorProjectHost : FallbackRazorProjectHost
+ {
+ internal TestFallbackRazorProjectHost(IUnconfiguredProjectCommonServices commonServices, Workspace workspace, ProjectSnapshotManagerBase projectManager)
+ : base(commonServices, workspace, projectManager)
+ {
+ }
+
+ public Version AssemblyVersion { get; set; }
+
+ protected override Version GetAssemblyVersion(string filePath)
+ {
+ return AssemblyVersion;
+ }
+ }
+
+ private class TestProjectSnapshotManager : DefaultProjectSnapshotManager
+ {
+ public TestProjectSnapshotManager(ForegroundDispatcher dispatcher, Workspace workspace)
+ : base(dispatcher, Mock.Of(), Mock.Of(), Array.Empty(), workspace)
+ {
+ }
+
+ protected override void NotifyBackgroundWorker(ProjectSnapshotUpdateContext context)
+ {
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/ProjectSnapshotWorkerQueueTest.cs b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/ProjectSnapshotWorkerQueueTest.cs
index f4129b8bd6..70ff58db57 100644
--- a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/ProjectSnapshotWorkerQueueTest.cs
+++ b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/ProjectSnapshotWorkerQueueTest.cs
@@ -16,30 +16,54 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
public ProjectSnapshotWorkerQueueTest()
{
- Project project1 = null;
- Project project2 = null;
+ HostProject1 = new HostProject("Test1.csproj", FallbackRazorConfiguration.MVC_1_0);
+ HostProject2 = new HostProject("Test2.csproj", FallbackRazorConfiguration.MVC_1_0);
- Workspace = TestWorkspace.Create(workspace =>
- {
- project1 = workspace.CurrentSolution.AddProject("Test1", "Test1", LanguageNames.CSharp);
- project2 = workspace.CurrentSolution.AddProject("Test2", "Test2", LanguageNames.CSharp);
- });
+ Workspace = TestWorkspace.Create();
- Project1 = project1;
- Project2 = project2;
+ var projectId1 = ProjectId.CreateNewId("Test1");
+ var projectId2 = ProjectId.CreateNewId("Test2");
+
+ var solution = Workspace.CurrentSolution
+ .AddProject(ProjectInfo.Create(
+ projectId1,
+ VersionStamp.Default,
+ "Test1",
+ "Test1",
+ LanguageNames.CSharp,
+ "Test1.csproj"))
+ .AddProject(ProjectInfo.Create(
+ projectId2,
+ VersionStamp.Default,
+ "Test2",
+ "Test2",
+ LanguageNames.CSharp,
+ "Test2.csproj")); ;
+
+ WorkspaceProject1 = solution.GetProject(projectId1);
+ WorkspaceProject2 = solution.GetProject(projectId2);
}
- public Project Project1 { get; }
+ private HostProject HostProject1 { get; }
- public Project Project2 { get; }
+ private HostProject HostProject2 { get; }
- public Workspace Workspace { get; }
+ private Project WorkspaceProject1 { get; }
+
+ private Project WorkspaceProject2 { get; }
+
+ private Workspace Workspace { get; }
[ForegroundFact]
public async Task Queue_ProcessesNotifications_AndGoesBackToSleep()
{
// Arrange
var projectManager = new TestProjectSnapshotManager(Dispatcher, Workspace);
+ projectManager.HostProjectAdded(HostProject1);
+ projectManager.HostProjectAdded(HostProject2);
+ projectManager.WorkspaceProjectAdded(WorkspaceProject1);
+ projectManager.WorkspaceProjectAdded(WorkspaceProject2);
+
var projectWorker = new TestProjectSnapshotWorker();
var queue = new ProjectSnapshotWorkerQueue(Dispatcher, projectManager, projectWorker)
@@ -51,10 +75,10 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
};
// Act & Assert
- queue.Enqueue(Project1);
+ queue.Enqueue(projectManager.GetSnapshot(HostProject1).CreateUpdateContext());
- Assert.True(queue.IsScheduledOrRunning);
- Assert.True(queue.HasPendingNotifications);
+ Assert.True(queue.IsScheduledOrRunning, "Queue should be scheduled during Enqueue");
+ Assert.True(queue.HasPendingNotifications, "Queue should have a notification created during Enqueue");
// Allow the background work to proceed.
queue.BlockBackgroundWorkStart.Set();
@@ -62,8 +86,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Get off the foreground thread and allow the updates to flow through.
await Task.Run(() => queue.NotifyForegroundWorkFinish.Wait(TimeSpan.FromSeconds(1)));
- Assert.False(queue.IsScheduledOrRunning);
- Assert.False(queue.HasPendingNotifications);
+ Assert.False(queue.IsScheduledOrRunning, "Queue should not have restarted");
+ Assert.False(queue.HasPendingNotifications, "Queue should have processed all notifications");
}
[ForegroundFact]
@@ -71,6 +95,11 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
// Arrange
var projectManager = new TestProjectSnapshotManager(Dispatcher, Workspace);
+ projectManager.HostProjectAdded(HostProject1);
+ projectManager.HostProjectAdded(HostProject2);
+ projectManager.WorkspaceProjectAdded(WorkspaceProject1);
+ projectManager.WorkspaceProjectAdded(WorkspaceProject2);
+
var projectWorker = new TestProjectSnapshotWorker();
var queue = new ProjectSnapshotWorkerQueue(Dispatcher, projectManager, projectWorker)
@@ -82,20 +111,20 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
};
// Act & Assert
- queue.Enqueue(Project1);
+ queue.Enqueue(projectManager.GetSnapshot(HostProject1).CreateUpdateContext());
- Assert.True(queue.IsScheduledOrRunning);
- Assert.True(queue.HasPendingNotifications);
+ Assert.True(queue.IsScheduledOrRunning, "Queue should be scheduled during Enqueue");
+ Assert.True(queue.HasPendingNotifications, "Queue should have a notification created during Enqueue");
// Allow the background work to proceed.
queue.BlockBackgroundWorkStart.Set();
queue.NotifyBackgroundWorkFinish.Wait(); // Block the foreground thread so we can queue another notification.
- Assert.True(queue.IsScheduledOrRunning);
- Assert.False(queue.HasPendingNotifications);
+ Assert.True(queue.IsScheduledOrRunning, "Worker should be processing now");
+ Assert.False(queue.HasPendingNotifications, "Worker should have taken all notifications");
- queue.Enqueue(Project2);
+ queue.Enqueue(projectManager.GetSnapshot(HostProject2).CreateUpdateContext());
Assert.True(queue.HasPendingNotifications); // Now we should see the worker restart when it finishes.
@@ -106,17 +135,17 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
queue.NotifyForegroundWorkFinish.Reset();
// It should start running again right away.
- Assert.True(queue.IsScheduledOrRunning);
- Assert.True(queue.HasPendingNotifications);
+ Assert.True(queue.IsScheduledOrRunning, "Queue should be scheduled during Enqueue");
+ Assert.True(queue.HasPendingNotifications, "Queue should have a notification created during Enqueue");
// Allow the background work to proceed.
queue.BlockBackgroundWorkStart.Set();
// Get off the foreground thread and allow the updates to flow through.
await Task.Run(() => queue.NotifyForegroundWorkFinish.Wait(TimeSpan.FromSeconds(1)));
-
- Assert.False(queue.IsScheduledOrRunning);
- Assert.False(queue.HasPendingNotifications);
+
+ Assert.False(queue.IsScheduledOrRunning, "Queue should not have restarted");
+ Assert.False(queue.HasPendingNotifications, "Queue should have processed all notifications");
}
private class TestProjectSnapshotManager : DefaultProjectSnapshotManager
@@ -126,17 +155,24 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
}
- public DefaultProjectSnapshot GetSnapshot(ProjectId id)
+ public DefaultProjectSnapshot GetSnapshot(HostProject hostProject)
{
- return Projects.Cast().FirstOrDefault(s => s.UnderlyingProject.Id == id);
+ return Projects.Cast().FirstOrDefault(s => s.FilePath == hostProject.FilePath);
+ }
+
+ public DefaultProjectSnapshot GetSnapshot(Project workspaceProject)
+ {
+ return Projects.Cast().FirstOrDefault(s => s.FilePath == workspaceProject.FilePath);
}
protected override void NotifyListeners(ProjectChangeEventArgs e)
{
}
- protected override void NotifyBackgroundWorker(Project project)
+ protected override void NotifyBackgroundWorker(ProjectSnapshotUpdateContext context)
{
+ Assert.NotNull(context.HostProject);
+ Assert.NotNull(context.WorkspaceProject);
}
}
diff --git a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/TestAssemblyReference.cs b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/TestAssemblyReference.cs
new file mode 100644
index 0000000000..b80f5e85ba
--- /dev/null
+++ b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/TestAssemblyReference.cs
@@ -0,0 +1,68 @@
+// 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.Reflection;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.ProjectSystem.Properties;
+
+namespace Microsoft.VisualStudio.ProjectSystem.References
+{
+ internal class TestAssemblyReference : IAssemblyReference
+ {
+ public AssemblyName AssemblyName { get; set; }
+
+ public string FullPath { get; set; }
+
+ public IProjectProperties Metadata => throw new System.NotImplementedException();
+
+ public Task GetAssemblyNameAsync()
+ {
+ return Task.FromResult(AssemblyName);
+ }
+
+ public Task GetCopyLocalAsync()
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public Task GetCopyLocalSatelliteAssembliesAsync()
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public Task GetDescriptionAsync()
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public Task GetFullPathAsync()
+ {
+ return Task.FromResult(FullPath);
+ }
+
+ public Task GetNameAsync()
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public Task GetReferenceOutputAssemblyAsync()
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public Task GetRequiredTargetFrameworkAsync()
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public Task GetSpecificVersionAsync()
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public Task IsWinMDFileAsync()
+ {
+ throw new System.NotImplementedException();
+ }
+ }
+}
diff --git a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/TestProjectChangeDescription.cs b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/TestProjectChangeDescription.cs
new file mode 100644
index 0000000000..a6aa3b21d5
--- /dev/null
+++ b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/TestProjectChangeDescription.cs
@@ -0,0 +1,24 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information
+
+using Microsoft.VisualStudio.ProjectSystem.Properties;
+
+namespace Microsoft.VisualStudio.ProjectSystem
+{
+ internal class TestProjectChangeDescription : IProjectChangeDescription
+ {
+ public string RuleName { get; set; }
+
+ public TestProjectRuleSnapshot Before { get; set; }
+
+ public IProjectChangeDiff Difference { get; set; }
+
+ public TestProjectRuleSnapshot After { get; set; }
+
+ IProjectRuleSnapshot IProjectChangeDescription.Before => Before;
+
+ IProjectChangeDiff IProjectChangeDescription.Difference => Difference;
+
+ IProjectRuleSnapshot IProjectChangeDescription.After => After;
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/TestProjectRuleSnapshot.cs b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/TestProjectRuleSnapshot.cs
new file mode 100644
index 0000000000..4239470863
--- /dev/null
+++ b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/TestProjectRuleSnapshot.cs
@@ -0,0 +1,61 @@
+// 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.Collections.Immutable;
+using Microsoft.VisualStudio.ProjectSystem.Properties;
+
+namespace Microsoft.VisualStudio.ProjectSystem
+{
+ internal class TestProjectRuleSnapshot : IProjectRuleSnapshot
+ {
+ public static TestProjectRuleSnapshot CreateProperties(string ruleName, Dictionary properties)
+ {
+ return new TestProjectRuleSnapshot(
+ ruleName,
+ items: ImmutableDictionary>.Empty,
+ properties: properties.ToImmutableDictionary(),
+ dataSourceVersions: ImmutableDictionary.Empty);
+ }
+
+ public static TestProjectRuleSnapshot CreateItems(string ruleName, Dictionary> items)
+ {
+ return new TestProjectRuleSnapshot(
+ ruleName,
+ items: items.ToImmutableDictionary(kvp => kvp.Key, kvp => (IImmutableDictionary)kvp.Value.ToImmutableDictionary()),
+ properties: ImmutableDictionary.Empty,
+ dataSourceVersions: ImmutableDictionary.Empty);
+ }
+
+ public TestProjectRuleSnapshot(
+ string ruleName,
+ IImmutableDictionary> items,
+ IImmutableDictionary properties,
+ IImmutableDictionary dataSourceVersions)
+ {
+ RuleName = ruleName;
+ Items = items;
+ Properties = properties;
+ DataSourceVersions = dataSourceVersions;
+ }
+
+ public void SetProperty(string key, string value)
+ {
+ Properties = Properties.SetItem(key, value);
+ }
+
+ public void SetItem(string key, Dictionary values)
+ {
+ Items = Items.SetItem(key, values.ToImmutableDictionary());
+ }
+
+ public string RuleName { get; }
+
+ public IImmutableDictionary> Items { get; set; }
+
+ public IImmutableDictionary Properties { get; set; }
+
+ public IImmutableDictionary DataSourceVersions { get; }
+ }
+}
diff --git a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/TestProjectSystemServices.cs b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/TestProjectSystemServices.cs
new file mode 100644
index 0000000000..161600f625
--- /dev/null
+++ b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/TestProjectSystemServices.cs
@@ -0,0 +1,799 @@
+// 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.Collections.Immutable;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Threading.Tasks.Dataflow;
+using System.Xml;
+using Microsoft.Build.Execution;
+using Microsoft.Build.Framework;
+using Microsoft.Build.Framework.XamlTypes;
+using Microsoft.VisualStudio.Composition;
+using Microsoft.VisualStudio.ProjectSystem;
+using Microsoft.VisualStudio.ProjectSystem.Build;
+using Microsoft.VisualStudio.ProjectSystem.Properties;
+using Microsoft.VisualStudio.ProjectSystem.References;
+using Microsoft.VisualStudio.Threading;
+using Moq;
+
+namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
+{
+ internal class TestProjectSystemServices : IUnconfiguredProjectCommonServices
+ {
+ public TestProjectSystemServices(string fullPath, params TestPropertyData[] data)
+ {
+ ProjectService = new TestProjectService();
+ ThreadingService = ProjectService.Services.ThreadingPolicy;
+
+ UnconfiguredProject = new TestUnconfiguredProject(ProjectService, fullPath);
+ ProjectService.LoadedUnconfiguredProjects.Add(UnconfiguredProject);
+
+ ActiveConfiguredProject = new TestConfiguredProject(UnconfiguredProject, data);
+ UnconfiguredProject.LoadedConfiguredProjects.Add(ActiveConfiguredProject);
+
+ ActiveConfiguredProjectAssemblyReferences = new TestAssemblyReferencesService();
+ ActiveConfiguredProjectRazorProperties = new Rules.RazorProjectProperties(ActiveConfiguredProject, UnconfiguredProject);
+ ActiveConfiguredProjectSubscription = new TestActiveConfiguredProjectSubscriptionService();
+ }
+
+ public TestProjectServices Services { get; }
+
+ public TestProjectService ProjectService { get; }
+
+ public TestUnconfiguredProject UnconfiguredProject { get; }
+
+ public TestConfiguredProject ActiveConfiguredProject { get; }
+
+ public TestAssemblyReferencesService ActiveConfiguredProjectAssemblyReferences { get; }
+
+ public Rules.RazorProjectProperties ActiveConfiguredProjectRazorProperties { get; }
+
+ public TestActiveConfiguredProjectSubscriptionService ActiveConfiguredProjectSubscription { get; }
+
+ public TestThreadingService ThreadingService { get; }
+
+ ConfiguredProject IUnconfiguredProjectCommonServices.ActiveConfiguredProject => ActiveConfiguredProject;
+
+ IAssemblyReferencesService IUnconfiguredProjectCommonServices.ActiveConfiguredProjectAssemblyReferences => ActiveConfiguredProjectAssemblyReferences;
+
+ IPackageReferencesService IUnconfiguredProjectCommonServices.ActiveConfiguredProjectPackageReferences => throw new NotImplementedException();
+
+ Rules.RazorProjectProperties IUnconfiguredProjectCommonServices.ActiveConfiguredProjectRazorProperties => ActiveConfiguredProjectRazorProperties;
+
+ IActiveConfiguredProjectSubscriptionService IUnconfiguredProjectCommonServices.ActiveConfiguredProjectSubscription => ActiveConfiguredProjectSubscription;
+
+ IProjectThreadingService IUnconfiguredProjectCommonServices.ThreadingService => ThreadingService;
+
+ UnconfiguredProject IUnconfiguredProjectCommonServices.UnconfiguredProject => UnconfiguredProject;
+
+ public IProjectVersionedValue CreateUpdate(params TestProjectChangeDescription[] descriptions)
+ {
+ return new ProjectVersionedValue(
+ value: new ProjectSubscriptionUpdate(
+ projectChanges: descriptions.ToImmutableDictionary(d => d.RuleName, d => (IProjectChangeDescription)d),
+ projectConfiguration: ActiveConfiguredProject.ProjectConfiguration),
+ dataSourceVersions: ImmutableDictionary.Empty);
+ }
+
+ public class TestProjectServices : IProjectServices
+ {
+ public TestProjectServices(TestProjectService projectService)
+ {
+ ProjectService = projectService;
+ ThreadingPolicy = new TestThreadingService();
+ }
+
+ public TestProjectService ProjectService { get; }
+
+ public TestThreadingService ThreadingPolicy { get; }
+
+ IProjectLockService IProjectServices.ProjectLockService => throw new NotImplementedException();
+
+ IProjectThreadingService IProjectServices.ThreadingPolicy => ThreadingPolicy;
+
+ IProjectFaultHandlerService IProjectServices.FaultHandler => throw new NotImplementedException();
+
+ IProjectReloader IProjectServices.ProjectReloader => throw new NotImplementedException();
+
+ ExportProvider IProjectCommonServices.ExportProvider => throw new NotImplementedException();
+
+ IProjectDataSourceRegistry IProjectCommonServices.DataSourceRegistry => throw new NotImplementedException();
+
+ IProjectService IProjectCommonServices.ProjectService => ProjectService;
+
+ IProjectCapabilitiesScope IProjectCommonServices.Capabilities => throw new NotImplementedException();
+ }
+
+ public class TestProjectService : IProjectService
+ {
+ public TestProjectService()
+ {
+ LoadedUnconfiguredProjects = new List();
+ Services = new TestProjectServices(this);
+ }
+
+ public List LoadedUnconfiguredProjects { get; }
+
+ public TestProjectServices Services { get; }
+
+ IEnumerable IProjectService.LoadedUnconfiguredProjects => throw new NotImplementedException();
+
+ IProjectServices IProjectService.Services => Services;
+
+ IProjectCapabilitiesScope IProjectService.Capabilities => throw new NotImplementedException();
+
+ Task IProjectService.LoadProjectAsync(string projectLocation, IImmutableSet projectCapabilities)
+ {
+ throw new NotImplementedException();
+ }
+
+ Task IProjectService.LoadProjectAsync(XmlReader reader, IImmutableSet projectCapabilities)
+ {
+ throw new NotImplementedException();
+ }
+
+ Task IProjectService.LoadProjectAsync(string projectLocation, bool delayAutoLoad, IImmutableSet projectCapabilities)
+ {
+ throw new NotImplementedException();
+ }
+
+ Task IProjectService.UnloadProjectAsync(UnconfiguredProject project)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ public class TestUnconfiguredProject : UnconfiguredProject
+ {
+ public TestUnconfiguredProject(TestProjectService projectService, string fullPath)
+ {
+ ProjectService = projectService;
+ FullPath = fullPath;
+
+ LoadedConfiguredProjects = new List();
+ }
+
+ public TestProjectService ProjectService { get; }
+
+ public string FullPath { get; set; }
+
+ public List LoadedConfiguredProjects { get; }
+
+ string UnconfiguredProject.FullPath => FullPath;
+ bool UnconfiguredProject.RequiresReloadForExternalFileChange => throw new NotImplementedException();
+
+ IProjectCapabilitiesScope UnconfiguredProject.Capabilities => throw new NotImplementedException();
+
+ IProjectService UnconfiguredProject.ProjectService => ProjectService;
+
+ IUnconfiguredProjectServices UnconfiguredProject.Services => throw new NotImplementedException();
+
+ IEnumerable UnconfiguredProject.LoadedConfiguredProjects => LoadedConfiguredProjects;
+
+ bool UnconfiguredProject.IsLoading => throw new NotImplementedException();
+
+ event AsyncEventHandler UnconfiguredProject.ProjectUnloading
+ {
+ add
+ {
+ throw new NotImplementedException();
+ }
+
+ remove
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ event AsyncEventHandler UnconfiguredProject.ProjectRenaming
+ {
+ add
+ {
+ }
+
+ remove
+ {
+ }
+ }
+
+ event AsyncEventHandler UnconfiguredProject.ProjectRenamedOnWriter
+ {
+ add
+ {
+ throw new NotImplementedException();
+ }
+
+ remove
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ event AsyncEventHandler UnconfiguredProject.ProjectRenamed
+ {
+ add
+ {
+ throw new NotImplementedException();
+ }
+
+ remove
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ Task UnconfiguredProject.CanRenameAsync(string newFilePath)
+ {
+ throw new NotImplementedException();
+ }
+
+ Task UnconfiguredProject.GetFileEncodingAsync()
+ {
+ throw new NotImplementedException();
+ }
+
+ Task UnconfiguredProject.GetIsDirtyAsync()
+ {
+ throw new NotImplementedException();
+ }
+
+ Task UnconfiguredProject.GetSuggestedConfiguredProjectAsync()
+ {
+ throw new NotImplementedException();
+ }
+
+ Task UnconfiguredProject.LoadConfiguredProjectAsync(string name, IImmutableDictionary configurationProperties)
+ {
+ throw new NotImplementedException();
+ }
+
+ Task UnconfiguredProject.LoadConfiguredProjectAsync(ProjectConfiguration projectConfiguration)
+ {
+ throw new NotImplementedException();
+ }
+
+ Task UnconfiguredProject.ReloadAsync(bool immediately)
+ {
+ throw new NotImplementedException();
+ }
+
+ Task UnconfiguredProject.RenameAsync(string newFilePath)
+ {
+ throw new NotImplementedException();
+ }
+
+ Task UnconfiguredProject.SaveAsync(string filePath)
+ {
+ throw new NotImplementedException();
+ }
+
+ Task UnconfiguredProject.SaveCopyAsync(string filePath, Encoding fileEncoding)
+ {
+ throw new NotImplementedException();
+ }
+
+ Task UnconfiguredProject.SaveUserFileAsync()
+ {
+ throw new NotImplementedException();
+ }
+
+ Task UnconfiguredProject.SetFileEncodingAsync(Encoding value)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ public class TestConfiguredProject : ConfiguredProject
+ {
+ public TestConfiguredProject(TestUnconfiguredProject unconfiguredProject, TestPropertyData[] data)
+ {
+ UnconfiguredProject = unconfiguredProject;
+ Services = new TestConfiguredProjectServices(this, data);
+
+ ProjectConfiguration = new StandardProjectConfiguration(
+ "Debug|AnyCPU",
+ ImmutableDictionary.Empty.Add("Configuration", "Debug").Add("Platform", "AnyCPU"));
+ }
+
+ public TestUnconfiguredProject UnconfiguredProject { get; }
+
+ public ProjectConfiguration ProjectConfiguration { get; }
+
+ public TestConfiguredProjectServices Services { get; }
+
+ IComparable ConfiguredProject.ProjectVersion => throw new NotImplementedException();
+
+ IReceivableSourceBlock ConfiguredProject.ProjectVersionBlock => throw new NotImplementedException();
+
+ ProjectConfiguration ConfiguredProject.ProjectConfiguration => ProjectConfiguration;
+
+ IProjectCapabilitiesScope ConfiguredProject.Capabilities => throw new NotImplementedException();
+
+ UnconfiguredProject ConfiguredProject.UnconfiguredProject => UnconfiguredProject;
+
+ IConfiguredProjectServices ConfiguredProject.Services => Services;
+
+ event AsyncEventHandler ConfiguredProject.ProjectUnloading
+ {
+ add
+ {
+ throw new NotImplementedException();
+ }
+
+ remove
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ event EventHandler ConfiguredProject.ProjectChanged
+ {
+ add
+ {
+ throw new NotImplementedException();
+ }
+
+ remove
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ event EventHandler ConfiguredProject.ProjectChangedSynchronous
+ {
+ add
+ {
+ throw new NotImplementedException();
+ }
+
+ remove
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ void ConfiguredProject.NotifyProjectChange()
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ public class TestConfiguredProjectServices : IConfiguredProjectServices
+ {
+ public TestConfiguredProjectServices(TestConfiguredProject configuredProject, TestPropertyData[] data)
+ {
+ ConfiguredProject = configuredProject;
+
+ AdditionalRuleDefinitions = new TestAdditionalRuleDefinitionsService();
+ PropertyPagesCatalog = new TestPropertyPagesCatalogProvider(new TestPropertyPagesCatalog(data));
+ }
+
+ public TestConfiguredProject ConfiguredProject { get; }
+
+ public TestAdditionalRuleDefinitionsService AdditionalRuleDefinitions { get; }
+
+ public TestPropertyPagesCatalogProvider PropertyPagesCatalog { get; }
+
+ IOutputGroupsService IConfiguredProjectServices.OutputGroups => throw new NotImplementedException();
+
+ IBuildProject IConfiguredProjectServices.Build => throw new NotImplementedException();
+
+ IBuildSupport IConfiguredProjectServices.BuildSupport => throw new NotImplementedException();
+
+ IAssemblyReferencesService IConfiguredProjectServices.AssemblyReferences => throw new NotImplementedException();
+
+ IComReferencesService IConfiguredProjectServices.ComReferences => throw new NotImplementedException();
+
+ ISdkReferencesService IConfiguredProjectServices.SdkReferences => throw new NotImplementedException();
+
+ IPackageReferencesService IConfiguredProjectServices.PackageReferences => throw new NotImplementedException();
+
+ IWinRTReferencesService IConfiguredProjectServices.WinRTReferences => throw new NotImplementedException();
+
+ IBuildDependencyProjectReferencesService IConfiguredProjectServices.ProjectReferences => throw new NotImplementedException();
+
+ IProjectItemProvider IConfiguredProjectServices.SourceItems => throw new NotImplementedException();
+
+ IProjectPropertiesProvider IConfiguredProjectServices.ProjectPropertiesProvider => throw new NotImplementedException();
+
+ IProjectPropertiesProvider IConfiguredProjectServices.UserPropertiesProvider => throw new NotImplementedException();
+
+ IProjectAsynchronousTasksService IConfiguredProjectServices.ProjectAsynchronousTasks => throw new NotImplementedException();
+
+ IAdditionalRuleDefinitionsService IConfiguredProjectServices.AdditionalRuleDefinitions => AdditionalRuleDefinitions;
+
+ IPropertyPagesCatalogProvider IConfiguredProjectServices.PropertyPagesCatalog => PropertyPagesCatalog;
+
+ IProjectSubscriptionService IConfiguredProjectServices.ProjectSubscription => throw new NotImplementedException();
+
+ IProjectSnapshotService IConfiguredProjectServices.ProjectSnapshotService => throw new NotImplementedException();
+
+ object IConfiguredProjectServices.HostObject => throw new NotImplementedException();
+
+ ExportProvider IProjectCommonServices.ExportProvider => throw new NotImplementedException();
+
+ IProjectDataSourceRegistry IProjectCommonServices.DataSourceRegistry => throw new NotImplementedException();
+
+ IProjectService IProjectCommonServices.ProjectService => ConfiguredProject.UnconfiguredProject.ProjectService;
+
+ IProjectCapabilitiesScope IProjectCommonServices.Capabilities => throw new NotImplementedException();
+ }
+
+ public class TestAdditionalRuleDefinitionsService : IAdditionalRuleDefinitionsService
+ {
+ IProjectVersionedValue IAdditionalRuleDefinitionsService.AdditionalRuleDefinitions => throw new NotImplementedException();
+
+ IReceivableSourceBlock> IProjectValueDataSource.SourceBlock => throw new NotImplementedException();
+
+ ISourceBlock> IProjectValueDataSource.SourceBlock => throw new NotImplementedException();
+
+ NamedIdentity IProjectValueDataSource.DataSourceKey => throw new NotImplementedException();
+
+ IComparable IProjectValueDataSource.DataSourceVersion => throw new NotImplementedException();
+
+ bool IAdditionalRuleDefinitionsService.AddRuleDefinition(string path, string context)
+ {
+ return false;
+ }
+
+ bool IAdditionalRuleDefinitionsService.AddRuleDefinition(Rule rule, string context)
+ {
+ return false;
+ }
+
+ IDisposable IJoinableProjectValueDataSource.Join()
+ {
+ throw new NotImplementedException();
+ }
+
+ bool IAdditionalRuleDefinitionsService.RemoveRuleDefinition(string path)
+ {
+ return false;
+ }
+
+ bool IAdditionalRuleDefinitionsService.RemoveRuleDefinition(Rule rule)
+ {
+ return false;
+ }
+ }
+
+ public class TestPropertyPagesCatalogProvider : IPropertyPagesCatalogProvider
+ {
+ public TestPropertyPagesCatalogProvider(TestPropertyPagesCatalog catalog)
+ {
+ Catalog = catalog;
+ CatalogsByContext = new Dictionary()
+ {
+ { "Project", catalog },
+ };
+ }
+
+ public TestPropertyPagesCatalog Catalog { get; }
+
+ public Dictionary CatalogsByContext { get; }
+
+ public IReceivableSourceBlock> SourceBlock => throw new NotImplementedException();
+
+ public NamedIdentity DataSourceKey => throw new NotImplementedException();
+
+ public IComparable DataSourceVersion => throw new NotImplementedException();
+
+ ISourceBlock> IProjectValueDataSource.SourceBlock => throw new NotImplementedException();
+
+ public Task GetCatalogAsync(string name, CancellationToken cancellationToken = default)
+ {
+ return Task.FromResult(CatalogsByContext[name]);
+ }
+
+ public Task> GetCatalogsAsync(CancellationToken cancellationToken = default)
+ {
+ return Task.FromResult>(CatalogsByContext.ToImmutableDictionary());
+ }
+
+ public IPropertyPagesCatalog GetMemoryOnlyCatalog(string context)
+ {
+ return Catalog;
+ }
+
+ public IDisposable Join()
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ public class TestActiveConfiguredProjectSubscriptionService : IActiveConfiguredProjectSubscriptionService
+ {
+ public TestActiveConfiguredProjectSubscriptionService()
+ {
+ JointRuleBlock = new BufferBlock>();
+ JointRuleSource = new TestProjectValueDataSource(JointRuleBlock);
+ }
+
+ public BufferBlock> JointRuleBlock { get; }
+
+ public TestProjectValueDataSource JointRuleSource { get; }
+
+ IReceivableSourceBlock> IProjectSubscriptionService.ProjectBlock => throw new NotImplementedException();
+
+ IProjectValueDataSource IProjectSubscriptionService.ProjectSource => throw new NotImplementedException();
+
+ IProjectValueDataSource IProjectSubscriptionService.ImportTreeSource => throw new NotImplementedException();
+
+ IProjectValueDataSource IProjectSubscriptionService.SharedFoldersSource => throw new NotImplementedException();
+
+ IProjectValueDataSource> IProjectSubscriptionService.OutputGroupsSource => throw new NotImplementedException();
+
+ IReceivableSourceBlock> IProjectSubscriptionService.ProjectCatalogBlock => throw new NotImplementedException();
+
+ IProjectValueDataSource IProjectSubscriptionService.ProjectCatalogSource => throw new NotImplementedException();
+
+ IReceivableSourceBlock> IProjectSubscriptionService.ProjectRuleBlock => throw new NotImplementedException();
+
+ IProjectValueDataSource IProjectSubscriptionService.ProjectRuleSource => throw new NotImplementedException();
+
+ IReceivableSourceBlock> IProjectSubscriptionService.ProjectBuildRuleBlock => throw new NotImplementedException();
+
+ IProjectValueDataSource IProjectSubscriptionService.ProjectBuildRuleSource => throw new NotImplementedException();
+
+ ISourceBlock> IProjectSubscriptionService.JointRuleBlock => JointRuleBlock;
+
+ IProjectValueDataSource IProjectSubscriptionService.JointRuleSource => JointRuleSource;
+
+ IReceivableSourceBlock> IProjectSubscriptionService.SourceItemsRuleBlock => throw new NotImplementedException();
+
+ IProjectValueDataSource IProjectSubscriptionService.SourceItemsRuleSource => throw new NotImplementedException();
+
+ IReceivableSourceBlock>> IProjectSubscriptionService.SourceItemRuleNamesBlock => throw new NotImplementedException();
+
+ IProjectValueDataSource> IProjectSubscriptionService.SourceItemRuleNamesSource => throw new NotImplementedException();
+ }
+
+ public class TestProjectValueDataSource : IProjectValueDataSource
+ {
+ public TestProjectValueDataSource(BufferBlock> sourceBlock)
+ {
+ SourceBlock = sourceBlock;
+ }
+
+ public BufferBlock> SourceBlock { get; }
+
+ IReceivableSourceBlock> IProjectValueDataSource.SourceBlock => SourceBlock;
+
+ ISourceBlock