Add a project system

Step 1: Add HostProject

This is a somewhat complex addition to the ProjectSnapshotManager. Now
that we accept updates from the underlying IDE project system we need to
coordinate those with the Workspace.

This means that ProjectSnapshot itself now also has a version concept.

Step 2: Introduce a new project system based on CPS

We use project capabilities defined by the Razor SDK to determine
whether to rely on MSBuild evaluation to detect the configuration or
whether to fallback to assembly-based detection.

Step 3: Flow RazorConfiguration everywhere

We use now expose the RazorConfiguration to the language service and
editor. This means that we no longer need to detect the project's
configuration asynchronously, it happens much faster now.
This commit is contained in:
Ryan Nowak 2018-02-02 18:13:16 -08:00
parent 59a1cf9293
commit 5008c7803c
68 changed files with 5497 additions and 1499 deletions

View File

@ -8,6 +8,8 @@
<MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>2.1.0-preview2-30077</MicrosoftAspNetCoreBenchmarkRunnerSourcesPackageVersion>
<MicrosoftAspNetCoreHtmlAbstractionsPackageVersion>2.1.0-preview2-30106</MicrosoftAspNetCoreHtmlAbstractionsPackageVersion>
<MicrosoftAspNetCoreTestingPackageVersion>2.1.0-preview2-30106</MicrosoftAspNetCoreTestingPackageVersion>
<MicrosoftBuildFrameworkPackageVersion>15.3.409</MicrosoftBuildFrameworkPackageVersion>
<MicrosoftBuildPackageVersion>15.3.409</MicrosoftBuildPackageVersion>
<MicrosoftCodeAnalysisCommonPackageVersion>2.4.0</MicrosoftCodeAnalysisCommonPackageVersion>
<MicrosoftCodeAnalysisCSharpPackageVersion>2.4.0</MicrosoftCodeAnalysisCSharpPackageVersion>
<MicrosoftExtensionsCommandLineUtilsSourcesPackageVersion>2.1.0-preview2-30106</MicrosoftExtensionsCommandLineUtilsSourcesPackageVersion>
@ -46,6 +48,7 @@
<VSIX_MicrosoftCodeAnalysisCSharpWorkspacesPackageVersion>2.6.0-beta1-62023-02</VSIX_MicrosoftCodeAnalysisCSharpWorkspacesPackageVersion>
<VSIX_MicrosoftCodeAnalysisEditorFeaturesTextPackageVersion>2.6.0-beta1-62023-02</VSIX_MicrosoftCodeAnalysisEditorFeaturesTextPackageVersion>
<VSIX_MicrosoftCodeAnalysisRemoteRazorServiceHubPackageVersion>2.6.0-beta1-62023-02</VSIX_MicrosoftCodeAnalysisRemoteRazorServiceHubPackageVersion>
<VSIX_MicrosoftCodeAnalysisVisualBasicWorkspacesPackageVersion>2.6.0-beta1-62023-02</VSIX_MicrosoftCodeAnalysisVisualBasicWorkspacesPackageVersion>
<VSIX_MicrosoftCodeAnalysisWorkspacesCommonPackageVersion>2.6.0-beta1-62023-02</VSIX_MicrosoftCodeAnalysisWorkspacesCommonPackageVersion>
<VSIX_MicrosoftVisualStudioLanguageServicesPackageVersion>2.6.0-beta1-62023-02</VSIX_MicrosoftVisualStudioLanguageServicesPackageVersion>
<VSIX_MicrosoftVisualStudioLanguageServicesRazorRemoteClientPackageVersion>2.6.0-beta1-62023-02</VSIX_MicrosoftVisualStudioLanguageServicesRazorRemoteClientPackageVersion>

View File

@ -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<RazorExtension>());
public RazorConfiguration(
RazorLanguageVersion languageVersion,
public static RazorConfiguration Create(
RazorLanguageVersion languageVersion,
string configurationName,
IEnumerable<RazorExtension> 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<RazorExtension> Extensions { get; }
public abstract IReadOnlyList<RazorExtension> 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<RazorExtension> Extensions { get; }
public override RazorLanguageVersion LanguageVersion { get; }
}
}
}

View File

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

View File

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

View File

@ -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<ProjectExtensibilityConfiguration> 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<AssemblyIdentity> references)
{
// Avoiding ToDictionary here because we don't want a crash if there is a duplicate name.
var assemblies = new Dictionary<string, AssemblyIdentity>();
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;
}
}
}

View File

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

View File

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

View File

@ -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<ProjectChangeEventArgs> Changed;
@ -17,8 +34,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
private readonly ProjectSnapshotWorkerQueue _workerQueue;
private readonly ProjectSnapshotWorker _worker;
private readonly Dictionary<ProjectId, DefaultProjectSnapshot> _projects;
private readonly Dictionary<string, DefaultProjectSnapshot> _projects;
public DefaultProjectSnapshotManager(
ForegroundDispatcher foregroundDispatcher,
ErrorReporter errorReporter,
@ -57,7 +74,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
_triggers = triggers.ToArray();
Workspace = workspace;
_projects = new Dictionary<ProjectId, DefaultProjectSnapshot>();
_projects = new Dictionary<string, DefaultProjectSnapshot>(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);
}
}
}
}

View File

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

View File

@ -26,9 +26,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
public ILanguageService CreateLanguageService(HostLanguageServices languageServices)
{
return new DefaultProjectSnapshotWorker(
_foregroundDispatcher,
languageServices.GetRequiredService<ProjectExtensibilityConfigurationFactory>());
return new DefaultProjectSnapshotWorker(_foregroundDispatcher);
}
}
}

View File

@ -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<RazorExtension> Extensions { get; }
public override RazorLanguageVersion LanguageVersion { get; }
}
}

View File

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

View File

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

View File

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

View File

@ -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<ProjectExtensibilityConfiguration>
{
public abstract IReadOnlyList<ProjectExtensibilityAssembly> 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);
}
}
}

View File

@ -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<ProjectExtensibilityConfiguration> GetConfigurationAsync(Project project, CancellationToken cancellationToken = default(CancellationToken));
}
}

View File

@ -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
{
/// <summary>
/// Describes how closely the configuration of Razor tooling matches the actual project dependencies.
/// </summary>
internal enum ProjectExtensibilityConfigurationKind
{
ApproximateMatch,
Fallback,
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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<ProjectId, Project> _projects;
private readonly Dictionary<string, ProjectSnapshotUpdateContext> _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<ProjectId, Project>();
_projects = new Dictionary<string, ProjectSnapshotUpdateContext>(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);
}
}
}

View File

@ -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<RazorExtension> Extensions { get; }
public override RazorLanguageVersion LanguageVersion { get; }
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -77,7 +77,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor.Editor
_textViews = new List<ITextView>();
}
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)
{

View File

@ -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<ContextChangeEventArgs> ContextChanged;
internal abstract ProjectExtensibilityConfiguration Configuration { get; }
public abstract RazorConfiguration Configuration { get; }
public abstract EditorSettings EditorSettings { get; }

View File

@ -11,6 +11,8 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Build" Version="$(MicrosoftBuildPackageVersion)" />
<PackageReference Include="Microsoft.Build.Framework" Version="$(MicrosoftBuildFrameworkPackageVersion)" />
<PackageReference Include="Microsoft.VisualStudio.ComponentModelHost" Version="$(MicrosoftVisualStudioComponentModelHostPackageVersion)" />
<PackageReference Include="Microsoft.VisualStudio.Editor" Version="$(MicrosoftVisualStudioEditorPackageVersion)" />
<PackageReference Include="Microsoft.VisualStudio.LanguageServices.Razor.RemoteClient" Version="$(VSIX_MicrosoftVisualStudioLanguageServicesRazorRemoteClientPackageVersion)" />
@ -26,7 +28,7 @@
<PackageReference Include="Microsoft.VisualStudio.Shell.Interop.8.0" Version="$(MicrosoftVisualStudioShellInterop80PackageVersion)" />
<PackageReference Include="Microsoft.VisualStudio.Shell.Interop.9.0" Version="$(MicrosoftVisualStudioShellInterop90PackageVersion)" />
<PackageReference Include="Microsoft.VisualStudio.Shell.Interop" Version="$(MicrosoftVisualStudioShellInteropPackageVersion)" />
<!-- We need to use this version of Json.Net to maintain consistency with Visual Studio. -->
<PackageReference Include="Newtonsoft.Json" Version="$(VisualStudio_NewtonsoftJsonPackageVersion)" />
</ItemGroup>
@ -35,4 +37,82 @@
<ProjectReference Include="..\..\src\Microsoft.VisualStudio.Editor.Razor\Microsoft.VisualStudio.Editor.Razor.csproj" />
</ItemGroup>
<ItemGroup>
<Reference Include="System.ComponentModel.Composition" />
<Reference Include="System.Xaml" />
</ItemGroup>
<!--
The ProjectSystem.SDK tasks that handle XamlPropertyRule don't work on the dotnet core version
of MSBuild. The workaround here is to only hardcode the generated code location such that it gets
checked in. Then we don't need to generate it at build time.
If you make changes to the rule files, then you need to update them using Desktop MSBuild :(
-->
<ItemGroup Condition="'$(MSBuildRuntimeType)'=='Core'">
<None Include="ProjectSystem\Rules\RazorConfiguration.xaml" />
<None Include="ProjectSystem\Rules\RazorExtension.xaml" />
<None Include="ProjectSystem\Rules\RazorGeneral.xaml" />
<Compile Update="ProjectSystem\Rules\RazorConfiguration.cs">
<DependentUpon>RazorConfiguration.xaml</DependentUpon>
</Compile>
<Compile Update="ProjectSystem\Rules\RazorExtension.cs">
<DependentUpon>RazorExtension.xaml</DependentUpon>
</Compile>
<Compile Update="ProjectSystem\Rules\RazorGeneral.cs">
<DependentUpon>RazorGeneral.xaml</DependentUpon>
</Compile>
<EmbeddedResource Include="ProjectSystem\Rules\RazorConfiguration.xaml">
<LogicalName>XamlRuleToCode:RazorConfiguration.xaml</LogicalName>
</EmbeddedResource>
<EmbeddedResource Include="ProjectSystem\Rules\RazorExtension.xaml">
<LogicalName>XamlRuleToCode:RazorExtension.xaml</LogicalName>
</EmbeddedResource>
<EmbeddedResource Include="ProjectSystem\Rules\RazorGeneral.xaml">
<LogicalName>XamlRuleToCode:RazorGeneral.xaml</LogicalName>
</EmbeddedResource>
</ItemGroup>
<ItemGroup Condition="'$(MSBuildRuntimeType)'!='Core'">
<XamlPropertyRule Include="ProjectSystem\Rules\RazorConfiguration.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:GenerateRuleSourceFromXaml</Generator>
<Namespace>Microsoft.CodeAnalysis.Razor.ProjectSystem.Rules</Namespace>
<RuleInjectionClassName>RazorProjectProperties</RuleInjectionClassName>
<Context></Context>
<OutputPath>ProjectSystem\Rules\</OutputPath>
</XamlPropertyRule>
<XamlPropertyRule Include="ProjectSystem\Rules\RazorExtension.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:GenerateRuleSourceFromXaml</Generator>
<Namespace>Microsoft.CodeAnalysis.Razor.ProjectSystem.Rules</Namespace>
<RuleInjectionClassName>RazorProjectProperties</RuleInjectionClassName>
<OutputPath>ProjectSystem\Rules\</OutputPath>
</XamlPropertyRule>
<XamlPropertyRule Include="ProjectSystem\Rules\RazorGeneral.xaml">
<SubType>Designer</SubType>
<Generator>MSBuild:GenerateRuleSourceFromXaml</Generator>
<Namespace>Microsoft.CodeAnalysis.Razor.ProjectSystem.Rules</Namespace>
<RuleInjectionClassName>RazorProjectProperties</RuleInjectionClassName>
<OutputPath>ProjectSystem\Rules\</OutputPath>
</XamlPropertyRule>
<Compile Update="ProjectSystem\Rules\RazorConfiguration.cs">
<DependentUpon>RazorGeneral.xaml</DependentUpon>
</Compile>
<Compile Update="ProjectSystem\Rules\RazorExtension.cs">
<DependentUpon>RazorGeneral.xaml</DependentUpon>
</Compile>
<Compile Update="ProjectSystem\Rules\RazorGeneral.cs">
<DependentUpon>RazorGeneral.xaml</DependentUpon>
</Compile>
</ItemGroup>
<!--
Despite us specifying %(XamlPropertyRule.OutputPath), the ProjectSystem.SDK targets still add files in the
obj folder to the Compile group. This is a workaround to remove them.
-->
<Target Name="GrossProjectSystemSDKWorkaround" AfterTargets="GenerateRulePrep">
<ItemGroup>
<Compile Remove="@(XamlPropertyRule->'$(IntermediateOutputPath)%(FileName)$(DefaultLanguageSourceExtension)')" />
</ItemGroup>
</Target>
</Project>

View File

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

View File

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

View File

@ -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<ConfiguredProject> _activeConfiguredProject;
private readonly ActiveConfiguredProject<IAssemblyReferencesService> _activeConfiguredProjectAssemblyReferences;
private readonly ActiveConfiguredProject<IPackageReferencesService> _activeConfiguredProjectPackageReferences;
private readonly ActiveConfiguredProject<Rules.RazorProjectProperties> _activeConfiguredProjectProperties;
[ImportingConstructor]
public UnconfiguredProjectCommonServices(
IProjectThreadingService threadingService,
UnconfiguredProject unconfiguredProject,
IActiveConfiguredProjectSubscriptionService activeConfiguredProjectSubscription,
ActiveConfiguredProject<ConfiguredProject> activeConfiguredProject,
ActiveConfiguredProject<IAssemblyReferencesService> activeConfiguredProjectAssemblyReferences,
ActiveConfiguredProject<IPackageReferencesService> activeConfiguredProjectPackageReferences,
ActiveConfiguredProject<Rules.RazorProjectProperties> 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; }
}
}

View File

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

View File

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

View File

@ -0,0 +1,212 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 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.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem.Rules {
internal partial class RazorConfiguration {
/// <summary>Backing field for deserialized rule.<see cref='Microsoft.Build.Framework.XamlTypes.Rule'/>.</summary>
private static Microsoft.Build.Framework.XamlTypes.Rule deserializedFallbackRule;
/// <summary>The name of the schema to look for at runtime to fulfill property access.</summary>
internal const string SchemaName = "RazorConfiguration";
/// <summary>The ItemType given in the Rule.DataSource property. May not apply to every Property's individual DataSource.</summary>
internal const string PrimaryDataSourceItemType = "RazorConfiguration";
/// <summary>The Label given in the Rule.DataSource property. May not apply to every Property's individual DataSource.</summary>
internal const string PrimaryDataSourceLabel = "";
/// <summary>Razor Extensions (The "Extensions" property).</summary>
internal const string ExtensionsProperty = "Extensions";
/// <summary>Backing field for the <see cref='Microsoft.Build.Framework.XamlTypes.Rule'/> property.</summary>
private Microsoft.VisualStudio.ProjectSystem.Properties.IRule rule;
/// <summary>Backing field for the file name of the rule property.</summary>
private string file;
/// <summary>Backing field for the ItemType property.</summary>
private string itemType;
/// <summary>Backing field for the ItemName property.</summary>
private string itemName;
/// <summary>Configured Project</summary>
private Microsoft.VisualStudio.ProjectSystem.ConfiguredProject configuredProject;
/// <summary>The dictionary of named catalogs.</summary>
private System.Collections.Immutable.IImmutableDictionary<string, Microsoft.VisualStudio.ProjectSystem.Properties.IPropertyPagesCatalog> catalogs;
/// <summary>Backing field for the <see cref='Microsoft.VisualStudio.ProjectSystem.Properties.IRule'/> property.</summary>
private Microsoft.VisualStudio.ProjectSystem.Properties.IRule fallbackRule;
/// <summary>Thread locking object</summary>
private object locker = new object();
/// <summary>Initializes a new instance of the RazorConfiguration class.</summary>
internal RazorConfiguration(Microsoft.VisualStudio.ProjectSystem.Properties.IRule rule) {
this.rule = rule;
}
/// <summary>Initializes a new instance of the RazorConfiguration class.</summary>
internal RazorConfiguration(Microsoft.VisualStudio.ProjectSystem.ConfiguredProject configuredProject, System.Collections.Immutable.IImmutableDictionary<string, Microsoft.VisualStudio.ProjectSystem.Properties.IPropertyPagesCatalog> 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;
}
/// <summary>Initializes a new instance of the RazorConfiguration class.</summary>
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;
}
/// <summary>Initializes a new instance of the RazorConfiguration class.</summary>
internal RazorConfiguration(Microsoft.VisualStudio.ProjectSystem.ConfiguredProject configuredProject, System.Collections.Immutable.IImmutableDictionary<string, Microsoft.VisualStudio.ProjectSystem.Properties.IPropertyPagesCatalog> catalogs, string context, Microsoft.VisualStudio.ProjectSystem.Properties.IProjectPropertiesContext propertyContext) :
this(configuredProject, catalogs, context, GetContextFile(propertyContext), propertyContext.ItemType, propertyContext.ItemName) {
}
/// <summary>Initializes a new instance of the RazorConfiguration class that assumes a project context (neither property sheet nor items).</summary>
internal RazorConfiguration(Microsoft.VisualStudio.ProjectSystem.ConfiguredProject configuredProject, System.Collections.Immutable.IImmutableDictionary<string, Microsoft.VisualStudio.ProjectSystem.Properties.IPropertyPagesCatalog> catalogs) :
this(configuredProject, catalogs, "Project", null, null, null) {
}
/// <summary>Gets the IRule used to get and set properties.</summary>
public Microsoft.VisualStudio.ProjectSystem.Properties.IRule Rule {
get {
return this.rule;
}
}
/// <summary>Razor Extensions</summary>
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;
}
}
/// <summary>Get the fallback rule if the current rule on disk is missing or a property in the rule on disk is missing</summary>
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<System.Object> 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<System.Threading.Tasks.Task<System.Collections.Immutable.IImmutableDictionary<string, Microsoft.VisualStudio.ProjectSystem.Properties.IPropertyPagesCatalog>>, object, RazorConfiguration> CreateRazorConfigurationPropertiesDelegate = new System.Func<System.Threading.Tasks.Task<System.Collections.Immutable.IImmutableDictionary<string, Microsoft.VisualStudio.ProjectSystem.Properties.IPropertyPagesCatalog>>, object, RazorConfiguration>(CreateRazorConfigurationProperties);
private static RazorConfiguration CreateRazorConfigurationProperties(System.Threading.Tasks.Task<System.Collections.Immutable.IImmutableDictionary<string, Microsoft.VisualStudio.ProjectSystem.Properties.IPropertyPagesCatalog>> namedCatalogs, object state) {
RazorProjectProperties that = ((RazorProjectProperties)(state));
return new RazorConfiguration(that.ConfiguredProject, namedCatalogs.Result, "Project", that.File, that.ItemType, that.ItemName);
}
/// <summary>Gets the strongly-typed property accessor used to get and set Configuration Properties properties.</summary>
internal System.Threading.Tasks.Task<RazorConfiguration> GetRazorConfigurationPropertiesAsync() {
System.Threading.Tasks.Task<System.Collections.Immutable.IImmutableDictionary<string, Microsoft.VisualStudio.ProjectSystem.Properties.IPropertyPagesCatalog>> namedCatalogsTask = this.GetNamedCatalogsAsync();
return namedCatalogsTask.ContinueWith(CreateRazorConfigurationPropertiesDelegate, this, System.Threading.CancellationToken.None, System.Threading.Tasks.TaskContinuationOptions.ExecuteSynchronously, System.Threading.Tasks.TaskScheduler.Default);
}
}
}

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<Rule
Description="Configuration Properties"
DisplayName="Configuration Properties"
Name="RazorConfiguration"
PageTemplate="generic"
xmlns="http://schemas.microsoft.com/build/2009/properties">
<Rule.DataSource>
<DataSource
Persistence="ProjectFile"
HasConfigurationCondition="True"
ItemType="RazorConfiguration" />
</Rule.DataSource>
<Rule.Categories>
<Category
Name="General"
DisplayName="General" />
</Rule.Categories>
<StringProperty
Category="General"
Description="Razor Extensions"
DisplayName="Razor Extensions"
Name="Extensions"
ReadOnly="True"
Visible="True" />
</Rule>

View File

@ -0,0 +1,235 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 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.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem.Rules {
internal partial class RazorExtension {
/// <summary>Backing field for deserialized rule.<see cref='Microsoft.Build.Framework.XamlTypes.Rule'/>.</summary>
private static Microsoft.Build.Framework.XamlTypes.Rule deserializedFallbackRule;
/// <summary>The name of the schema to look for at runtime to fulfill property access.</summary>
internal const string SchemaName = "RazorExtension";
/// <summary>The ItemType given in the Rule.DataSource property. May not apply to every Property's individual DataSource.</summary>
internal const string PrimaryDataSourceItemType = "RazorExtension";
/// <summary>The Label given in the Rule.DataSource property. May not apply to every Property's individual DataSource.</summary>
internal const string PrimaryDataSourceLabel = "";
/// <summary>Razor Extension Assembly Name (The "AssemblyName" property).</summary>
internal const string AssemblyNameProperty = "AssemblyName";
/// <summary>Razor Extension Assembly File Path (The "AssemblyFilePath" property).</summary>
internal const string AssemblyFilePathProperty = "AssemblyFilePath";
/// <summary>Backing field for the <see cref='Microsoft.Build.Framework.XamlTypes.Rule'/> property.</summary>
private Microsoft.VisualStudio.ProjectSystem.Properties.IRule rule;
/// <summary>Backing field for the file name of the rule property.</summary>
private string file;
/// <summary>Backing field for the ItemType property.</summary>
private string itemType;
/// <summary>Backing field for the ItemName property.</summary>
private string itemName;
/// <summary>Configured Project</summary>
private Microsoft.VisualStudio.ProjectSystem.ConfiguredProject configuredProject;
/// <summary>The dictionary of named catalogs.</summary>
private System.Collections.Immutable.IImmutableDictionary<string, Microsoft.VisualStudio.ProjectSystem.Properties.IPropertyPagesCatalog> catalogs;
/// <summary>Backing field for the <see cref='Microsoft.VisualStudio.ProjectSystem.Properties.IRule'/> property.</summary>
private Microsoft.VisualStudio.ProjectSystem.Properties.IRule fallbackRule;
/// <summary>Thread locking object</summary>
private object locker = new object();
/// <summary>Initializes a new instance of the RazorExtension class.</summary>
internal RazorExtension(Microsoft.VisualStudio.ProjectSystem.Properties.IRule rule) {
this.rule = rule;
}
/// <summary>Initializes a new instance of the RazorExtension class.</summary>
internal RazorExtension(Microsoft.VisualStudio.ProjectSystem.ConfiguredProject configuredProject, System.Collections.Immutable.IImmutableDictionary<string, Microsoft.VisualStudio.ProjectSystem.Properties.IPropertyPagesCatalog> 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;
}
/// <summary>Initializes a new instance of the RazorExtension class.</summary>
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;
}
/// <summary>Initializes a new instance of the RazorExtension class.</summary>
internal RazorExtension(Microsoft.VisualStudio.ProjectSystem.ConfiguredProject configuredProject, System.Collections.Immutable.IImmutableDictionary<string, Microsoft.VisualStudio.ProjectSystem.Properties.IPropertyPagesCatalog> catalogs, string context, Microsoft.VisualStudio.ProjectSystem.Properties.IProjectPropertiesContext propertyContext) :
this(configuredProject, catalogs, context, GetContextFile(propertyContext), propertyContext.ItemType, propertyContext.ItemName) {
}
/// <summary>Initializes a new instance of the RazorExtension class that assumes a project context (neither property sheet nor items).</summary>
internal RazorExtension(Microsoft.VisualStudio.ProjectSystem.ConfiguredProject configuredProject, System.Collections.Immutable.IImmutableDictionary<string, Microsoft.VisualStudio.ProjectSystem.Properties.IPropertyPagesCatalog> catalogs) :
this(configuredProject, catalogs, "Project", null, null, null) {
}
/// <summary>Gets the IRule used to get and set properties.</summary>
public Microsoft.VisualStudio.ProjectSystem.Properties.IRule Rule {
get {
return this.rule;
}
}
/// <summary>Razor Extension Assembly Name</summary>
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;
}
}
/// <summary>Razor Extension Assembly File Path</summary>
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;
}
}
/// <summary>Get the fallback rule if the current rule on disk is missing or a property in the rule on disk is missing</summary>
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<System.Object> 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<System.Threading.Tasks.Task<System.Collections.Immutable.IImmutableDictionary<string, Microsoft.VisualStudio.ProjectSystem.Properties.IPropertyPagesCatalog>>, object, RazorExtension> CreateRazorExtensionPropertiesDelegate = new System.Func<System.Threading.Tasks.Task<System.Collections.Immutable.IImmutableDictionary<string, Microsoft.VisualStudio.ProjectSystem.Properties.IPropertyPagesCatalog>>, object, RazorExtension>(CreateRazorExtensionProperties);
private static RazorExtension CreateRazorExtensionProperties(System.Threading.Tasks.Task<System.Collections.Immutable.IImmutableDictionary<string, Microsoft.VisualStudio.ProjectSystem.Properties.IPropertyPagesCatalog>> namedCatalogs, object state) {
RazorProjectProperties that = ((RazorProjectProperties)(state));
return new RazorExtension(that.ConfiguredProject, namedCatalogs.Result, "Project", that.File, that.ItemType, that.ItemName);
}
/// <summary>Gets the strongly-typed property accessor used to get and set Extension Properties properties.</summary>
internal System.Threading.Tasks.Task<RazorExtension> GetRazorExtensionPropertiesAsync() {
System.Threading.Tasks.Task<System.Collections.Immutable.IImmutableDictionary<string, Microsoft.VisualStudio.ProjectSystem.Properties.IPropertyPagesCatalog>> namedCatalogsTask = this.GetNamedCatalogsAsync();
return namedCatalogsTask.ContinueWith(CreateRazorExtensionPropertiesDelegate, this, System.Threading.CancellationToken.None, System.Threading.Tasks.TaskContinuationOptions.ExecuteSynchronously, System.Threading.Tasks.TaskScheduler.Default);
}
}
}

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="utf-8"?>
<Rule
Description="Extension Properties"
DisplayName="Extension Properties"
Name="RazorExtension"
PageTemplate="generic"
xmlns="http://schemas.microsoft.com/build/2009/properties">
<Rule.DataSource>
<DataSource
Persistence="ProjectFile"
HasConfigurationCondition="True"
ItemType="RazorExtension" />
</Rule.DataSource>
<Rule.Categories>
<Category
Name="General"
DisplayName="General" />
</Rule.Categories>
<StringProperty
Category="General"
Description="Razor Extension Assembly Name"
DisplayName="Razor Extension Assembly Name"
Name="AssemblyName"
ReadOnly="True"
Visible="True" />
<StringProperty
Category="General"
Description="Razor Extension Assembly File Path"
DisplayName="Razor Extension Assembly File Path"
Name="AssemblyFilePath"
ReadOnly="True"
Visible="True" />
</Rule>

View File

@ -0,0 +1,235 @@
//------------------------------------------------------------------------------
// <auto-generated>
// 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.
// </auto-generated>
//------------------------------------------------------------------------------
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem.Rules {
internal partial class RazorGeneral {
/// <summary>Backing field for deserialized rule.<see cref='Microsoft.Build.Framework.XamlTypes.Rule'/>.</summary>
private static Microsoft.Build.Framework.XamlTypes.Rule deserializedFallbackRule;
/// <summary>The name of the schema to look for at runtime to fulfill property access.</summary>
internal const string SchemaName = "RazorGeneral";
/// <summary>The ItemType given in the Rule.DataSource property. May not apply to every Property's individual DataSource.</summary>
internal const string PrimaryDataSourceItemType = null;
/// <summary>The Label given in the Rule.DataSource property. May not apply to every Property's individual DataSource.</summary>
internal const string PrimaryDataSourceLabel = "";
/// <summary>Razor Language Version (The "RazorLangVersion" property).</summary>
internal const string RazorLangVersionProperty = "RazorLangVersion";
/// <summary>Razor Configuration Name (The "RazorDefaultConfiguration" property).</summary>
internal const string RazorDefaultConfigurationProperty = "RazorDefaultConfiguration";
/// <summary>Backing field for the <see cref='Microsoft.Build.Framework.XamlTypes.Rule'/> property.</summary>
private Microsoft.VisualStudio.ProjectSystem.Properties.IRule rule;
/// <summary>Backing field for the file name of the rule property.</summary>
private string file;
/// <summary>Backing field for the ItemType property.</summary>
private string itemType;
/// <summary>Backing field for the ItemName property.</summary>
private string itemName;
/// <summary>Configured Project</summary>
private Microsoft.VisualStudio.ProjectSystem.ConfiguredProject configuredProject;
/// <summary>The dictionary of named catalogs.</summary>
private System.Collections.Immutable.IImmutableDictionary<string, Microsoft.VisualStudio.ProjectSystem.Properties.IPropertyPagesCatalog> catalogs;
/// <summary>Backing field for the <see cref='Microsoft.VisualStudio.ProjectSystem.Properties.IRule'/> property.</summary>
private Microsoft.VisualStudio.ProjectSystem.Properties.IRule fallbackRule;
/// <summary>Thread locking object</summary>
private object locker = new object();
/// <summary>Initializes a new instance of the RazorGeneral class.</summary>
internal RazorGeneral(Microsoft.VisualStudio.ProjectSystem.Properties.IRule rule) {
this.rule = rule;
}
/// <summary>Initializes a new instance of the RazorGeneral class.</summary>
internal RazorGeneral(Microsoft.VisualStudio.ProjectSystem.ConfiguredProject configuredProject, System.Collections.Immutable.IImmutableDictionary<string, Microsoft.VisualStudio.ProjectSystem.Properties.IPropertyPagesCatalog> 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;
}
/// <summary>Initializes a new instance of the RazorGeneral class.</summary>
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;
}
/// <summary>Initializes a new instance of the RazorGeneral class.</summary>
internal RazorGeneral(Microsoft.VisualStudio.ProjectSystem.ConfiguredProject configuredProject, System.Collections.Immutable.IImmutableDictionary<string, Microsoft.VisualStudio.ProjectSystem.Properties.IPropertyPagesCatalog> catalogs, string context, Microsoft.VisualStudio.ProjectSystem.Properties.IProjectPropertiesContext propertyContext) :
this(configuredProject, catalogs, context, GetContextFile(propertyContext), propertyContext.ItemType, propertyContext.ItemName) {
}
/// <summary>Initializes a new instance of the RazorGeneral class that assumes a project context (neither property sheet nor items).</summary>
internal RazorGeneral(Microsoft.VisualStudio.ProjectSystem.ConfiguredProject configuredProject, System.Collections.Immutable.IImmutableDictionary<string, Microsoft.VisualStudio.ProjectSystem.Properties.IPropertyPagesCatalog> catalogs) :
this(configuredProject, catalogs, "Project", null, null, null) {
}
/// <summary>Gets the IRule used to get and set properties.</summary>
public Microsoft.VisualStudio.ProjectSystem.Properties.IRule Rule {
get {
return this.rule;
}
}
/// <summary>Razor Language Version</summary>
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;
}
}
/// <summary>Razor Configuration Name</summary>
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;
}
}
/// <summary>Get the fallback rule if the current rule on disk is missing or a property in the rule on disk is missing</summary>
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<System.Object> 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<System.Threading.Tasks.Task<System.Collections.Immutable.IImmutableDictionary<string, Microsoft.VisualStudio.ProjectSystem.Properties.IPropertyPagesCatalog>>, object, RazorGeneral> CreateRazorGeneralPropertiesDelegate = new System.Func<System.Threading.Tasks.Task<System.Collections.Immutable.IImmutableDictionary<string, Microsoft.VisualStudio.ProjectSystem.Properties.IPropertyPagesCatalog>>, object, RazorGeneral>(CreateRazorGeneralProperties);
private static RazorGeneral CreateRazorGeneralProperties(System.Threading.Tasks.Task<System.Collections.Immutable.IImmutableDictionary<string, Microsoft.VisualStudio.ProjectSystem.Properties.IPropertyPagesCatalog>> namedCatalogs, object state) {
RazorProjectProperties that = ((RazorProjectProperties)(state));
return new RazorGeneral(that.ConfiguredProject, namedCatalogs.Result, "Project", that.File, that.ItemType, that.ItemName);
}
/// <summary>Gets the strongly-typed property accessor used to get and set Razor Properties properties.</summary>
internal System.Threading.Tasks.Task<RazorGeneral> GetRazorGeneralPropertiesAsync() {
System.Threading.Tasks.Task<System.Collections.Immutable.IImmutableDictionary<string, Microsoft.VisualStudio.ProjectSystem.Properties.IPropertyPagesCatalog>> namedCatalogsTask = this.GetNamedCatalogsAsync();
return namedCatalogsTask.ContinueWith(CreateRazorGeneralPropertiesDelegate, this, System.Threading.CancellationToken.None, System.Threading.Tasks.TaskContinuationOptions.ExecuteSynchronously, System.Threading.Tasks.TaskScheduler.Default);
}
}
}

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<Rule
Description="Razor Properties"
DisplayName="Razor Properties"
Name="RazorGeneral"
PageTemplate="generic"
xmlns="http://schemas.microsoft.com/build/2009/properties">
<Rule.DataSource>
<DataSource
Persistence="ProjectFile"
HasConfigurationCondition="True" />
</Rule.DataSource>
<Rule.Categories>
<Category
Name="General"
DisplayName="General" />
</Rule.Categories>
<StringProperty
Category="General"
Description="Razor Language Version"
DisplayName="Razor Language Version"
Name="RazorLangVersion"
ReadOnly="True"
Visible="True" />
<StringProperty
Category="General"
Description="Razor Configuration Name"
DisplayName="Razor Configuration Name"
Name="RazorDefaultConfiguration"
ReadOnly="True"
Visible="True" />
</Rule>

View File

@ -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)
{
}
}
}

View File

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

View File

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

View File

@ -19,6 +19,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="$(VSIX_MicrosoftCodeAnalysisWorkspacesCommonPackageVersion)" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="$(VSIX_MicrosoftCodeAnalysisCSharpWorkspacesPackageVersion)" />
<PackageReference Include="Microsoft.CodeAnalysis.VisualBasic.Workspaces" Version="$(VSIX_MicrosoftCodeAnalysisVisualBasicWorkspacesPackageVersion)" />
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="$(MicrosoftExtensionsDependencyModelPackageVersion)" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkPackageVersion)" />
<PackageReference Include="Moq" Version="$(MoqPackageVersion)" />

View File

@ -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<AssemblyIdentity, RazorLanguageVersion>
{
{ 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<MvcExtensibilityConfiguration>(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<MvcExtensibilityConfiguration>(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<MvcExtensibilityConfiguration>(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<MvcExtensibilityConfiguration>(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<MvcExtensibilityConfiguration>(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<MvcExtensibilityConfiguration>(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<MvcExtensibilityConfiguration>(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<MvcExtensibilityConfiguration>(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());
}
}
}

View File

@ -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<ProjectSnapshotChangeTrigger>(), 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<ProjectExtensibilityConfiguration>();
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<ProjectExtensibilityConfiguration>();
// 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<ProjectExtensibilityConfiguration>();
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<ProjectExtensibilityConfiguration>();
// 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<ProjectExtensibilityConfiguration>();
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<ProjectSnapshotChangeTrigger> triggers, Workspace workspace)
: base(Mock.Of<ForegroundDispatcher>(), Mock.Of<ErrorReporter>(), Mock.Of<ProjectSnapshotWorker>(), triggers, workspace)
{
}
public bool ListenersNotified { get; private set; }
public bool WorkerStarted { get; private set; }
public DefaultProjectSnapshot GetSnapshot(ProjectId id)
{
return Projects.Cast<DefaultProjectSnapshot>().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;
}
}
}
}

View File

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

View File

@ -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<ProjectSnapshotChangeTrigger> triggers, Workspace workspace)
public TestProjectSnapshotManager(IEnumerable<ProjectSnapshotChangeTrigger> triggers, Workspace workspace)
: base(Mock.Of<ForegroundDispatcher>(), Mock.Of<ErrorReporter>(), new TestProjectSnapshotWorker(), triggers, workspace)
{
}
protected override void NotifyBackgroundWorker(Project project)
protected override void NotifyBackgroundWorker(ProjectSnapshotUpdateContext context)
{
Assert.NotNull(context.HostProject);
Assert.NotNull(context.WorkspaceProject);
}
}

View File

@ -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<Mvc1_X.ViewComponentTagHelperPass>());
}
[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<MyCoolNewFeature>());
Assert.Single(engine.Engine.Features.OfType<MvcLatest.MvcViewDocumentClassifierPass>());
Assert.Single(engine.Engine.Features.OfType<MvcLatest.ViewComponentTagHelperPass>());
}
[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);

View File

@ -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<ProjectSnapshotChangeTrigger>(), 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<ProjectSnapshotChangeTrigger> triggers, Workspace workspace)
: base(dispatcher, Mock.Of<ErrorReporter>(), Mock.Of<ProjectSnapshotWorker>(), triggers, workspace)
{
}
public bool ListenersNotified { get; private set; }
public bool WorkerStarted { get; private set; }
public DefaultProjectSnapshot GetSnapshot(HostProject hostProject)
{
return Projects.Cast<DefaultProjectSnapshot>().FirstOrDefault(s => s.FilePath == hostProject.FilePath);
}
public DefaultProjectSnapshot GetSnapshot(Project workspaceProject)
{
return Projects.Cast<DefaultProjectSnapshot>().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;
}
}
}
}

View File

@ -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<string, string>()
{
{ 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<string, Dictionary<string, string>>()
{
{ "MVC-2.1", new Dictionary<string, string>() { { "Extensions", "MVC-2.1;Another-Thing" }, } },
})
},
new TestProjectChangeDescription()
{
RuleName = Rules.RazorExtension.SchemaName,
After = TestProjectRuleSnapshot.CreateItems(Rules.RazorExtension.SchemaName, new Dictionary<string, Dictionary<string, string>>()
{
{ "MVC-2.1", new Dictionary<string, string>(){ } },
{ "Another-Thing", new Dictionary<string, string>(){ } },
})
}
};
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<string, string>()
{
{ Rules.RazorGeneral.RazorLangVersionProperty, "" },
{ Rules.RazorGeneral.RazorDefaultConfigurationProperty, "" },
}),
},
new TestProjectChangeDescription()
{
RuleName = Rules.RazorConfiguration.SchemaName,
After = TestProjectRuleSnapshot.CreateItems(Rules.RazorConfiguration.SchemaName, new Dictionary<string, Dictionary<string, string>>()
{
})
},
new TestProjectChangeDescription()
{
RuleName = Rules.RazorExtension.SchemaName,
After = TestProjectRuleSnapshot.CreateItems(Rules.RazorExtension.SchemaName, new Dictionary<string, Dictionary<string, string>>()
{
})
}
};
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<string, string>()
{
{ 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<string, Dictionary<string, string>>()
{
{ "MVC-2.1", new Dictionary<string, string>() { { "Extensions", "MVC-2.1;Another-Thing" }, } },
})
},
new TestProjectChangeDescription()
{
RuleName = Rules.RazorExtension.SchemaName,
After = TestProjectRuleSnapshot.CreateItems(Rules.RazorExtension.SchemaName, new Dictionary<string, Dictionary<string, string>>()
{
{ "MVC-2.1", new Dictionary<string, string>(){ } },
{ "Another-Thing", new Dictionary<string, string>(){ } },
})
}
};
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<string, string>() { { "Extensions", "MVC-2.0;Another-Thing" }, });
changes[2].After.SetItem("MVC-2.0", new Dictionary<string, string>());
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<string, string>()
{
{ 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<string, Dictionary<string, string>>()
{
{ "MVC-2.1", new Dictionary<string, string>() { { "Extensions", "MVC-2.1;Another-Thing" }, } },
})
},
new TestProjectChangeDescription()
{
RuleName = Rules.RazorExtension.SchemaName,
After = TestProjectRuleSnapshot.CreateItems(Rules.RazorExtension.SchemaName, new Dictionary<string, Dictionary<string, string>>()
{
{ "MVC-2.1", new Dictionary<string, string>(){ } },
{ "Another-Thing", new Dictionary<string, string>(){ } },
})
}
};
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<string, string>()
{
{ 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<string, Dictionary<string, string>>()
{
{ "MVC-2.1", new Dictionary<string, string>() { { "Extensions", "MVC-2.1;Another-Thing" }, } },
})
},
new TestProjectChangeDescription()
{
RuleName = Rules.RazorExtension.SchemaName,
After = TestProjectRuleSnapshot.CreateItems(Rules.RazorExtension.SchemaName, new Dictionary<string, Dictionary<string, string>>()
{
{ "MVC-2.1", new Dictionary<string, string>(){ } },
{ "Another-Thing", new Dictionary<string, string>(){ } },
})
}
};
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<string, string>() { { "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<string, string>()
{
{ 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<string, Dictionary<string, string>>()
{
{ "MVC-2.1", new Dictionary<string, string>() { { "Extensions", "MVC-2.1;Another-Thing" }, } },
})
},
new TestProjectChangeDescription()
{
RuleName = Rules.RazorExtension.SchemaName,
After = TestProjectRuleSnapshot.CreateItems(Rules.RazorExtension.SchemaName, new Dictionary<string, Dictionary<string, string>>()
{
{ "MVC-2.1", new Dictionary<string, string>(){ } },
{ "Another-Thing", new Dictionary<string, string>(){ } },
})
}
};
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<ErrorReporter>(), Mock.Of<ProjectSnapshotWorker>(), Array.Empty<ProjectSnapshotChangeTrigger>(), workspace)
{
}
protected override void NotifyBackgroundWorker(ProjectSnapshotUpdateContext context)
{
}
}
}
}

View File

@ -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<string, Dictionary<string, string>>()
{
{ "c:\\nuget\\Microsoft.AspNetCore.Mvc.razor.dll", new Dictionary<string, string>() },
}),
},
};
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<string, Dictionary<string, string>>()
{
}),
},
};
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<string, Dictionary<string, string>>()
{
{ "c:\\nuget\\Microsoft.AspNetCore.Mvc.razor.dll", new Dictionary<string, string>() },
}),
},
};
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<string, Dictionary<string, string>>()
{
{ "c:\\nuget\\Microsoft.AspNetCore.Mvc.razor.dll", new Dictionary<string, string>() },
}),
},
};
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<string, Dictionary<string, string>>()
{
{ "c:\\nuget\\Microsoft.AspNetCore.Mvc.razor.dll", new Dictionary<string, string>() },
}),
},
};
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<string, Dictionary<string, string>>()
{
{ "c:\\nuget\\Microsoft.AspNetCore.Mvc.razor.dll", new Dictionary<string, string>() },
}),
},
};
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<string, Dictionary<string, string>>()
{
{ "c:\\nuget\\Microsoft.AspNetCore.Mvc.razor.dll", new Dictionary<string, string>() },
}),
},
};
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<ErrorReporter>(), Mock.Of<ProjectSnapshotWorker>(), Array.Empty<ProjectSnapshotChangeTrigger>(), workspace)
{
}
protected override void NotifyBackgroundWorker(ProjectSnapshotUpdateContext context)
{
}
}
}
}

View File

@ -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<DefaultProjectSnapshot>().FirstOrDefault(s => s.UnderlyingProject.Id == id);
return Projects.Cast<DefaultProjectSnapshot>().FirstOrDefault(s => s.FilePath == hostProject.FilePath);
}
public DefaultProjectSnapshot GetSnapshot(Project workspaceProject)
{
return Projects.Cast<DefaultProjectSnapshot>().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);
}
}

View File

@ -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<AssemblyName> GetAssemblyNameAsync()
{
return Task.FromResult(AssemblyName);
}
public Task<bool> GetCopyLocalAsync()
{
throw new System.NotImplementedException();
}
public Task<bool> GetCopyLocalSatelliteAssembliesAsync()
{
throw new System.NotImplementedException();
}
public Task<string> GetDescriptionAsync()
{
throw new System.NotImplementedException();
}
public Task<string> GetFullPathAsync()
{
return Task.FromResult(FullPath);
}
public Task<string> GetNameAsync()
{
throw new System.NotImplementedException();
}
public Task<bool> GetReferenceOutputAssemblyAsync()
{
throw new System.NotImplementedException();
}
public Task<string> GetRequiredTargetFrameworkAsync()
{
throw new System.NotImplementedException();
}
public Task<bool> GetSpecificVersionAsync()
{
throw new System.NotImplementedException();
}
public Task<bool> IsWinMDFileAsync()
{
throw new System.NotImplementedException();
}
}
}

View File

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

View File

@ -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<string, string> properties)
{
return new TestProjectRuleSnapshot(
ruleName,
items: ImmutableDictionary<string, IImmutableDictionary<string, string>>.Empty,
properties: properties.ToImmutableDictionary(),
dataSourceVersions: ImmutableDictionary<NamedIdentity, IComparable>.Empty);
}
public static TestProjectRuleSnapshot CreateItems(string ruleName, Dictionary<string, Dictionary<string, string>> items)
{
return new TestProjectRuleSnapshot(
ruleName,
items: items.ToImmutableDictionary(kvp => kvp.Key, kvp => (IImmutableDictionary<string, string>)kvp.Value.ToImmutableDictionary()),
properties: ImmutableDictionary<string, string>.Empty,
dataSourceVersions: ImmutableDictionary<NamedIdentity, IComparable>.Empty);
}
public TestProjectRuleSnapshot(
string ruleName,
IImmutableDictionary<string, IImmutableDictionary<string, string>> items,
IImmutableDictionary<string, string> properties,
IImmutableDictionary<NamedIdentity, IComparable> 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<string, string> values)
{
Items = Items.SetItem(key, values.ToImmutableDictionary());
}
public string RuleName { get; }
public IImmutableDictionary<string, IImmutableDictionary<string, string>> Items { get; set; }
public IImmutableDictionary<string, string> Properties { get; set; }
public IImmutableDictionary<NamedIdentity, IComparable> DataSourceVersions { get; }
}
}

View File

@ -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<IProjectSubscriptionUpdate> CreateUpdate(params TestProjectChangeDescription[] descriptions)
{
return new ProjectVersionedValue<IProjectSubscriptionUpdate>(
value: new ProjectSubscriptionUpdate(
projectChanges: descriptions.ToImmutableDictionary(d => d.RuleName, d => (IProjectChangeDescription)d),
projectConfiguration: ActiveConfiguredProject.ProjectConfiguration),
dataSourceVersions: ImmutableDictionary<NamedIdentity, IComparable>.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<TestUnconfiguredProject>();
Services = new TestProjectServices(this);
}
public List<TestUnconfiguredProject> LoadedUnconfiguredProjects { get; }
public TestProjectServices Services { get; }
IEnumerable<UnconfiguredProject> IProjectService.LoadedUnconfiguredProjects => throw new NotImplementedException();
IProjectServices IProjectService.Services => Services;
IProjectCapabilitiesScope IProjectService.Capabilities => throw new NotImplementedException();
Task<UnconfiguredProject> IProjectService.LoadProjectAsync(string projectLocation, IImmutableSet<string> projectCapabilities)
{
throw new NotImplementedException();
}
Task<UnconfiguredProject> IProjectService.LoadProjectAsync(XmlReader reader, IImmutableSet<string> projectCapabilities)
{
throw new NotImplementedException();
}
Task<UnconfiguredProject> IProjectService.LoadProjectAsync(string projectLocation, bool delayAutoLoad, IImmutableSet<string> 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<TestConfiguredProject>();
}
public TestProjectService ProjectService { get; }
public string FullPath { get; set; }
public List<TestConfiguredProject> 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<ConfiguredProject> UnconfiguredProject.LoadedConfiguredProjects => LoadedConfiguredProjects;
bool UnconfiguredProject.IsLoading => throw new NotImplementedException();
event AsyncEventHandler UnconfiguredProject.ProjectUnloading
{
add
{
throw new NotImplementedException();
}
remove
{
throw new NotImplementedException();
}
}
event AsyncEventHandler<ProjectRenamedEventArgs> UnconfiguredProject.ProjectRenaming
{
add
{
}
remove
{
}
}
event AsyncEventHandler<ProjectRenamedEventArgs> UnconfiguredProject.ProjectRenamedOnWriter
{
add
{
throw new NotImplementedException();
}
remove
{
throw new NotImplementedException();
}
}
event AsyncEventHandler<ProjectRenamedEventArgs> UnconfiguredProject.ProjectRenamed
{
add
{
throw new NotImplementedException();
}
remove
{
throw new NotImplementedException();
}
}
Task<bool> UnconfiguredProject.CanRenameAsync(string newFilePath)
{
throw new NotImplementedException();
}
Task<Encoding> UnconfiguredProject.GetFileEncodingAsync()
{
throw new NotImplementedException();
}
Task<bool> UnconfiguredProject.GetIsDirtyAsync()
{
throw new NotImplementedException();
}
Task<ConfiguredProject> UnconfiguredProject.GetSuggestedConfiguredProjectAsync()
{
throw new NotImplementedException();
}
Task<ConfiguredProject> UnconfiguredProject.LoadConfiguredProjectAsync(string name, IImmutableDictionary<string, string> configurationProperties)
{
throw new NotImplementedException();
}
Task<ConfiguredProject> 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<string, string>.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<IComparable> 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<IAdditionalRuleDefinitions> IAdditionalRuleDefinitionsService.AdditionalRuleDefinitions => throw new NotImplementedException();
IReceivableSourceBlock<IProjectVersionedValue<IAdditionalRuleDefinitions>> IProjectValueDataSource<IAdditionalRuleDefinitions>.SourceBlock => throw new NotImplementedException();
ISourceBlock<IProjectVersionedValue<object>> 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<string, IPropertyPagesCatalog>()
{
{ "Project", catalog },
};
}
public TestPropertyPagesCatalog Catalog { get; }
public Dictionary<string, IPropertyPagesCatalog> CatalogsByContext { get; }
public IReceivableSourceBlock<IProjectVersionedValue<IProjectCatalogSnapshot>> SourceBlock => throw new NotImplementedException();
public NamedIdentity DataSourceKey => throw new NotImplementedException();
public IComparable DataSourceVersion => throw new NotImplementedException();
ISourceBlock<IProjectVersionedValue<object>> IProjectValueDataSource.SourceBlock => throw new NotImplementedException();
public Task<IPropertyPagesCatalog> GetCatalogAsync(string name, CancellationToken cancellationToken = default)
{
return Task.FromResult(CatalogsByContext[name]);
}
public Task<IImmutableDictionary<string, IPropertyPagesCatalog>> GetCatalogsAsync(CancellationToken cancellationToken = default)
{
return Task.FromResult<IImmutableDictionary<string, IPropertyPagesCatalog>>(CatalogsByContext.ToImmutableDictionary());
}
public IPropertyPagesCatalog GetMemoryOnlyCatalog(string context)
{
return Catalog;
}
public IDisposable Join()
{
throw new NotImplementedException();
}
}
public class TestActiveConfiguredProjectSubscriptionService : IActiveConfiguredProjectSubscriptionService
{
public TestActiveConfiguredProjectSubscriptionService()
{
JointRuleBlock = new BufferBlock<IProjectVersionedValue<IProjectSubscriptionUpdate>>();
JointRuleSource = new TestProjectValueDataSource<IProjectSubscriptionUpdate>(JointRuleBlock);
}
public BufferBlock<IProjectVersionedValue<IProjectSubscriptionUpdate>> JointRuleBlock { get; }
public TestProjectValueDataSource<IProjectSubscriptionUpdate> JointRuleSource { get; }
IReceivableSourceBlock<IProjectVersionedValue<IProjectSnapshot>> IProjectSubscriptionService.ProjectBlock => throw new NotImplementedException();
IProjectValueDataSource<IProjectSnapshot> IProjectSubscriptionService.ProjectSource => throw new NotImplementedException();
IProjectValueDataSource<IProjectImportTreeSnapshot> IProjectSubscriptionService.ImportTreeSource => throw new NotImplementedException();
IProjectValueDataSource<IProjectSharedFoldersSnapshot> IProjectSubscriptionService.SharedFoldersSource => throw new NotImplementedException();
IProjectValueDataSource<IImmutableDictionary<string, IOutputGroup>> IProjectSubscriptionService.OutputGroupsSource => throw new NotImplementedException();
IReceivableSourceBlock<IProjectVersionedValue<IProjectCatalogSnapshot>> IProjectSubscriptionService.ProjectCatalogBlock => throw new NotImplementedException();
IProjectValueDataSource<IProjectCatalogSnapshot> IProjectSubscriptionService.ProjectCatalogSource => throw new NotImplementedException();
IReceivableSourceBlock<IProjectVersionedValue<IProjectSubscriptionUpdate>> IProjectSubscriptionService.ProjectRuleBlock => throw new NotImplementedException();
IProjectValueDataSource<IProjectSubscriptionUpdate> IProjectSubscriptionService.ProjectRuleSource => throw new NotImplementedException();
IReceivableSourceBlock<IProjectVersionedValue<IProjectSubscriptionUpdate>> IProjectSubscriptionService.ProjectBuildRuleBlock => throw new NotImplementedException();
IProjectValueDataSource<IProjectSubscriptionUpdate> IProjectSubscriptionService.ProjectBuildRuleSource => throw new NotImplementedException();
ISourceBlock<IProjectVersionedValue<IProjectSubscriptionUpdate>> IProjectSubscriptionService.JointRuleBlock => JointRuleBlock;
IProjectValueDataSource<IProjectSubscriptionUpdate> IProjectSubscriptionService.JointRuleSource => JointRuleSource;
IReceivableSourceBlock<IProjectVersionedValue<IProjectSubscriptionUpdate>> IProjectSubscriptionService.SourceItemsRuleBlock => throw new NotImplementedException();
IProjectValueDataSource<IProjectSubscriptionUpdate> IProjectSubscriptionService.SourceItemsRuleSource => throw new NotImplementedException();
IReceivableSourceBlock<IProjectVersionedValue<IImmutableSet<string>>> IProjectSubscriptionService.SourceItemRuleNamesBlock => throw new NotImplementedException();
IProjectValueDataSource<IImmutableSet<string>> IProjectSubscriptionService.SourceItemRuleNamesSource => throw new NotImplementedException();
}
public class TestProjectValueDataSource<T> : IProjectValueDataSource<T>
{
public TestProjectValueDataSource(BufferBlock<IProjectVersionedValue<T>> sourceBlock)
{
SourceBlock = sourceBlock;
}
public BufferBlock<IProjectVersionedValue<T>> SourceBlock { get; }
IReceivableSourceBlock<IProjectVersionedValue<T>> IProjectValueDataSource<T>.SourceBlock => SourceBlock;
ISourceBlock<IProjectVersionedValue<object>> IProjectValueDataSource.SourceBlock => throw new NotImplementedException();
NamedIdentity IProjectValueDataSource.DataSourceKey => throw new NotImplementedException();
IComparable IProjectValueDataSource.DataSourceVersion => throw new NotImplementedException();
IDisposable IJoinableProjectValueDataSource.Join()
{
throw new NotImplementedException();
}
}
public class TestPropertyPagesCatalog : IPropertyPagesCatalog
{
private readonly Dictionary<string, IRule> _data;
public TestPropertyPagesCatalog(TestPropertyData[] data)
{
_data = new Dictionary<string, IRule>();
foreach (var category in data.GroupBy(p => p.Category))
{
_data.Add(
category.Key,
CreateRule(category.Select(property => CreateProperty(property.PropertyName, property.Value, property.SetValues))));
}
}
private static IRule CreateRule(IEnumerable<IProperty> properties)
{
var rule = new Mock<IRule>();
rule
.Setup(o => o.GetProperty(It.IsAny<string>()))
.Returns((string propertyName) =>
{
return properties.FirstOrDefault(p => p.Name == propertyName);
});
return rule.Object;
}
private static IProperty CreateProperty(string name, object value, List<object> setValues = null)
{
var property = new Mock<IProperty>();
property.SetupGet(o => o.Name)
.Returns(name);
property.Setup(o => o.GetValueAsync())
.ReturnsAsync(value);
property.As<IEvaluatedProperty>().Setup(p => p.GetEvaluatedValueAtEndAsync()).ReturnsAsync(value.ToString());
property.As<IEvaluatedProperty>().Setup(p => p.GetEvaluatedValueAsync()).ReturnsAsync(value.ToString());
if (setValues != null)
{
property
.Setup(p => p.SetValueAsync(It.IsAny<object>()))
.Callback<object>(obj => setValues.Add(obj))
.Returns(() => Task.CompletedTask);
}
return property.Object;
}
IRule IPropertyPagesCatalog.BindToContext(string schemaName, string file, string itemType, string itemName)
{
_data.TryGetValue(schemaName, out var value);
return value;
}
IRule IPropertyPagesCatalog.BindToContext(string schemaName, IProjectPropertiesContext context)
{
throw new NotImplementedException();
}
IRule IPropertyPagesCatalog.BindToContext(string schemaName, ProjectInstance projectInstance, string itemType, string itemName)
{
throw new NotImplementedException();
}
IRule IPropertyPagesCatalog.BindToContext(string schemaName, ProjectInstance projectInstance, ITaskItem taskItem)
{
throw new NotImplementedException();
}
IReadOnlyCollection<string> IPropertyPagesCatalog.GetProjectLevelPropertyPagesSchemas()
{
throw new NotImplementedException();
}
IReadOnlyCollection<string> IPropertyPagesCatalog.GetPropertyPagesSchemas()
{
throw new NotImplementedException();
}
IReadOnlyCollection<string> IPropertyPagesCatalog.GetPropertyPagesSchemas(string itemType)
{
throw new NotImplementedException();
}
IReadOnlyCollection<string> IPropertyPagesCatalog.GetPropertyPagesSchemas(IEnumerable<string> paths)
{
throw new NotImplementedException();
}
Rule IPropertyPagesCatalog.GetSchema(string schemaName)
{
throw new NotImplementedException();
}
}
public class TestAssemblyReferencesService : IAssemblyReferencesService
{
public TestAssemblyReferencesService()
{
ResolvedReferences = new List<IAssemblyReference>();
}
public List<IAssemblyReference> ResolvedReferences { get; }
Task<AddReferenceResult<IUnresolvedAssemblyReference>> IAssemblyReferencesService.AddAsync(AssemblyName assemblyName, string assemblyPath)
{
throw new NotImplementedException();
}
Task<bool> IAssemblyReferencesService.CanResolveAsync(AssemblyName assemblyName, string assemblyPath)
{
throw new NotImplementedException();
}
Task<bool> IAssemblyReferencesService.ContainsAsync(AssemblyName assemblyName, string assemblyPath)
{
throw new NotImplementedException();
}
Task<IAssemblyReference> IAssemblyReferencesService.GetResolvedReferenceAsync(AssemblyName assemblyName, string assemblyPath)
{
throw new NotImplementedException();
}
Task<IAssemblyReference> IResolvableReferencesService<IUnresolvedAssemblyReference, IAssemblyReference>.GetResolvedReferenceAsync(IUnresolvedAssemblyReference unresolvedReference)
{
throw new NotImplementedException();
}
Task<IImmutableSet<IAssemblyReference>> IResolvableReferencesService<IUnresolvedAssemblyReference, IAssemblyReference>.GetResolvedReferencesAsync()
{
return Task.FromResult<IImmutableSet<IAssemblyReference>>(ResolvedReferences.ToImmutableHashSet());
}
Task<IUnresolvedAssemblyReference> IAssemblyReferencesService.GetUnresolvedReferenceAsync(AssemblyName assemblyName, string assemblyPath)
{
throw new NotImplementedException();
}
Task<IUnresolvedAssemblyReference> IResolvableReferencesService<IUnresolvedAssemblyReference, IAssemblyReference>.GetUnresolvedReferenceAsync(IAssemblyReference resolvedReference)
{
throw new NotImplementedException();
}
Task<IImmutableSet<IUnresolvedAssemblyReference>> IResolvableReferencesService<IUnresolvedAssemblyReference, IAssemblyReference>.GetUnresolvedReferencesAsync()
{
throw new NotImplementedException();
}
Task IAssemblyReferencesService.RemoveAsync(AssemblyName assemblyName, string assemblyPath)
{
throw new NotImplementedException();
}
Task IResolvableReferencesService<IUnresolvedAssemblyReference, IAssemblyReference>.RemoveAsync(IUnresolvedAssemblyReference reference)
{
throw new NotImplementedException();
}
Task IResolvableReferencesService<IUnresolvedAssemblyReference, IAssemblyReference>.RemoveAsync(IEnumerable<IUnresolvedAssemblyReference> references)
{
throw new NotImplementedException();
}
}
public class TestThreadingService : IProjectThreadingService
{
public TestThreadingService()
{
JoinableTaskContext = new JoinableTaskContextNode(new JoinableTaskContext());
JoinableTaskFactory = new JoinableTaskFactory(JoinableTaskContext.Context);
}
public JoinableTaskContextNode JoinableTaskContext { get; }
public JoinableTaskFactory JoinableTaskFactory { get; }
public bool IsOnMainThread => throw new NotImplementedException();
public void ExecuteSynchronously(Func<Task> asyncAction)
{
asyncAction().GetAwaiter().GetResult();
}
public T ExecuteSynchronously<T>(Func<Task<T>> asyncAction)
{
return asyncAction().GetAwaiter().GetResult();
}
public void Fork(
Func<Task> asyncAction,
JoinableTaskFactory factory = null,
UnconfiguredProject unconfiguredProject = null,
ConfiguredProject configuredProject = null,
ErrorReportSettings watsonReportSettings = null,
ProjectFaultSeverity faultSeverity = ProjectFaultSeverity.Recoverable,
ForkOptions options = ForkOptions.Default)
{
throw new NotImplementedException();
}
public IDisposable SuppressProjectExecutionContext()
{
throw new NotImplementedException();
}
public void VerifyOnUIThread()
{
if (!JoinableTaskContext.IsOnMainThread)
{
throw new InvalidOperationException("This isn't the main thread.");
}
}
}
}
}

View File

@ -0,0 +1,18 @@
// 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;
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
public class TestPropertyData
{
public string Category { get; set; }
public string PropertyName { get; set; }
public object Value { get; set; }
public List<object> SetValues { get; set; }
}
}

View File

@ -23,7 +23,7 @@ namespace Microsoft.VisualStudio.RazorExtension.DocumentInfo
_documentTracker = documentTracker;
}
public string Configuration => _documentTracker.Configuration?.DisplayName;
public string Configuration => _documentTracker.Configuration?.ConfigurationName;
public bool IsSupportedDocument => _documentTracker.IsSupportedProject;

View File

@ -79,7 +79,9 @@
<Compile Include="RazorInfo\DocumentInfoViewModel.cs" />
<Compile Include="RazorInfo\DocumentViewModel.cs" />
<Compile Include="NotifyPropertyChanged.cs" />
<Compile Include="RazorInfo\ProjectSnapshotViewModel.cs" />
<Compile Include="RazorInfo\ProjectViewModel.cs" />
<Compile Include="RazorInfo\PropertyViewModel.cs" />
<Compile Include="RazorInfo\RazorInfoToolWindow.cs" />
<Compile Include="RazorInfo\RazorInfoToolWindowCommand.cs" />
<Compile Include="RazorInfo\RazorInfoViewModel.cs" />
@ -296,4 +298,4 @@
<Target Name="GetBuildVersion" Outputs="$(VsixVersion)" />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Import Project="$(VSToolsPath)\VSSDK\Microsoft.VsSDK.targets" Condition="Exists('$(VSToolsPath)\VSSDK\Microsoft.VsSDK.targets')" />
</Project>
</Project>

View File

@ -8,21 +8,10 @@ namespace Microsoft.VisualStudio.RazorExtension.RazorInfo
{
public class ProjectInfoViewModel : NotifyPropertyChanged
{
private ObservableCollection<AssemblyViewModel> _assemblies;
private ObservableCollection<DirectiveViewModel> _directives;
private ObservableCollection<DocumentViewModel> _documents;
private ObservableCollection<TagHelperViewModel> _tagHelpers;
public ObservableCollection<AssemblyViewModel> Assemblies
{
get { return _assemblies; }
set
{
_assemblies = value;
OnPropertyChanged();
}
}
public ObservableCollection<DirectiveViewModel> Directives
{
get { return _directives; }

View File

@ -0,0 +1,36 @@
// 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.
#if RAZOR_EXTENSION_DEVELOPER_MODE
using System.Collections.ObjectModel;
using System.IO;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
namespace Microsoft.VisualStudio.RazorExtension.RazorInfo
{
public class ProjectSnapshotViewModel : NotifyPropertyChanged
{
internal ProjectSnapshotViewModel(ProjectSnapshot project)
{
Project = project;
Id = project.WorkspaceProject?.Id;
Properties = new ObservableCollection<PropertyViewModel>()
{
new PropertyViewModel("Razor Language Version", project.Configuration?.LanguageVersion.ToString()),
new PropertyViewModel("Configuration Name", $"{project.Configuration?.ConfigurationName} ({project.Configuration?.GetType().Name ?? "unknown"})"),
new PropertyViewModel("Workspace Project", project.WorkspaceProject?.Name)
};
}
internal ProjectSnapshot Project { get; }
public string Name => Path.GetFileNameWithoutExtension(Project.FilePath);
public ProjectId Id { get; }
public ObservableCollection<PropertyViewModel> Properties { get; }
}
}
#endif

View File

@ -2,21 +2,35 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#if RAZOR_EXTENSION_DEVELOPER_MODE
using Microsoft.CodeAnalysis;
using System.IO;
namespace Microsoft.VisualStudio.RazorExtension.RazorInfo
{
public class ProjectViewModel : NotifyPropertyChanged
{
public ProjectViewModel(Project project)
private ProjectSnapshotViewModel _snapshot;
internal ProjectViewModel(string filePath)
{
Id = project.Id;
Name = project.Name;
FilePath = filePath;
}
public string FilePath { get; }
public string Name { get; }
public string Name => Path.GetFileNameWithoutExtension(FilePath);
public ProjectId Id { get; }
public bool HasSnapshot => Snapshot != null;
public ProjectSnapshotViewModel Snapshot
{
get => _snapshot;
set
{
_snapshot = value;
OnPropertyChanged();
OnPropertyChanged(nameof(HasSnapshot));
}
}
}
}
#endif

View File

@ -0,0 +1,21 @@
// 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.
#if RAZOR_EXTENSION_DEVELOPER_MODE
namespace Microsoft.VisualStudio.RazorExtension.RazorInfo
{
public class PropertyViewModel : NotifyPropertyChanged
{
internal PropertyViewModel(string name, string value)
{
Name = name;
Value = value;
}
public string Name { get; }
public string Value { get; }
}
}
#endif

View File

@ -4,7 +4,6 @@
#if RAZOR_EXTENSION_DEVELOPER_MODE
using System;
using System.Runtime.InteropServices;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Razor;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using Microsoft.VisualStudio.ComponentModelHost;
@ -18,16 +17,22 @@ namespace Microsoft.VisualStudio.RazorExtension.RazorInfo
[Guid("079e9499-d150-40af-8876-3047f7942c2a")]
public class RazorInfoToolWindow : ToolWindowPane
{
private ProjectExtensibilityConfigurationFactory _configurationFactory;
private IRazorEngineDocumentGenerator _documentGenerator;
private IRazorEngineDirectiveResolver _directiveResolver;
private ProjectSnapshotManager _projectManager;
private TagHelperResolver _tagHelperResolver;
private VisualStudioWorkspace _workspace;
public RazorInfoToolWindow() : base(null)
{
this.Caption = "Razor Info";
this.Content = new RazorInfoToolWindowControl();
Caption = "Razor Info";
Content = new RazorInfoToolWindowControl();
}
private RazorInfoViewModel DataContext
{
get => (RazorInfoViewModel)((RazorInfoToolWindowControl)Content).DataContext;
set => ((RazorInfoToolWindowControl)Content).DataContext = value;
}
protected override void Initialize()
@ -35,16 +40,28 @@ namespace Microsoft.VisualStudio.RazorExtension.RazorInfo
base.Initialize();
var componentModel = (IComponentModel)GetService(typeof(SComponentModel));
_workspace = componentModel.GetService<VisualStudioWorkspace>();
_configurationFactory = componentModel.GetService<ProjectExtensibilityConfigurationFactory>();
_documentGenerator = componentModel.GetService<IRazorEngineDocumentGenerator>();
_directiveResolver = componentModel.GetService<IRazorEngineDirectiveResolver>();
_tagHelperResolver = componentModel.GetService<TagHelperResolver>();
_tagHelperResolver = _workspace.Services.GetLanguageServices(RazorLanguage.Name).GetRequiredService<TagHelperResolver>();
_workspace = componentModel.GetService<VisualStudioWorkspace>();
_workspace.WorkspaceChanged += Workspace_WorkspaceChanged;
_projectManager = _workspace.Services.GetLanguageServices(RazorLanguage.Name).GetRequiredService<ProjectSnapshotManager>();
_projectManager.Changed += ProjectManager_Changed;
Reset(_workspace.CurrentSolution);
DataContext = new RazorInfoViewModel(this, _workspace, _projectManager, _directiveResolver, _tagHelperResolver, _documentGenerator, OnException);
foreach (var project in _projectManager.Projects)
{
DataContext.Projects.Add(new ProjectViewModel(project.FilePath)
{
Snapshot = new ProjectSnapshotViewModel(project),
});
}
if (DataContext.Projects.Count > 0)
{
DataContext.CurrentProject = DataContext.Projects[0];
}
}
protected override void Dispose(bool disposing)
@ -53,28 +70,69 @@ namespace Microsoft.VisualStudio.RazorExtension.RazorInfo
if (disposing)
{
_workspace.WorkspaceChanged -= Workspace_WorkspaceChanged;
_projectManager.Changed -= ProjectManager_Changed;
}
}
private void Reset(Solution solution)
private void ProjectManager_Changed(object sender, ProjectChangeEventArgs e)
{
if (solution == null)
switch (e.Kind)
{
((RazorInfoToolWindowControl)this.Content).DataContext = null;
return;
}
case ProjectChangeKind.Added:
{
var added = new ProjectViewModel(e.Project.FilePath)
{
Snapshot = new ProjectSnapshotViewModel(e.Project),
};
var viewModel = new RazorInfoViewModel(this, _workspace, _configurationFactory, _directiveResolver, _tagHelperResolver, _documentGenerator, OnException);
foreach (var project in solution.Projects)
{
if (project.Language == LanguageNames.CSharp)
{
viewModel.Projects.Add(new ProjectViewModel(project));
}
}
DataContext.Projects.Add(added);
((RazorInfoToolWindowControl)this.Content).DataContext = viewModel;
if (DataContext.Projects.Count == 1)
{
DataContext.CurrentProject = added;
}
break;
}
case ProjectChangeKind.Removed:
{
ProjectViewModel removed = null;
for (var i = DataContext.Projects.Count - 1; i >= 0; i--)
{
var project = DataContext.Projects[i];
if (project.FilePath == e.Project.FilePath)
{
removed = project;
DataContext.Projects.RemoveAt(i);
break;
}
}
if (DataContext.CurrentProject == removed)
{
DataContext.CurrentProject = null;
}
break;
}
case ProjectChangeKind.Changed:
{
ProjectViewModel changed = null;
for (var i = DataContext.Projects.Count - 1; i >= 0; i--)
{
var project = DataContext.Projects[i];
if (project.FilePath == e.Project.FilePath)
{
changed = project;
changed.Snapshot = new ProjectSnapshotViewModel(e.Project);
break;
}
}
break;
}
}
}
private void OnException(Exception ex)
@ -87,24 +145,6 @@ namespace Microsoft.VisualStudio.RazorExtension.RazorInfo
OLEMSGBUTTON.OLEMSGBUTTON_OK,
OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST);
}
private void Workspace_WorkspaceChanged(object sender, WorkspaceChangeEventArgs e)
{
switch (e.Kind)
{
case WorkspaceChangeKind.ProjectAdded:
case WorkspaceChangeKind.ProjectChanged:
case WorkspaceChangeKind.ProjectReloaded:
case WorkspaceChangeKind.ProjectRemoved:
case WorkspaceChangeKind.SolutionAdded:
case WorkspaceChangeKind.SolutionChanged:
case WorkspaceChangeKind.SolutionCleared:
case WorkspaceChangeKind.SolutionReloaded:
case WorkspaceChangeKind.SolutionRemoved:
Reset(e.NewSolution);
break;
}
}
}
}
#endif

View File

@ -1,162 +1,266 @@
<UserControl x:Class="Microsoft.VisualStudio.RazorExtension.RazorInfo.RazorInfoToolWindowControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:behaviors="clr-namespace:Microsoft.VisualStudio.RazorExtension.Behaviors"
Background="{DynamicResource VsBrush.Window}"
Foreground="{DynamicResource VsBrush.WindowText}"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="300"
Name="RazorInfoToolWindow">
<UserControl.Resources>
<Style x:Key="{x:Type Button}">
<Setter Property="FrameworkElement.Margin"
Value="5" />
</Style>
<Style x:Key="{x:Type GridSplitter}">
<Setter Property="FrameworkElement.Height"
Value="5" />
<Setter Property="FrameworkElement.HorizontalAlignment"
Value="Stretch" />
<Setter Property="FrameworkElement.VerticalAlignment"
Value="Bottom" />
<Setter Property="GridSplitter.ShowsPreview"
Value="True" />
</Style>
<Style x:Key="{x:Type ListBox}">
<Setter Property="FrameworkElement.Margin"
Value="5" />
</Style>
<Style x:Key="{x:Type ListView}">
<Setter Property="FrameworkElement.Margin"
Value="5" />
</Style>
</UserControl.Resources>
<ScrollViewer VerticalScrollBarVisibility="Visible">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Expander Grid.Row="0"
IsExpanded="True">
<Expander.Header>
<Label Content="Select Project" />
</Expander.Header>
<StackPanel>
<ListBox ItemsSource="{Binding Projects}"
SelectedValue="{Binding CurrentProject}"
IsEnabled="{Binding IsSelectionEnabled}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
<Button Content="Load"
Command="{Binding LoadCommand}" />
<UserControl
x:Class="Microsoft.VisualStudio.RazorExtension.RazorInfo.RazorInfoToolWindowControl"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Microsoft.VisualStudio.RazorExtension.RazorInfo"
xmlns:behaviors="clr-namespace:Microsoft.VisualStudio.RazorExtension.Behaviors"
Background="{DynamicResource VsBrush.Window}"
Foreground="{DynamicResource VsBrush.WindowText}"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="300"
Name="RazorInfoToolWindow">
<UserControl.Resources>
<Style
x:Key="{x:Type Button}">
<Setter
Property="FrameworkElement.Margin"
Value="5" />
<Setter
Property="Control.Padding"
Value="5 2 5 2" />
</Style>
<Style
x:Key="{x:Type GridSplitter}">
<Setter
Property="FrameworkElement.Height"
Value="5" />
<Setter
Property="FrameworkElement.HorizontalAlignment"
Value="Stretch" />
<Setter
Property="FrameworkElement.VerticalAlignment"
Value="Bottom" />
<Setter
Property="GridSplitter.ShowsPreview"
Value="True" />
</Style>
<Style
x:Key="{x:Type ComboBox}">
<Setter
Property="FrameworkElement.Margin"
Value="5" />
</Style>
<Style
x:Key="{x:Type Label}">
<Setter
Property="FrameworkElement.Margin"
Value="5" />
</Style>
<Style
x:Key="{x:Type ListBox}">
<Setter
Property="FrameworkElement.Margin"
Value="5" />
</Style>
<Style
x:Key="{x:Type ListView}">
<Setter
Property="FrameworkElement.Margin"
Value="5" />
</Style>
</UserControl.Resources>
<ScrollViewer
VerticalScrollBarVisibility="Visible">
<Grid>
<Grid.RowDefinitions>
<RowDefinition
Height="Auto" />
<RowDefinition
Height="Auto" />
<RowDefinition
Height="Auto" />
<RowDefinition
Height="Auto" />
<RowDefinition
Height="Auto" />
<RowDefinition
Height="Auto" />
<RowDefinition
Height="Auto" />
<RowDefinition
Height="Auto" />
<RowDefinition
Height="Auto" />
<RowDefinition
Height="Auto" />
<RowDefinition
Height="Auto" />
<RowDefinition
Height="Auto" />
</Grid.RowDefinitions>
<Grid
Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition
Width="Auto" />
<ColumnDefinition
Width="*" />
<ColumnDefinition
Width="Auto" />
</Grid.ColumnDefinitions>
<Label
Grid.Column="0"
Content="Select Project" />
<ComboBox
Grid.Column="1"
ItemsSource="{Binding Projects}"
SelectedValue="{Binding CurrentProject}"
IsEnabled="{Binding IsSelectionEnabled}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel
Orientation="Horizontal">
<Label
Margin="0"
Content="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ComboBox>
<Button
Grid.Column="2"
Content="Load"
Command="{Binding LoadCommand}" />
</Grid>
<GridSplitter
Grid.Row="1"
IsEnabled="False" />
<Grid
Grid.Row="2"
IsEnabled="{Binding CurrentProject.HasSnapshot}"
Height="150">
<ListView
ItemsSource="{Binding CurrentProject.Snapshot.Properties}">
<ListView.View>
<GridView
AllowsColumnReorder="False">
<GridViewColumn
Width="150">
<GridViewColumnHeader
Content="Property Name" />
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock
TextWrapping="NoWrap"
TextTrimming="CharacterEllipsis"
Text="{Binding Path=Name, Mode=OneWay}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
<GridViewColumn
Width="Auto">
<GridViewColumnHeader
Content="Property Value" />
<GridViewColumn.CellTemplate>
<DataTemplate>
<TextBlock
TextAlignment="Right"
Margin="0"
TextWrapping="NoWrap"
TextTrimming="CharacterEllipsis"
Text="{Binding Path=Value, Mode=OneWay}" />
</DataTemplate>
</GridViewColumn.CellTemplate>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
</Grid>
<GridSplitter
Grid.Row="3" />
<Expander
Grid.Row="4"
IsEnabled="{Binding CurrentProjectInfo, TargetNullValue=False}">
<Expander.Header>
<Label
Content="Tag Helpers" />
</Expander.Header>
<ListView
ItemsSource="{Binding CurrentProjectInfo.TagHelpers}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel
Orientation="Horizontal">
<Label
Content="{Binding TargetElement}" />
<Label
Content="{Binding TypeName}" />
<Label
Content="{Binding AssemblyName}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListView>
</Expander>
<GridSplitter
Grid.Row="5" />
<Expander
Grid.Row="6"
IsEnabled="{Binding CurrentProjectInfo, TargetNullValue=False}">
<Expander.Header>
<Label
Content="Directives" />
</Expander.Header>
<ListView
ItemsSource="{Binding CurrentProjectInfo.Directives}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel
Orientation="Horizontal">
<Label
Content="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListView>
</Expander>
<GridSplitter
Grid.Row="7" />
<Expander
Grid.Row="8"
IsEnabled="{Binding CurrentProjectInfo, TargetNullValue=False}">
<Expander.Header>
<Label
Content="Select Document" />
</Expander.Header>
<StackPanel>
<ListBox
ItemsSource="{Binding CurrentProjectInfo.Documents}"
SelectedValue="{Binding CurrentDocument}"
IsEnabled="{Binding IsSelectionEnabled}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel
Orientation="Horizontal">
<Label
Content="{Binding FilePath}" />
</StackPanel>
</Expander>
<GridSplitter Grid.Row="1" />
<Expander Grid.Row="2"
IsEnabled="{Binding CurrentProjectInfo, TargetNullValue=False}">
<Expander.Header>
<Label Content="Assemblies" />
</Expander.Header>
<ListView ItemsSource="{Binding CurrentProjectInfo.Assemblies}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Name}">
<Label.ToolTip>
<ToolTip Content="{Binding FilePath}" />
</Label.ToolTip>
</Label>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListView>
</Expander>
<GridSplitter Grid.Row="3" />
<Expander Grid.Row="4"
IsEnabled="{Binding CurrentProjectInfo, TargetNullValue=False}" >
<Expander.Header>
<Label Content="Tag Helpers" />
</Expander.Header>
<ListView ItemsSource="{Binding CurrentProjectInfo.TagHelpers}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding TargetElement}" />
<Label Content="{Binding TypeName}" />
<Label Content="{Binding AssemblyName}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListView>
</Expander>
<GridSplitter Grid.Row="5" />
<Expander Grid.Row="6"
IsEnabled="{Binding CurrentProjectInfo, TargetNullValue=False}">
<Expander.Header>
<Label Content="Directives" />
</Expander.Header>
<ListView ItemsSource="{Binding CurrentProjectInfo.Directives}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding Name}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListView>
</Expander>
<GridSplitter Grid.Row="7" />
<Expander Grid.Row="8"
IsEnabled="{Binding CurrentProjectInfo, TargetNullValue=False}">
<Expander.Header>
<Label Content="Select Document" />
</Expander.Header>
<StackPanel>
<ListBox ItemsSource="{Binding CurrentProjectInfo.Documents}"
SelectedValue="{Binding CurrentDocument}"
IsEnabled="{Binding IsSelectionEnabled}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Label Content="{Binding FilePath}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
<Button Content="Generate"
Command="{Binding GenerateCommand}" />
</StackPanel>
</Expander>
<GridSplitter Grid.Row="9" />
<Expander Grid.Row="10"
IsEnabled="{Binding CurrentDocumentInfo, TargetNullValue=False}">
<Expander.Header>
<Label Content="Generated Code" />
</Expander.Header>
<TextBlock Text="{Binding CurrentDocumentInfo.Text}"
TextWrapping="Wrap" />
</Expander>
<GridSplitter Grid.Row="11" />
</Grid>
</ScrollViewer>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ListBox>
<Button
Content="Generate"
Command="{Binding GenerateCommand}" />
</StackPanel>
</Expander>
<GridSplitter
Grid.Row="9" />
<Expander
Grid.Row="10"
IsEnabled="{Binding CurrentDocumentInfo, TargetNullValue=False}">
<Expander.Header>
<Label
Content="Generated Code" />
</Expander.Header>
<TextBlock
Text="{Binding CurrentDocumentInfo.Text}"
TextWrapping="Wrap" />
</Expander>
<GridSplitter
Grid.Row="11" />
</Grid>
</ScrollViewer>
</UserControl>

View File

@ -24,12 +24,12 @@ namespace Microsoft.VisualStudio.RazorExtension.RazorInfo
{
internal class RazorInfoViewModel : NotifyPropertyChanged
{
private readonly ProjectExtensibilityConfigurationFactory _configurationFactory;
private readonly IRazorEngineDirectiveResolver _directiveResolver;
private readonly IRazorEngineDocumentGenerator _documentGenerator;
private readonly TagHelperResolver _tagHelperResolver;
private readonly IServiceProvider _services;
private readonly Workspace _workspace;
private readonly ProjectSnapshotManager _projectManager;
private readonly Action<Exception> _errorHandler;
private DocumentViewModel _currentDocument;
@ -43,7 +43,7 @@ namespace Microsoft.VisualStudio.RazorExtension.RazorInfo
public RazorInfoViewModel(
IServiceProvider services,
Workspace workspace,
ProjectExtensibilityConfigurationFactory configurationFactory,
ProjectSnapshotManager projectManager,
IRazorEngineDirectiveResolver directiveResolver,
TagHelperResolver tagHelperResolver,
IRazorEngineDocumentGenerator documentGenerator,
@ -51,7 +51,7 @@ namespace Microsoft.VisualStudio.RazorExtension.RazorInfo
{
_services = services;
_workspace = workspace;
_configurationFactory = configurationFactory;
_projectManager = projectManager;
_directiveResolver = directiveResolver;
_tagHelperResolver = tagHelperResolver;
_documentGenerator = documentGenerator;
@ -94,7 +94,7 @@ namespace Microsoft.VisualStudio.RazorExtension.RazorInfo
CurrentProjectInfo = null; // Clear cached value
}
}
public ProjectInfoViewModel CurrentProjectInfo
{
get { return _currentProjectInfo; }
@ -142,7 +142,7 @@ namespace Microsoft.VisualStudio.RazorExtension.RazorInfo
private bool CanExecuteLoad(object state)
{
return !IsLoading && CurrentProject != null;
return !IsLoading && CurrentProject?.Snapshot?.Project?.WorkspaceProject != null;
}
private void ExecuteLoad(object state)
@ -152,7 +152,7 @@ namespace Microsoft.VisualStudio.RazorExtension.RazorInfo
private bool CanExecuteGenerate(object state)
{
return !IsLoading && CurrentDocument != null;
return !IsLoading && CurrentDocument != null && CurrentProject?.Snapshot?.Project?.WorkspaceProject != null;
}
private void ExecuteGenerate(object state)
@ -168,10 +168,9 @@ namespace Microsoft.VisualStudio.RazorExtension.RazorInfo
IsLoading = true;
var solution = _workspace.CurrentSolution;
var project = solution.GetProject(projectViewModel.Id);
var project = solution.GetProject(projectViewModel.Snapshot.Project.WorkspaceProject.Id);
var documents = GetCshtmlDocuments(project);
var configuration = await _configurationFactory.GetConfigurationAsync(project);
var directives = await _directiveResolver.GetRazorEngineDirectivesAsync(_workspace, project);
var assemblyFilters = project.MetadataReferences
@ -184,7 +183,6 @@ namespace Microsoft.VisualStudio.RazorExtension.RazorInfo
CurrentProjectInfo = new ProjectInfoViewModel()
{
Assemblies = new ObservableCollection<AssemblyViewModel>(configuration.Assemblies.Select(a => new AssemblyViewModel(a))),
Directives = new ObservableCollection<DirectiveViewModel>(directives.Select(d => new DirectiveViewModel(d))),
Documents = new ObservableCollection<DocumentViewModel>(documents.Select(d => new DocumentViewModel(d))),
TagHelpers = new ObservableCollection<TagHelperViewModel>(resolutionResult.Descriptors.Select(t => new TagHelperViewModel(t))),
@ -232,7 +230,7 @@ namespace Microsoft.VisualStudio.RazorExtension.RazorInfo
if (text != null)
{
var project = _workspace.CurrentSolution.GetProject(CurrentProject.Id);
var project = _workspace.CurrentSolution.GetProject(CurrentProject.Snapshot.Project.WorkspaceProject.Id);
var generated = await _documentGenerator.GenerateDocumentAsync(_workspace, project, documentViewModel.FilePath, text);
CurrentDocumentInfo = new DocumentInfoViewModel(generated);