Add host project system for VS4Mac.

- Tied into VS4Macs ProjectExtensions in order to bootstrap our Razor world.
- We currently watch all DotNet projects with the expectation that they're the only ones that can potentially turn into Razor compatible projects.
- Added a fallback Razor project host which is used for pre-Razor SDK Razor versions (< 2.1).
- Added a default Razor project host which consumes all MSBuild data from the users packages and sets up the Razor world accordingly.
- Had to modify some existing contracts to work better with new expectations. one of these was the VS4Mac specific Workspace accessor; essentially we needed to be able to lookup a workspace from a solution.
- Some of our previous expectations about addins were wrong (not being able to directly reference your libraries). To avoid using reflection to bootstrap our types I tried out directly referencing our libraries and all worked fine.
- Refactored the DefaultRazorProjectHost in windows (since we had to in Mac) for testing purposes.

#2081
This commit is contained in:
N. Taylor Mullen 2018-03-02 13:53:00 -08:00
parent 492e958114
commit 1d602d1205
23 changed files with 2106 additions and 55 deletions

View File

@ -36,7 +36,7 @@
<MicrosoftVisualStudioShellInterop90PackageVersion>9.0.30729</MicrosoftVisualStudioShellInterop90PackageVersion>
<MicrosoftVisualStudioShellInteropPackageVersion>7.10.6071</MicrosoftVisualStudioShellInteropPackageVersion>
<MicrosoftVisualStudioTextUIPackageVersion>15.6.161-preview</MicrosoftVisualStudioTextUIPackageVersion>
<MonoAddinsPackageVersion>1.3.7</MonoAddinsPackageVersion>
<MonoAddinsPackageVersion>1.3.8</MonoAddinsPackageVersion>
<MonoDevelopSdkPackageVersion>1.0.1</MonoDevelopSdkPackageVersion>
<MoqPackageVersion>4.7.49</MoqPackageVersion>
<NETStandardLibrary20PackageVersion>2.0.1</NETStandardLibrary20PackageVersion>

View File

@ -3,6 +3,7 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Razor.Performance, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.VisualStudio.Mac.RazorAddin, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.CodeAnalysis.Razor.Workspaces.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.CodeAnalysis.Remote.Razor, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.VisualStudio.Editor.Razor, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]

View File

@ -2,6 +2,8 @@
// 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.ComponentModel.Composition;
using System.Linq;
using System.Threading;
@ -10,6 +12,9 @@ using System.Threading.Tasks.Dataflow;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.VisualStudio.LanguageServices;
using Microsoft.VisualStudio.ProjectSystem;
using Microsoft.VisualStudio.ProjectSystem.Properties;
using ProjectState = System.Collections.Immutable.IImmutableDictionary<string, Microsoft.VisualStudio.ProjectSystem.Properties.IProjectRuleSnapshot>;
using ProjectStateItem = System.Collections.Generic.KeyValuePair<string, System.Collections.Immutable.IImmutableDictionary<string, string>>;
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
@ -82,47 +87,180 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
await ExecuteWithLock(async () =>
{
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 (TryGetConfiguration(update.Value.CurrentState, out var configuration))
{
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();
var hostProject = new HostProject(CommonServices.UnconfiguredProject.FullPath, configuration);
await UpdateProjectUnsafeAsync(hostProject).ConfigureAwait(false);
}
if (configuration == null)
else
{
// Ok we can't find a language version. Let's assume this project isn't using Razor then.
// Ok we can't find a configuration. 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);
});
}, registerFaultHandler: true);
}
// Internal for testing
internal static bool TryGetConfiguration(
ProjectState projectState,
out RazorConfiguration configuration)
{
if (!TryGetDefaultConfiguration(projectState, out var defaultConfiguration))
{
configuration = null;
return false;
}
if (!TryGetLanguageVersion(projectState, out var languageVersion))
{
configuration = null;
return false;
}
if (!TryGetConfigurationItem(defaultConfiguration, projectState, out var configurationItem))
{
configuration = null;
return false;
}
if (!TryGetConfiguredExtensionNames(configurationItem, out var configuredExtensionNames))
{
configuration = null;
return false;
}
if (!TryGetExtensions(configuredExtensionNames, projectState, out var extensions))
{
configuration = null;
return false;
}
configuration = new ProjectSystemRazorConfiguration(languageVersion, configurationItem.Key, extensions);
return true;
}
// Internal for testing
internal static bool TryGetDefaultConfiguration(ProjectState projectState, out string defaultConfiguration)
{
if (!projectState.TryGetValue(Rules.RazorGeneral.SchemaName, out var rule))
{
defaultConfiguration = null;
return false;
}
if (!rule.Properties.TryGetValue(Rules.RazorGeneral.RazorDefaultConfigurationProperty, out defaultConfiguration))
{
defaultConfiguration = null;
return false;
}
if (string.IsNullOrEmpty(defaultConfiguration))
{
defaultConfiguration = null;
return false;
}
return true;
}
// Internal for testing
internal static bool TryGetLanguageVersion(ProjectState projectState, out RazorLanguageVersion languageVersion)
{
if (!projectState.TryGetValue(Rules.RazorGeneral.SchemaName, out var rule))
{
languageVersion = null;
return false;
}
if (!rule.Properties.TryGetValue(Rules.RazorGeneral.RazorLangVersionProperty, out var languageVersionValue))
{
languageVersion = null;
return false;
}
if (string.IsNullOrEmpty(languageVersionValue))
{
languageVersion = null;
return false;
}
if (!RazorLanguageVersion.TryParse(languageVersionValue, out languageVersion))
{
languageVersion = RazorLanguageVersion.Latest;
}
return true;
}
// Internal for testing
internal static bool TryGetConfigurationItem(
string configuration,
ProjectState projectState,
out ProjectStateItem configurationItem)
{
if (!projectState.TryGetValue(Rules.RazorConfiguration.PrimaryDataSourceItemType, out var configurationState))
{
configurationItem = default(ProjectStateItem);
return false;
}
var razorConfigurationItems = configurationState.Items;
foreach (var item in razorConfigurationItems)
{
if (item.Key == configuration)
{
configurationItem = item;
return true;
}
}
configurationItem = default(ProjectStateItem);
return false;
}
// Internal for testing
internal static bool TryGetConfiguredExtensionNames(ProjectStateItem configurationItem, out string[] configuredExtensionNames)
{
if (!configurationItem.Value.TryGetValue(Rules.RazorConfiguration.ExtensionsProperty, out var extensionNamesValue))
{
configuredExtensionNames = null;
return false;
}
if (string.IsNullOrEmpty(extensionNamesValue))
{
configuredExtensionNames = null;
return false;
}
configuredExtensionNames = extensionNamesValue.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
return true;
}
// Internal for testing
internal static bool TryGetExtensions(string[] configuredExtensionNames, ProjectState projectState, out ProjectSystemRazorExtension[] extensions)
{
if (!projectState.TryGetValue(Rules.RazorExtension.PrimaryDataSourceItemType, out var extensionState))
{
extensions = null;
return false;
}
var extensionItems = extensionState.Items;
var extensionList = new List<ProjectSystemRazorExtension>();
foreach (var item in extensionItems)
{
var extensionName = item.Key;
if (configuredExtensionNames.Contains(extensionName))
{
extensionList.Add(new ProjectSystemRazorExtension(extensionName));
}
}
extensions = extensionList.ToArray();
return true;
}
}
}

View File

@ -13,12 +13,13 @@ namespace Microsoft.VisualStudio.Mac.LanguageServices.Razor
{
[System.Composition.Shared]
[Export(typeof(VisualStudioWorkspaceAccessor))]
internal class DefaultVisualStudioWorkspaceAccessor : VisualStudioWorkspaceAccessor
[Export(typeof(VisualStudioMacWorkspaceAccessor))]
internal class DefaultVisualStudioMacWorkspaceAccessor : VisualStudioMacWorkspaceAccessor
{
private readonly TextBufferProjectService _projectService;
[ImportingConstructor]
public DefaultVisualStudioWorkspaceAccessor(TextBufferProjectService projectService)
public DefaultVisualStudioMacWorkspaceAccessor(TextBufferProjectService projectService)
{
if (projectService == null)
{
@ -56,7 +57,17 @@ namespace Microsoft.VisualStudio.Mac.LanguageServices.Razor
return false;
}
workspace = TypeSystemService.GetWorkspace(hostSolution);
return TryGetWorkspace(hostSolution, out workspace);
}
public override bool TryGetWorkspace(Solution solution, out Workspace workspace)
{
if (solution == null)
{
throw new ArgumentNullException(nameof(solution));
}
workspace = TypeSystemService.GetWorkspace(solution);
// Workspace cannot be null at this point. If TypeSystemService.GetWorkspace isn't able to find a corresponding
// workspace it returns an empty workspace. Therefore, in order to see if we have a valid workspace we need to

View File

@ -17,6 +17,7 @@ namespace Microsoft.VisualStudio.Mac.LanguageServices.Razor.Editor
[Export(typeof(TextBufferProjectService))]
internal class DefaultTextBufferProjectService : TextBufferProjectService
{
private const string DotNetCoreRazorCapability = "DotNetCoreRazor | AspNetCore";
private readonly ITextDocumentFactoryService _documentFactory;
[ImportingConstructor]
@ -73,7 +74,20 @@ namespace Microsoft.VisualStudio.Mac.LanguageServices.Razor.Editor
}
// VisualStudio for Mac only supports ASP.NET Core Razor.
public override bool IsSupportedProject(object project) => project is DotNetProject;
public override bool IsSupportedProject(object project)
{
if (!(project is DotNetProject dotNetProject))
{
return false;
}
if (!dotNetProject.IsCapabilityMatch(DotNetCoreRazorCapability))
{
return false;
}
return true;
}
public override string GetProjectName(object project)
{

View File

@ -0,0 +1,155 @@
// 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.CodeAnalysis.Razor;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using Microsoft.VisualStudio.Editor.Razor;
using MonoDevelop.Projects;
namespace Microsoft.VisualStudio.Mac.LanguageServices.Razor.ProjectSystem
{
internal class DefaultDotNetProjectHost : DotNetProjectHost
{
private const string ExplicitRazorConfigurationCapability = "DotNetCoreRazorConfiguration";
private readonly DotNetProject _project;
private readonly ForegroundDispatcher _foregroundDispatcher;
private readonly VisualStudioMacWorkspaceAccessor _workspaceAccessor;
private readonly TextBufferProjectService _projectService;
private RazorProjectHostBase _razorProjectHost;
public DefaultDotNetProjectHost(
DotNetProject project,
ForegroundDispatcher foregroundDispatcher,
VisualStudioMacWorkspaceAccessor workspaceAccessor,
TextBufferProjectService projectService)
{
if (project == null)
{
throw new ArgumentNullException(nameof(project));
}
if (foregroundDispatcher == null)
{
throw new ArgumentNullException(nameof(foregroundDispatcher));
}
if (workspaceAccessor == null)
{
throw new ArgumentNullException(nameof(workspaceAccessor));
}
if (projectService == null)
{
throw new ArgumentNullException(nameof(projectService));
}
_project = project;
_foregroundDispatcher = foregroundDispatcher;
_workspaceAccessor = workspaceAccessor;
_projectService = projectService;
}
// Internal for testing
internal DefaultDotNetProjectHost(
ForegroundDispatcher foregroundDispatcher,
VisualStudioMacWorkspaceAccessor workspaceAccessor,
TextBufferProjectService projectService)
{
if (foregroundDispatcher == null)
{
throw new ArgumentNullException(nameof(foregroundDispatcher));
}
if (workspaceAccessor == null)
{
throw new ArgumentNullException(nameof(workspaceAccessor));
}
if (projectService == null)
{
throw new ArgumentNullException(nameof(projectService));
}
_foregroundDispatcher = foregroundDispatcher;
_workspaceAccessor = workspaceAccessor;
_projectService = projectService;
}
public override DotNetProject Project => _project;
public override void Subscribe()
{
_foregroundDispatcher.AssertForegroundThread();
UpdateRazorHostProject();
_project.ProjectCapabilitiesChanged += Project_ProjectCapabilitiesChanged;
_project.Disposing += Project_Disposing;
}
private void Project_Disposing(object sender, EventArgs e)
{
_foregroundDispatcher.AssertForegroundThread();
_project.ProjectCapabilitiesChanged -= Project_ProjectCapabilitiesChanged;
_project.Disposing -= Project_Disposing;
DetatchCurrentRazorProjectHost();
}
private void Project_ProjectCapabilitiesChanged(object sender, EventArgs e) => UpdateRazorHostProject();
// Internal for testing
internal void UpdateRazorHostProject()
{
_foregroundDispatcher.AssertForegroundThread();
DetatchCurrentRazorProjectHost();
if (!_projectService.IsSupportedProject(_project))
{
// Not a Razor compatible project.
return;
}
if (!TryGetProjectSnapshotManager(out var projectSnapshotManager))
{
// Could not get a ProjectSnapshotManager for the current project.
return;
}
if (_project.IsCapabilityMatch(ExplicitRazorConfigurationCapability))
{
// SDK >= 2.1
_razorProjectHost = new DefaultRazorProjectHost(_project, _foregroundDispatcher, projectSnapshotManager);
return;
}
// We're an older version of Razor at this point, SDK < 2.1
_razorProjectHost = new FallbackRazorProjectHost(_project, _foregroundDispatcher, projectSnapshotManager);
}
private bool TryGetProjectSnapshotManager(out ProjectSnapshotManagerBase projectSnapshotManagerBase)
{
if (!_workspaceAccessor.TryGetWorkspace(_project.ParentSolution, out var workspace))
{
// Could not locate workspace for razor project. Project is most likely tearing down.
projectSnapshotManagerBase = null;
return false;
}
var languageService = workspace.Services.GetLanguageServices(RazorLanguage.Name);
projectSnapshotManagerBase = (ProjectSnapshotManagerBase)languageService.GetRequiredService<ProjectSnapshotManager>();
return true;
}
private void DetatchCurrentRazorProjectHost()
{
_razorProjectHost?.Detatch();
_razorProjectHost = null;
}
}
}

View File

@ -0,0 +1,180 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis.Razor;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using MonoDevelop.Projects;
using MonoDevelop.Projects.MSBuild;
namespace Microsoft.VisualStudio.Mac.LanguageServices.Razor.ProjectSystem
{
internal class DefaultRazorProjectHost : RazorProjectHostBase
{
private const string RazorLangVersionProperty = "RazorLangVersion";
private const string RazorDefaultConfigurationProperty = "RazorDefaultConfiguration";
private const string RazorExtensionItemType = "RazorExtension";
private const string RazorConfigurationItemType = "RazorConfiguration";
private const string RazorConfigurationItemTypeExtensionsProperty = "Extensions";
public DefaultRazorProjectHost(
DotNetProject project,
ForegroundDispatcher foregroundDispatcher,
ProjectSnapshotManagerBase projectSnapshotManager)
: base(project, foregroundDispatcher, projectSnapshotManager)
{
}
protected override async Task OnProjectChangedAsync()
{
ForegroundDispatcher.AssertBackgroundThread();
await ExecuteWithLockAsync(async () =>
{
var projectProperties = DotNetProject.MSBuildProject.EvaluatedProperties;
var projectItems = DotNetProject.MSBuildProject.EvaluatedItems;
if (TryGetConfiguration(projectProperties, projectItems, out var configuration))
{
var hostProject = new HostProject(DotNetProject.FileName.FullPath, configuration);
await UpdateHostProjectUnsafeAsync(hostProject).ConfigureAwait(false);
}
else
{
// Ok we can't find a configuration. Let's assume this project isn't using Razor then.
await UpdateHostProjectUnsafeAsync(null).ConfigureAwait(false);
}
});
}
// Internal for testing
internal static bool TryGetConfiguration(
IMSBuildEvaluatedPropertyCollection projectProperties,
IEnumerable<IMSBuildItemEvaluated> projectItems,
out RazorConfiguration configuration)
{
if (!TryGetDefaultConfiguration(projectProperties, out var defaultConfiguration))
{
configuration = null;
return false;
}
if (!TryGetLanguageVersion(projectProperties, out var languageVersion))
{
configuration = null;
return false;
}
if (!TryGetConfigurationItem(defaultConfiguration, projectItems, out var configurationItem))
{
configuration = null;
return false;
}
if (!TryGetConfiguredExtensionNames(configurationItem, out var configuredExtensionNames))
{
configuration = null;
return false;
}
var extensions = GetExtensions(configuredExtensionNames, projectItems);
configuration = new ProjectSystemRazorConfiguration(languageVersion, configurationItem.Include, extensions);
return true;
}
// Internal for testing
internal static bool TryGetDefaultConfiguration(IMSBuildEvaluatedPropertyCollection projectProperties, out string defaultConfiguration)
{
defaultConfiguration = projectProperties.GetValue(RazorDefaultConfigurationProperty);
if (string.IsNullOrEmpty(defaultConfiguration))
{
defaultConfiguration = null;
return false;
}
return true;
}
// Internal for testing
internal static bool TryGetLanguageVersion(IMSBuildEvaluatedPropertyCollection projectProperties, out RazorLanguageVersion languageVersion)
{
var languageVersionValue = projectProperties.GetValue(RazorLangVersionProperty);
if (string.IsNullOrEmpty(languageVersionValue))
{
languageVersion = null;
return false;
}
if (!RazorLanguageVersion.TryParse(languageVersionValue, out languageVersion))
{
languageVersion = RazorLanguageVersion.Latest;
}
return true;
}
// Internal for testing
internal static bool TryGetConfigurationItem(
string configuration,
IEnumerable<IMSBuildItemEvaluated> projectItems,
out IMSBuildItemEvaluated configurationItem)
{
foreach (var item in projectItems)
{
if (item.Name == RazorConfigurationItemType && item.Include == configuration)
{
configurationItem = item;
return true;
}
}
configurationItem = null;
return false;
}
// Internal for testing
internal static bool TryGetConfiguredExtensionNames(IMSBuildItemEvaluated configurationItem, out string[] configuredExtensionNames)
{
var extensionNamesValue = configurationItem.Metadata.GetValue(RazorConfigurationItemTypeExtensionsProperty);
if (string.IsNullOrEmpty(extensionNamesValue))
{
configuredExtensionNames = null;
return false;
}
configuredExtensionNames = extensionNamesValue.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
return true;
}
// Internal for testing
internal static ProjectSystemRazorExtension[] GetExtensions(
string[] configuredExtensionNames,
IEnumerable<IMSBuildItemEvaluated> projectItems)
{
var extensions = new List<ProjectSystemRazorExtension>();
foreach (var item in projectItems)
{
if (item.Name != RazorExtensionItemType)
{
// Not a RazorExtension
continue;
}
var extensionName = item.Include;
if (configuredExtensionNames.Contains(extensionName))
{
extensions.Add(new ProjectSystemRazorExtension(extensionName));
}
}
return extensions.ToArray();
}
}
}

View File

@ -0,0 +1,14 @@
// 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 MonoDevelop.Projects;
namespace Microsoft.VisualStudio.Mac.LanguageServices.Razor.ProjectSystem
{
internal abstract class DotNetProjectHost
{
public abstract DotNetProject Project { get; }
public abstract void Subscribe();
}
}

View File

@ -0,0 +1,57 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.ComponentModel.Composition;
using Microsoft.CodeAnalysis.Razor;
using Microsoft.VisualStudio.Editor.Razor;
using MonoDevelop.Projects;
namespace Microsoft.VisualStudio.Mac.LanguageServices.Razor.ProjectSystem
{
[System.Composition.Shared]
[Export(typeof(DotNetProjectHostFactory))]
internal class DotNetProjectHostFactory
{
private readonly ForegroundDispatcher _foregroundDispatcher;
private readonly VisualStudioMacWorkspaceAccessor _workspaceAccessor;
private readonly TextBufferProjectService _projectService;
[ImportingConstructor]
public DotNetProjectHostFactory(
ForegroundDispatcher foregroundDispatcher,
VisualStudioMacWorkspaceAccessor workspaceAccessor,
TextBufferProjectService projectService)
{
if (foregroundDispatcher == null)
{
throw new ArgumentNullException(nameof(foregroundDispatcher));
}
if (workspaceAccessor == null)
{
throw new ArgumentNullException(nameof(workspaceAccessor));
}
if (projectService == null)
{
throw new ArgumentNullException(nameof(projectService));
}
_foregroundDispatcher = foregroundDispatcher;
_workspaceAccessor = workspaceAccessor;
_projectService = projectService;
}
public DotNetProjectHost Create(DotNetProject project)
{
if (project == null)
{
throw new ArgumentNullException(nameof(project));
}
var projectHost = new DefaultDotNetProjectHost(project, _foregroundDispatcher, _workspaceAccessor, _projectService);
return projectHost;
}
}
}

View File

@ -0,0 +1,99 @@
// 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.IO;
using System.Linq;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Razor;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using MonoDevelop.Projects;
using AssemblyReference = MonoDevelop.Projects.AssemblyReference;
namespace Microsoft.VisualStudio.Mac.LanguageServices.Razor.ProjectSystem
{
internal class FallbackRazorProjectHost : RazorProjectHostBase
{
private const string MvcAssemblyFileName = "Microsoft.AspNetCore.Mvc.Razor.dll";
public FallbackRazorProjectHost(
DotNetProject project,
ForegroundDispatcher foregroundDispatcher,
ProjectSnapshotManagerBase projectSnapshotManager)
: base(project, foregroundDispatcher, projectSnapshotManager)
{
}
protected override async Task OnProjectChangedAsync()
{
ForegroundDispatcher.AssertBackgroundThread();
await ExecuteWithLockAsync(async () =>
{
var referencedAssemblies = await DotNetProject.GetReferencedAssemblies(ConfigurationSelector.Default);
var mvcReference = referencedAssemblies.FirstOrDefault(IsMvcAssembly);
if (mvcReference == null)
{
// Ok we can't find an MVC version. Let's assume this project isn't using Razor then.
await UpdateHostProjectUnsafeAsync(null).ConfigureAwait(false);
return;
}
var version = GetAssemblyVersion(mvcReference.FilePath);
if (version == null)
{
// Ok we can't find an MVC version. Let's assume this project isn't using Razor then.
await UpdateHostProjectUnsafeAsync(null).ConfigureAwait(false);
return;
}
var configuration = FallbackRazorConfiguration.SelectConfiguration(version);
var hostProject = new HostProject(DotNetProject.FileName.FullPath, configuration);
await UpdateHostProjectUnsafeAsync(hostProject).ConfigureAwait(false);
});
}
// Internal for testing
internal static bool IsMvcAssembly(AssemblyReference reference)
{
var fileName = reference?.FilePath.FileName;
if (string.IsNullOrEmpty(fileName))
{
return false;
}
if (string.Equals(reference.FilePath.FileName, MvcAssemblyFileName, StringComparison.OrdinalIgnoreCase))
{
// Mvc assembly
return true;
}
return false;
}
private static Version GetAssemblyVersion(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,169 @@
// 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.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Razor;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using Microsoft.VisualStudio.Threading;
using MonoDevelop.Projects;
namespace Microsoft.VisualStudio.Mac.LanguageServices.Razor.ProjectSystem
{
internal abstract class RazorProjectHostBase
{
// References changes are always triggered when project changes happen.
private const string ProjectChangedHint = "References";
private bool _batchingProjectChanges;
private readonly DotNetProject _dotNetProject;
private readonly ForegroundDispatcher _foregroundDispatcher;
private readonly ProjectSnapshotManagerBase _projectSnapshotManager;
private readonly AsyncSemaphore _onProjectChangedInnerSemaphore;
private readonly AsyncSemaphore _projectChangedSemaphore;
private HostProject _currentHostProject;
public RazorProjectHostBase(
DotNetProject project,
ForegroundDispatcher foregroundDispatcher,
ProjectSnapshotManagerBase projectSnapshotManager)
{
if (project == null)
{
throw new ArgumentNullException(nameof(project));
}
if (foregroundDispatcher == null)
{
throw new ArgumentNullException(nameof(foregroundDispatcher));
}
if (projectSnapshotManager == null)
{
throw new ArgumentNullException(nameof(projectSnapshotManager));
}
_dotNetProject = project;
_foregroundDispatcher = foregroundDispatcher;
_projectSnapshotManager = projectSnapshotManager;
_onProjectChangedInnerSemaphore = new AsyncSemaphore(initialCount: 1);
_projectChangedSemaphore = new AsyncSemaphore(initialCount: 1);
AttachToProject();
}
public DotNetProject DotNetProject => _dotNetProject;
public HostProject HostProject => _currentHostProject;
protected ForegroundDispatcher ForegroundDispatcher => _foregroundDispatcher;
public void Detatch()
{
_foregroundDispatcher.AssertForegroundThread();
DotNetProject.Modified -= DotNetProject_Modified;
UpdateHostProjectForeground(null);
}
protected abstract Task OnProjectChangedAsync();
// Protected virtual for testing
protected virtual void AttachToProject()
{
ForegroundDispatcher.AssertForegroundThread();
DotNetProject.Modified += DotNetProject_Modified;
// Trigger the initial update to the project.
_batchingProjectChanges = true;
Task.Factory.StartNew(ProjectChangedBackgroundAsync, null, CancellationToken.None, TaskCreationOptions.None, ForegroundDispatcher.BackgroundScheduler);
}
// Must be called inside the lock.
protected async Task UpdateHostProjectUnsafeAsync(HostProject newHostProject)
{
_foregroundDispatcher.AssertBackgroundThread();
await Task.Factory.StartNew(UpdateHostProjectForeground, newHostProject, CancellationToken.None, TaskCreationOptions.None, ForegroundDispatcher.ForegroundScheduler);
}
protected async Task ExecuteWithLockAsync(Func<Task> func)
{
using (await _projectChangedSemaphore.EnterAsync().ConfigureAwait(false))
{
await func().ConfigureAwait(false);
}
}
private async Task ProjectChangedBackgroundAsync(object state)
{
ForegroundDispatcher.AssertBackgroundThread();
_batchingProjectChanges = false;
// Ensure ordering, typically we'll only have 1 background thread in flight at a time. However,
// between this line and the one prior another background thread could have also entered this
// method. This is here to protect against us changing the order of project changed events.
using (await _onProjectChangedInnerSemaphore.EnterAsync().ConfigureAwait(false))
{
await OnProjectChangedAsync();
}
}
private void DotNetProject_Modified(object sender, SolutionItemModifiedEventArgs args)
{
if (args == null)
{
throw new ArgumentNullException(nameof(args));
}
ForegroundDispatcher.AssertForegroundThread();
if (_batchingProjectChanges)
{
// Already waiting to recompute host project, no need to do any more work to determine if we're dirty.
return;
}
var projectChanged = args.Any(arg => string.Equals(arg.Hint, ProjectChangedHint, StringComparison.Ordinal));
if (projectChanged)
{
// This method can be spammed for tons of project change events but all we really care about is "are we dirty?".
// Therefore, we re-dispatch here to allow any remaining project change events to fire and to then only have 1 host
// project change trigger; this way we don't spam our own system with re-configure calls.
_batchingProjectChanges = true;
Task.Factory.StartNew(ProjectChangedBackgroundAsync, null, CancellationToken.None, TaskCreationOptions.None, ForegroundDispatcher.BackgroundScheduler);
}
}
private void UpdateHostProjectForeground(object state)
{
_foregroundDispatcher.AssertForegroundThread();
var newHostProject = (HostProject)state;
if (_currentHostProject == null && newHostProject == null)
{
// This is a no-op. This project isn't using Razor.
}
else if (_currentHostProject == null && newHostProject != null)
{
_projectSnapshotManager.HostProjectAdded(newHostProject);
}
else if (_currentHostProject != null && newHostProject == null)
{
_projectSnapshotManager.HostProjectRemoved(HostProject);
}
else
{
_projectSnapshotManager.HostProjectChanged(newHostProject);
}
_currentHostProject = newHostProject;
}
}
}

View File

@ -3,5 +3,6 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.VisualStudio.Mac.RazorAddin, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.VisualStudio.Mac.LanguageServices.Razor.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]

View File

@ -0,0 +1,14 @@
// 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.Editor.Razor;
using MonoDevelop.Projects;
using Workspace = Microsoft.CodeAnalysis.Workspace;
namespace Microsoft.VisualStudio.Mac.LanguageServices.Razor
{
internal abstract class VisualStudioMacWorkspaceAccessor : VisualStudioWorkspaceAccessor
{
public abstract bool TryGetWorkspace(Solution solution, out Workspace workspace);
}
}

View File

@ -3,12 +3,16 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.VisualStudio.LanguageServices.Razor;
using Microsoft.VisualStudio.ProjectSystem;
using Microsoft.VisualStudio.ProjectSystem.Properties;
using Moq;
using Xunit;
using ProjectStateItem = System.Collections.Generic.KeyValuePair<string, System.Collections.Immutable.IImmutableDictionary<string, string>>;
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
@ -24,6 +28,571 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
private Workspace Workspace { get; }
[Fact]
public void TryGetDefaultConfiguration_FailsIfNoRule()
{
// Arrange
var projectState = new Dictionary<string, IProjectRuleSnapshot>().ToImmutableDictionary();
// Act
var result = DefaultRazorProjectHost.TryGetDefaultConfiguration(projectState, out var defaultConfiguration);
// Assert
Assert.False(result);
Assert.Null(defaultConfiguration);
}
[Fact]
public void TryGetDefaultConfiguration_FailsIfNoConfiguration()
{
// Arrange
var projectState = new Dictionary<string, IProjectRuleSnapshot>()
{
[Rules.RazorGeneral.SchemaName] = TestProjectRuleSnapshot.CreateProperties(Rules.RazorGeneral.SchemaName, new Dictionary<string, string>())
}.ToImmutableDictionary();
// Act
var result = DefaultRazorProjectHost.TryGetDefaultConfiguration(projectState, out var defaultConfiguration);
// Assert
Assert.False(result);
Assert.Null(defaultConfiguration);
}
[Fact]
public void TryGetDefaultConfiguration_FailsIfEmptyConfiguration()
{
// Arrange
var projectState = new Dictionary<string, IProjectRuleSnapshot>()
{
[Rules.RazorGeneral.SchemaName] = TestProjectRuleSnapshot.CreateProperties(
Rules.RazorGeneral.SchemaName,
new Dictionary<string, string>()
{
[Rules.RazorGeneral.RazorDefaultConfigurationProperty] = string.Empty
})
}.ToImmutableDictionary();
// Act
var result = DefaultRazorProjectHost.TryGetDefaultConfiguration(projectState, out var defaultConfiguration);
// Assert
Assert.False(result);
Assert.Null(defaultConfiguration);
}
[Fact]
public void TryGetDefaultConfiguration_SucceedsWithValidConfiguration()
{
// Arrange
var expectedConfiguration = "Razor-13.37";
var projectState = new Dictionary<string, IProjectRuleSnapshot>()
{
[Rules.RazorGeneral.SchemaName] = TestProjectRuleSnapshot.CreateProperties(
Rules.RazorGeneral.SchemaName,
new Dictionary<string, string>()
{
[Rules.RazorGeneral.RazorDefaultConfigurationProperty] = expectedConfiguration
})
}.ToImmutableDictionary();
// Act
var result = DefaultRazorProjectHost.TryGetDefaultConfiguration(projectState, out var defaultConfiguration);
// Assert
Assert.True(result);
Assert.Equal(expectedConfiguration, defaultConfiguration);
}
[Fact]
public void TryGetLanguageVersion_FailsIfNoRule()
{
// Arrange
var projectState = new Dictionary<string, IProjectRuleSnapshot>().ToImmutableDictionary();
// Act
var result = DefaultRazorProjectHost.TryGetLanguageVersion(projectState, out var languageVersion);
// Assert
Assert.False(result);
Assert.Null(languageVersion);
}
[Fact]
public void TryGetLanguageVersion_FailsIfNoLanguageVersion()
{
// Arrange
var projectState = new Dictionary<string, IProjectRuleSnapshot>()
{
[Rules.RazorGeneral.SchemaName] = TestProjectRuleSnapshot.CreateProperties(Rules.RazorGeneral.SchemaName, new Dictionary<string, string>())
}.ToImmutableDictionary();
// Act
var result = DefaultRazorProjectHost.TryGetLanguageVersion(projectState, out var languageVersion);
// Assert
Assert.False(result);
Assert.Null(languageVersion);
}
[Fact]
public void TryGetLanguageVersion_FailsIfEmptyLanguageVersion()
{
// Arrange
var projectState = new Dictionary<string, IProjectRuleSnapshot>()
{
[Rules.RazorGeneral.SchemaName] = TestProjectRuleSnapshot.CreateProperties(
Rules.RazorGeneral.SchemaName,
new Dictionary<string, string>()
{
[Rules.RazorGeneral.RazorLangVersionProperty] = string.Empty
})
}.ToImmutableDictionary();
// Act
var result = DefaultRazorProjectHost.TryGetLanguageVersion(projectState, out var languageVersion);
// Assert
Assert.False(result);
Assert.Null(languageVersion);
}
[Fact]
public void TryGetLanguageVersion_SucceedsWithValidLanguageVersion()
{
// Arrange
var projectState = new Dictionary<string, IProjectRuleSnapshot>()
{
[Rules.RazorGeneral.SchemaName] = TestProjectRuleSnapshot.CreateProperties(
Rules.RazorGeneral.SchemaName,
new Dictionary<string, string>()
{
[Rules.RazorGeneral.RazorLangVersionProperty] = "1.0"
})
}.ToImmutableDictionary();
// Act
var result = DefaultRazorProjectHost.TryGetLanguageVersion(projectState, out var languageVersion);
// Assert
Assert.True(result);
Assert.Same(RazorLanguageVersion.Version_1_0, languageVersion);
}
[Fact]
public void TryGetLanguageVersion_SucceedsWithUnknownLanguageVersion_DefaultsToLatest()
{
// Arrange
var projectState = new Dictionary<string, IProjectRuleSnapshot>()
{
[Rules.RazorGeneral.SchemaName] = TestProjectRuleSnapshot.CreateProperties(
Rules.RazorGeneral.SchemaName,
new Dictionary<string, string>()
{
[Rules.RazorGeneral.RazorLangVersionProperty] = "13.37"
})
}.ToImmutableDictionary();
// Act
var result = DefaultRazorProjectHost.TryGetLanguageVersion(projectState, out var languageVersion);
// Assert
Assert.True(result);
Assert.Same(RazorLanguageVersion.Latest, languageVersion);
}
[Fact]
public void TryGetConfigurationItem_FailsNoRazorConfigurationRule()
{
// Arrange
var projectState = new Dictionary<string, IProjectRuleSnapshot>().ToImmutableDictionary();
// Act
var result = DefaultRazorProjectHost.TryGetConfigurationItem("Razor-13.37", projectState, out var configurationItem);
// Assert
Assert.False(result);
}
[Fact]
public void TryGetConfigurationItem_FailsNoRazorConfigurationItems()
{
// Arrange
var projectState = new Dictionary<string, IProjectRuleSnapshot>()
{
[Rules.RazorConfiguration.SchemaName] = TestProjectRuleSnapshot.CreateItems(
Rules.RazorConfiguration.SchemaName,
new Dictionary<string, Dictionary<string, string>>())
}.ToImmutableDictionary();
// Act
var result = DefaultRazorProjectHost.TryGetConfigurationItem("Razor-13.37", projectState, out var configurationItem);
// Assert
Assert.False(result);
}
[Fact]
public void TryGetConfigurationItem_FailsNoMatchingRazorConfigurationItems()
{
// Arrange
var projectState = new Dictionary<string, IProjectRuleSnapshot>()
{
[Rules.RazorConfiguration.SchemaName] = TestProjectRuleSnapshot.CreateItems(
Rules.RazorConfiguration.SchemaName,
new Dictionary<string, Dictionary<string, string>>()
{
["Razor-10.0"] = new Dictionary<string, string>(),
})
}.ToImmutableDictionary();
// Act
var result = DefaultRazorProjectHost.TryGetConfigurationItem("Razor-13.37", projectState, out var configurationItem);
// Assert
Assert.False(result);
}
[Fact]
public void TryGetConfigurationItem_SucceedsForMatchingConfigurationItem()
{
// Arrange
var expectedConfiguration = "Razor-13.37";
var expectedConfigurationValue = new Dictionary<string, string>()
{
[Rules.RazorConfiguration.ExtensionsProperty] = "SomeExtension"
};
var projectState = new Dictionary<string, IProjectRuleSnapshot>()
{
[Rules.RazorConfiguration.SchemaName] = TestProjectRuleSnapshot.CreateItems(
Rules.RazorConfiguration.SchemaName,
new Dictionary<string, Dictionary<string, string>>()
{
[expectedConfiguration] = expectedConfigurationValue
})
}.ToImmutableDictionary();
// Act
var result = DefaultRazorProjectHost.TryGetConfigurationItem(expectedConfiguration, projectState, out var configurationItem);
// Assert
Assert.True(result);
Assert.Equal(expectedConfiguration, configurationItem.Key);
Assert.True(Enumerable.SequenceEqual(expectedConfigurationValue, configurationItem.Value));
}
[Fact]
public void TryGetConfiguredExtensionNames_FailsIfNoExtensions()
{
// Arrange
var extensions = new Dictionary<string, string>().ToImmutableDictionary();
var configurationItem = new ProjectStateItem(Rules.RazorConfiguration.SchemaName, extensions);
// Act
var result = DefaultRazorProjectHost.TryGetConfiguredExtensionNames(configurationItem, out var configuredExtensionnames);
// Assert
Assert.False(result);
Assert.Null(configuredExtensionnames);
}
[Fact]
public void TryGetConfiguredExtensionNames_FailsIfEmptyExtensions()
{
// Arrange
var extensions = new Dictionary<string, string>()
{
[Rules.RazorConfiguration.ExtensionsProperty] = string.Empty
}.ToImmutableDictionary();
var configurationItem = new ProjectStateItem(Rules.RazorConfiguration.SchemaName, extensions);
// Act
var result = DefaultRazorProjectHost.TryGetConfiguredExtensionNames(configurationItem, out var configuredExtensionNames);
// Assert
Assert.False(result);
Assert.Null(configuredExtensionNames);
}
[Fact]
public void TryGetConfiguredExtensionNames_SucceedsIfSingleExtension()
{
// Arrange
var expectedExtensionName = "SomeExtensionName";
var extensions = new Dictionary<string, string>()
{
[Rules.RazorConfiguration.ExtensionsProperty] = expectedExtensionName
}.ToImmutableDictionary();
var configurationItem = new ProjectStateItem(Rules.RazorConfiguration.SchemaName, extensions);
// Act
var result = DefaultRazorProjectHost.TryGetConfiguredExtensionNames(configurationItem, out var configuredExtensionNames);
// Assert
Assert.True(result);
var extensionName = Assert.Single(configuredExtensionNames);
Assert.Equal(expectedExtensionName, extensionName);
}
[Fact]
public void TryGetConfiguredExtensionNames_SucceedsIfMultipleExtensions()
{
// Arrange
var extensions = new Dictionary<string, string>()
{
[Rules.RazorConfiguration.ExtensionsProperty] = "SomeExtensionName;SomeOtherExtensionName"
}.ToImmutableDictionary();
var configurationItem = new ProjectStateItem(Rules.RazorConfiguration.SchemaName, extensions);
// Act
var result = DefaultRazorProjectHost.TryGetConfiguredExtensionNames(configurationItem, out var configuredExtensionNames);
// Assert
Assert.True(result);
Assert.Collection(
configuredExtensionNames,
name => Assert.Equal("SomeExtensionName", name),
name => Assert.Equal("SomeOtherExtensionName", name));
}
[Fact]
public void TryGetExtensions_NoExtensions()
{
// Arrange
var projectState = new Dictionary<string, IProjectRuleSnapshot>().ToImmutableDictionary();
// Act
var result = DefaultRazorProjectHost.TryGetExtensions(new[] { "Extension1", "Extension2" }, projectState, out var extensions);
// Assert
Assert.False(result);
Assert.Null(extensions);
}
[Fact]
public void TryGetExtensions_SucceedsWithUnConfiguredExtensionTypes()
{
// Arrange
var projectState = new Dictionary<string, IProjectRuleSnapshot>()
{
[Rules.RazorExtension.PrimaryDataSourceItemType] = TestProjectRuleSnapshot.CreateItems(
Rules.RazorExtension.PrimaryDataSourceItemType,
new Dictionary<string, Dictionary<string, string>>()
{
["UnconfiguredExtensionName"] = new Dictionary<string, string>()
})
}.ToImmutableDictionary();
// Act
var result = DefaultRazorProjectHost.TryGetExtensions(new[] { "Extension1", "Extension2" }, projectState, out var extensions);
// Assert
Assert.True(result);
Assert.Empty(extensions);
}
[Fact]
public void TryGetExtensions_SucceedsWithSomeConfiguredExtensions()
{
// Arrange
var expectedExtension1Name = "Extension1";
var expectedExtension2Name = "Extension2";
var projectState = new Dictionary<string, IProjectRuleSnapshot>()
{
[Rules.RazorExtension.PrimaryDataSourceItemType] = TestProjectRuleSnapshot.CreateItems(
Rules.RazorExtension.PrimaryDataSourceItemType,
new Dictionary<string, Dictionary<string, string>>()
{
["UnconfiguredExtensionName"] = new Dictionary<string, string>(),
[expectedExtension1Name] = new Dictionary<string, string>(),
[expectedExtension2Name] = new Dictionary<string, string>(),
})
}.ToImmutableDictionary();
// Act
var result = DefaultRazorProjectHost.TryGetExtensions(new[] { expectedExtension1Name, expectedExtension2Name }, projectState, out var extensions);
// Assert
Assert.True(result);
Assert.Collection(
extensions,
extension => Assert.Equal(expectedExtension2Name, extension.ExtensionName),
extension => Assert.Equal(expectedExtension1Name, extension.ExtensionName));
}
[Fact]
public void TryGetConfiguration_FailsIfNoDefaultConfiguration()
{
// Arrange
var projectState = new Dictionary<string, IProjectRuleSnapshot>().ToImmutableDictionary();
// Act
var result = DefaultRazorProjectHost.TryGetConfiguration(projectState, out var configuration);
// Assert
Assert.False(result);
Assert.Null(configuration);
}
[Fact]
public void TryGetConfiguration_FailsIfNoLanguageVersion()
{
// Arrange
var projectState = new Dictionary<string, IProjectRuleSnapshot>()
{
[Rules.RazorGeneral.SchemaName] = TestProjectRuleSnapshot.CreateProperties(
Rules.RazorGeneral.SchemaName,
new Dictionary<string, string>()
{
[Rules.RazorGeneral.RazorDefaultConfigurationProperty] = "13.37"
})
}.ToImmutableDictionary();
// Act
var result = DefaultRazorProjectHost.TryGetConfiguration(projectState, out var configuration);
// Assert
Assert.False(result);
Assert.Null(configuration);
}
[Fact]
public void TryGetConfiguration_FailsIfNoConfigurationItems()
{
// Arrange
var projectState = new Dictionary<string, IProjectRuleSnapshot>()
{
[Rules.RazorGeneral.SchemaName] = TestProjectRuleSnapshot.CreateProperties(
Rules.RazorGeneral.SchemaName,
new Dictionary<string, string>()
{
[Rules.RazorGeneral.RazorDefaultConfigurationProperty] = "13.37",
[Rules.RazorGeneral.RazorLangVersionProperty] = "1.0",
})
}.ToImmutableDictionary();
// Act
var result = DefaultRazorProjectHost.TryGetConfiguration(projectState, out var configuration);
// Assert
Assert.False(result);
Assert.Null(configuration);
}
[Fact]
public void TryGetConfiguration_FailsIfNoConfiguredExtensionNames()
{
// Arrange
var projectState = new Dictionary<string, IProjectRuleSnapshot>()
{
[Rules.RazorGeneral.SchemaName] = TestProjectRuleSnapshot.CreateProperties(
Rules.RazorGeneral.SchemaName,
new Dictionary<string, string>()
{
[Rules.RazorGeneral.RazorDefaultConfigurationProperty] = "13.37",
[Rules.RazorGeneral.RazorLangVersionProperty] = "1.0",
}),
[Rules.RazorConfiguration.SchemaName] = TestProjectRuleSnapshot.CreateItems(
Rules.RazorConfiguration.SchemaName,
new Dictionary<string, Dictionary<string, string>>()
{
["Razor-13.37"] = new Dictionary<string, string>()
})
}.ToImmutableDictionary();
// Act
var result = DefaultRazorProjectHost.TryGetConfiguration(projectState, out var configuration);
// Assert
Assert.False(result);
Assert.Null(configuration);
}
[Fact]
public void TryGetConfiguration_FailsIfNoExtensions()
{
// Arrange
var projectState = new Dictionary<string, IProjectRuleSnapshot>()
{
[Rules.RazorGeneral.SchemaName] = TestProjectRuleSnapshot.CreateProperties(
Rules.RazorGeneral.SchemaName,
new Dictionary<string, string>()
{
[Rules.RazorGeneral.RazorDefaultConfigurationProperty] = "13.37",
[Rules.RazorGeneral.RazorLangVersionProperty] = "1.0",
}),
[Rules.RazorConfiguration.SchemaName] = TestProjectRuleSnapshot.CreateItems(
Rules.RazorConfiguration.SchemaName,
new Dictionary<string, Dictionary<string, string>>()
{
["SomeExtension"] = new Dictionary<string, string>()
{
["Extensions"] = "Razor-13.37"
}
})
}.ToImmutableDictionary();
// Act
var result = DefaultRazorProjectHost.TryGetConfiguration(projectState, out var configuration);
// Assert
Assert.False(result);
Assert.Null(configuration);
}
// This is more of an integration test but is here to test the overall flow/functionality
[Fact]
public void TryGetConfiguration_SucceedsWithAllPreRequisites()
{
// Arrange
var expectedLanguageVersion = RazorLanguageVersion.Version_1_0;
var expectedConfigurationName = "Razor-Test";
var expectedExtension1Name = "Extension1";
var expectedExtension2Name = "Extension2";
var projectState = new Dictionary<string, IProjectRuleSnapshot>()
{
[Rules.RazorGeneral.SchemaName] = TestProjectRuleSnapshot.CreateProperties(
Rules.RazorGeneral.SchemaName,
new Dictionary<string, string>()
{
[Rules.RazorGeneral.RazorDefaultConfigurationProperty] = expectedConfigurationName,
[Rules.RazorGeneral.RazorLangVersionProperty] = "1.0",
}),
[Rules.RazorConfiguration.SchemaName] = TestProjectRuleSnapshot.CreateItems(
Rules.RazorConfiguration.SchemaName,
new Dictionary<string, Dictionary<string, string>>()
{
["UnconfiguredRazorConfiguration"] = new Dictionary<string, string>()
{
["Extensions"] = "Razor-9.0"
},
[expectedConfigurationName] = new Dictionary<string, string>()
{
["Extensions"] = expectedExtension1Name + ";" + expectedExtension2Name
}
}),
[Rules.RazorExtension.PrimaryDataSourceItemType] = TestProjectRuleSnapshot.CreateItems(
Rules.RazorExtension.PrimaryDataSourceItemType,
new Dictionary<string, Dictionary<string, string>>()
{
[expectedExtension1Name] = new Dictionary<string, string>(),
[expectedExtension2Name] = new Dictionary<string, string>(),
})
}.ToImmutableDictionary();
// Act
var result = DefaultRazorProjectHost.TryGetConfiguration(projectState, out var configuration);
// Assert
Assert.True(result);
Assert.Equal(expectedLanguageVersion, configuration.LanguageVersion);
Assert.Equal(expectedConfigurationName, configuration.ConfigurationName);
Assert.Collection(
configuration.Extensions,
extension => Assert.Equal(expectedExtension2Name, extension.ExtensionName),
extension => Assert.Equal(expectedExtension1Name, extension.ExtensionName));
}
[ForegroundFact]
public async Task DefaultRazorProjectHost_ForegroundThread_CreateAndDispose_Succeeds()
{
@ -232,8 +801,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
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));
e => Assert.Equal("Another-Thing", e.ExtensionName),
e => Assert.Equal("MVC-2.0", e.ExtensionName));
await Task.Run(async () => await host.DisposeAsync());
Assert.Empty(ProjectManager.Projects);
@ -443,7 +1012,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
private class TestProjectSnapshotManager : DefaultProjectSnapshotManager
{
public TestProjectSnapshotManager(ForegroundDispatcher dispatcher, Workspace workspace)
public TestProjectSnapshotManager(ForegroundDispatcher dispatcher, Workspace workspace)
: base(dispatcher, Mock.Of<ErrorReporter>(), Mock.Of<ProjectSnapshotWorker>(), Array.Empty<ProjectSnapshotChangeTrigger>(), workspace)
{
}

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 Microsoft.VisualStudio.Editor.Razor;
using Moq;
using Xunit;
namespace Microsoft.VisualStudio.Mac.LanguageServices.Razor.ProjectSystem
{
public class DefaultDotNetProjectHostTest : ForegroundDispatcherTestBase
{
[Fact]
public void UpdateRazorHostProject_UnsupportedProjectNoops()
{
// Arrange
var projectService = new Mock<TextBufferProjectService>();
projectService.Setup(p => p.IsSupportedProject(It.IsAny<object>()))
.Returns(false);
var dotNetProjectHost = new DefaultDotNetProjectHost(
Dispatcher,
Mock.Of<VisualStudioMacWorkspaceAccessor>(),
projectService.Object);
// Act & Assert
dotNetProjectHost.UpdateRazorHostProject();
}
// -------------------------------------------------------------------------------------------
// Purposefully do not have any more tests here because that would involve mocking MonoDevelop
// types. The default constructors for the Solution / DotNetProject MonoDevelop types change
// static classes (they assume they're being created in an IDE).
// -------------------------------------------------------------------------------------------
}
}

View File

@ -0,0 +1,470 @@
// 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 Microsoft.AspNetCore.Razor.Language;
using MonoDevelop.Projects.MSBuild;
using Xunit;
namespace Microsoft.VisualStudio.Mac.LanguageServices.Razor.ProjectSystem
{
public class DefaultRazorProjectHostTest
{
[Fact]
public void TryGetDefaultConfiguration_FailsIfNoConfiguration()
{
// Arrange
var projectProperties = new MSBuildPropertyGroup();
// Act
var result = DefaultRazorProjectHost.TryGetDefaultConfiguration(projectProperties, out var defaultConfiguration);
// Assert
Assert.False(result);
Assert.Null(defaultConfiguration);
}
[Fact]
public void TryGetDefaultConfiguration_FailsIfEmptyConfiguration()
{
// Arrange
var projectProperties = new MSBuildPropertyGroup();
projectProperties.SetValue("RazorDefaultConfiguration", string.Empty);
// Act
var result = DefaultRazorProjectHost.TryGetDefaultConfiguration(projectProperties, out var defaultConfiguration);
// Assert
Assert.False(result);
Assert.Null(defaultConfiguration);
}
[Fact]
public void TryGetDefaultConfiguration_SucceedsWithValidConfiguration()
{
// Arrange
var expectedConfiguration = "Razor-13.37";
var projectProperties = new MSBuildPropertyGroup();
projectProperties.SetValue("RazorDefaultConfiguration", expectedConfiguration);
// Act
var result = DefaultRazorProjectHost.TryGetDefaultConfiguration(projectProperties, out var defaultConfiguration);
// Assert
Assert.True(result);
Assert.Equal(expectedConfiguration, defaultConfiguration);
}
[Fact]
public void TryGetLanguageVersion_FailsIfNoLanguageVersion()
{
// Arrange
var projectProperties = new MSBuildPropertyGroup();
// Act
var result = DefaultRazorProjectHost.TryGetLanguageVersion(projectProperties, out var languageVersion);
// Assert
Assert.False(result);
Assert.Null(languageVersion);
}
[Fact]
public void TryGetLanguageVersion_FailsIfEmptyLanguageVersion()
{
// Arrange
var projectProperties = new MSBuildPropertyGroup();
projectProperties.SetValue("RazorLangVersion", string.Empty);
// Act
var result = DefaultRazorProjectHost.TryGetLanguageVersion(projectProperties, out var languageVersion);
// Assert
Assert.False(result);
Assert.Null(languageVersion);
}
[Fact]
public void TryGetLanguageVersion_SucceedsWithValidLanguageVersion()
{
// Arrange
var projectProperties = new MSBuildPropertyGroup();
projectProperties.SetValue("RazorLangVersion", "1.0");
// Act
var result = DefaultRazorProjectHost.TryGetLanguageVersion(projectProperties, out var languageVersion);
// Assert
Assert.True(result);
Assert.Same(RazorLanguageVersion.Version_1_0, languageVersion);
}
[Fact]
public void TryGetLanguageVersion_SucceedsWithUnknownLanguageVersion_DefaultsToLatest()
{
// Arrange
var projectProperties = new MSBuildPropertyGroup();
projectProperties.SetValue("RazorLangVersion", "13.37");
// Act
var result = DefaultRazorProjectHost.TryGetLanguageVersion(projectProperties, out var languageVersion);
// Assert
Assert.True(result);
Assert.Same(RazorLanguageVersion.Latest, languageVersion);
}
[Fact]
public void TryGetConfigurationItem_FailsNoRazorConfigurationItems()
{
// Arrange
var projectItems = Enumerable.Empty<IMSBuildItemEvaluated>();
// Act
var result = DefaultRazorProjectHost.TryGetConfigurationItem("Razor-13.37", projectItems, out var configurationItem);
// Assert
Assert.False(result);
Assert.Null(configurationItem);
}
[Fact]
public void TryGetConfigurationItem_FailsNoMatchingRazorConfigurationItems()
{
// Arrange
var projectItems = new IMSBuildItemEvaluated[]
{
new TestMSBuildItem("RazorConfiguration")
{
Include = "Razor-10.0",
}
};
// Act
var result = DefaultRazorProjectHost.TryGetConfigurationItem("Razor-13.37", projectItems, out var configurationItem);
// Assert
Assert.False(result);
Assert.Null(configurationItem);
}
[Fact]
public void TryGetConfigurationItem_SucceedsForMatchingConfigurationItem()
{
// Arrange
var expectedConfiguration = "Razor-13.37";
var expectedConfigurationItem = new TestMSBuildItem("RazorConfiguration")
{
Include = expectedConfiguration,
};
var projectItems = new IMSBuildItemEvaluated[]
{
new TestMSBuildItem("RazorConfiguration")
{
Include = "Razor-10.0-DoesNotMatch",
},
expectedConfigurationItem
};
// Act
var result = DefaultRazorProjectHost.TryGetConfigurationItem(expectedConfiguration, projectItems, out var configurationItem);
// Assert
Assert.True(result);
Assert.Same(expectedConfigurationItem, configurationItem);
}
[Fact]
public void TryGetConfiguredExtensionNames_FailsIfNoExtensions()
{
// Arrange
var configurationItem = new TestMSBuildItem("RazorConfiguration");
// Act
var result = DefaultRazorProjectHost.TryGetConfiguredExtensionNames(configurationItem, out var configuredExtensionnames);
// Assert
Assert.False(result);
Assert.Null(configuredExtensionnames);
}
[Fact]
public void TryGetConfiguredExtensionNames_FailsIfEmptyExtensions()
{
// Arrange
var configurationItem = new TestMSBuildItem("RazorConfiguration");
configurationItem.TestMetadata.SetValue("Extensions", string.Empty);
// Act
var result = DefaultRazorProjectHost.TryGetConfiguredExtensionNames(configurationItem, out var configuredExtensionNames);
// Assert
Assert.False(result);
Assert.Null(configuredExtensionNames);
}
[Fact]
public void TryGetConfiguredExtensionNames_SucceedsIfSingleExtension()
{
// Arrange
var expectedExtensionName = "SomeExtensionName";
var configurationItem = new TestMSBuildItem("RazorConfiguration");
configurationItem.TestMetadata.SetValue("Extensions", expectedExtensionName);
// Act
var result = DefaultRazorProjectHost.TryGetConfiguredExtensionNames(configurationItem, out var configuredExtensionNames);
// Assert
Assert.True(result);
var extensionName = Assert.Single(configuredExtensionNames);
Assert.Equal(expectedExtensionName, extensionName);
}
[Fact]
public void TryGetConfiguredExtensionNames_SucceedsIfMultipleExtensions()
{
// Arrange
var configurationItem = new TestMSBuildItem("RazorConfiguration");
configurationItem.TestMetadata.SetValue("Extensions", "SomeExtensionName;SomeOtherExtensionName");
// Act
var result = DefaultRazorProjectHost.TryGetConfiguredExtensionNames(configurationItem, out var configuredExtensionNames);
// Assert
Assert.True(result);
Assert.Collection(
configuredExtensionNames,
name => Assert.Equal("SomeExtensionName", name),
name => Assert.Equal("SomeOtherExtensionName", name));
}
[Fact]
public void GetExtensions_NoExtensionTypes_ReturnsEmptyArray()
{
// Arrange
var projectItems = new IMSBuildItemEvaluated[]
{
new TestMSBuildItem("NotAnExtension")
{
Include = "Extension1",
},
};
// Act
var extensions = DefaultRazorProjectHost.GetExtensions(new[] { "Extension1", "Extension2" }, projectItems);
// Assert
Assert.Empty(extensions);
}
[Fact]
public void GetExtensions_UnConfiguredExtensionTypes_ReturnsEmptyArray()
{
// Arrange
var projectItems = new IMSBuildItemEvaluated[]
{
new TestMSBuildItem("RazorExtension")
{
Include = "UnconfiguredExtensionName",
},
};
// Act
var extensions = DefaultRazorProjectHost.GetExtensions(new[] { "Extension1", "Extension2" }, projectItems);
// Assert
Assert.Empty(extensions);
}
[Fact]
public void GetExtensions_SomeConfiguredExtensions_ReturnsConfiguredExtensions()
{
// Arrange
var expectedExtension1Name = "Extension1";
var expectedExtension2Name = "Extension2";
var projectItems = new IMSBuildItemEvaluated[]
{
new TestMSBuildItem("RazorExtension")
{
Include = "UnconfiguredExtensionName",
},
new TestMSBuildItem("RazorExtension")
{
Include = expectedExtension1Name,
},
new TestMSBuildItem("RazorExtension")
{
Include = expectedExtension2Name,
},
};
// Act
var extensions = DefaultRazorProjectHost.GetExtensions(new[] { expectedExtension1Name, expectedExtension2Name }, projectItems);
// Assert
Assert.Collection(
extensions,
extension => Assert.Equal(expectedExtension1Name, extension.ExtensionName),
extension => Assert.Equal(expectedExtension2Name, extension.ExtensionName));
}
[Fact]
public void TryGetConfiguration_FailsIfNoDefaultConfiguration()
{
// Arrange
var projectProperties = new MSBuildPropertyGroup();
var projectItems = new IMSBuildItemEvaluated[0];
// Act
var result = DefaultRazorProjectHost.TryGetConfiguration(projectProperties, projectItems, out var configuration);
// Assert
Assert.False(result);
Assert.Null(configuration);
}
[Fact]
public void TryGetConfiguration_FailsIfNoLanguageVersion()
{
// Arrange
var projectProperties = new MSBuildPropertyGroup();
projectProperties.SetValue("RazorDefaultConfiguration", "Razor-13.37");
var projectItems = new IMSBuildItemEvaluated[0];
// Act
var result = DefaultRazorProjectHost.TryGetConfiguration(projectProperties, projectItems, out var configuration);
// Assert
Assert.False(result);
Assert.Null(configuration);
}
[Fact]
public void TryGetConfiguration_FailsIfNoConfigurationItems()
{
// Arrange
var projectProperties = new MSBuildPropertyGroup();
projectProperties.SetValue("RazorDefaultConfiguration", "Razor-13.37");
projectProperties.SetValue("RazorLangVersion", "1.0");
var projectItems = new IMSBuildItemEvaluated[0];
// Act
var result = DefaultRazorProjectHost.TryGetConfiguration(projectProperties, projectItems, out var configuration);
// Assert
Assert.False(result);
Assert.Null(configuration);
}
[Fact]
public void TryGetConfiguration_FailsIfNoConfiguredExtensionNames()
{
// Arrange
var projectProperties = new MSBuildPropertyGroup();
projectProperties.SetValue("RazorDefaultConfiguration", "Razor-13.37");
projectProperties.SetValue("RazorLangVersion", "1.0");
var projectItems = new IMSBuildItemEvaluated[]
{
new TestMSBuildItem("RazorConfiguration")
{
Include = "Razor-13.37",
},
};
// Act
var result = DefaultRazorProjectHost.TryGetConfiguration(projectProperties, projectItems, out var configuration);
// Assert
Assert.False(result);
Assert.Null(configuration);
}
// This is more of an integration test but is here to test the overall flow/functionality
[Fact]
public void TryGetConfiguration_SucceedsWithAllPreRequisites()
{
// Arrange
var expectedLanguageVersion = RazorLanguageVersion.Version_1_0;
var expectedConfigurationName = "Razor-Test";
var expectedExtension1Name = "Extension1";
var expectedExtension2Name = "Extension2";
var expectedRazorConfigurationItem = new TestMSBuildItem("RazorConfiguration")
{
Include = expectedConfigurationName,
};
expectedRazorConfigurationItem.TestMetadata.SetValue("Extensions", "Extension1;Extension2");
var projectItems = new IMSBuildItemEvaluated[]
{
new TestMSBuildItem("RazorConfiguration")
{
Include = "UnconfiguredRazorConfiguration",
},
new TestMSBuildItem("RazorExtension")
{
Include = "UnconfiguredExtensionName",
},
new TestMSBuildItem("RazorExtension")
{
Include = expectedExtension1Name,
},
new TestMSBuildItem("RazorExtension")
{
Include = expectedExtension2Name,
},
expectedRazorConfigurationItem,
};
var projectProperties = new MSBuildPropertyGroup();
projectProperties.SetValue("RazorDefaultConfiguration", expectedConfigurationName);
projectProperties.SetValue("RazorLangVersion", "1.0");
// Act
var result = DefaultRazorProjectHost.TryGetConfiguration(projectProperties, projectItems, out var configuration);
// Assert
Assert.True(result);
Assert.Equal(expectedLanguageVersion, configuration.LanguageVersion);
Assert.Equal(expectedConfigurationName, configuration.ConfigurationName);
Assert.Collection(
configuration.Extensions,
extension => Assert.Equal(expectedExtension1Name, extension.ExtensionName),
extension => Assert.Equal(expectedExtension2Name, extension.ExtensionName));
}
private class TestMSBuildItem : IMSBuildItemEvaluated
{
private readonly MSBuildPropertyGroup _metadata;
private readonly string _name;
private string _include;
public TestMSBuildItem(string name)
{
_name = name;
_metadata = new MSBuildPropertyGroup();
}
public string Name => _name;
public string Include
{
get => _include;
set => _include = value;
}
public MSBuildPropertyGroup TestMetadata => _metadata;
public IMSBuildPropertyGroupEvaluated Metadata => _metadata;
public string Condition => throw new System.NotImplementedException();
public bool IsImported => throw new System.NotImplementedException();
public string UnevaluatedInclude => throw new System.NotImplementedException();
public MSBuildItem SourceItem => throw new System.NotImplementedException();
public IEnumerable<MSBuildItem> SourceItems => throw new System.NotImplementedException();
}
}
}

View File

@ -8,13 +8,13 @@ using Xunit;
namespace Microsoft.VisualStudio.Mac.LanguageServices.Razor
{
public class DefaultVisualStudioWorkspaceAccessorTest
public class DefaultVisualStudioMacWorkspaceAccessorTest
{
[Fact]
public void TryGetWorkspace_NoHostProject_ReturnsFalse()
{
// Arrange
var workspaceAccessor = new DefaultVisualStudioWorkspaceAccessor(Mock.Of<TextBufferProjectService>());
var workspaceAccessor = new DefaultVisualStudioMacWorkspaceAccessor(Mock.Of<TextBufferProjectService>());
var textBuffer = Mock.Of<ITextBuffer>();
// Act

View File

@ -0,0 +1,62 @@
// 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 MonoDevelop.Core;
using MonoDevelop.Projects;
using Xunit;
namespace Microsoft.VisualStudio.Mac.LanguageServices.Razor.ProjectSystem
{
public class FallbackRazorProjectHostTest
{
[Theory]
[InlineData(null)]
[InlineData("")]
public void IsMvcAssembly_FailsIfNullOrEmptyFilePath(string filePath)
{
// Arrange
var assemblyFilePath = new FilePath(filePath);
var assemblyReference = new AssemblyReference(assemblyFilePath);
// Act
var result = FallbackRazorProjectHost.IsMvcAssembly(assemblyReference);
// Assert
Assert.False(result);
}
[Fact]
public void IsMvcAssembly_FailsIfNotMvc()
{
// Arrange
var assemblyFilePath = new FilePath("C:/Path/To/Assembly.dll");
var assemblyReference = new AssemblyReference(assemblyFilePath);
// Act
var result = FallbackRazorProjectHost.IsMvcAssembly(assemblyReference);
// Assert
Assert.False(result);
}
[Fact]
public void IsMvcAssembly_SucceedsIfMvc()
{
// Arrange
var assemblyFilePath = new FilePath("C:/Path/To/Microsoft.AspNetCore.Mvc.Razor.dll");
var assemblyReference = new AssemblyReference(assemblyFilePath);
// Act
var result = FallbackRazorProjectHost.IsMvcAssembly(assemblyReference);
// Assert
Assert.True(result);
}
// -------------------------------------------------------------------------------------------
// Purposefully do not have any more tests here because that would involve mocking MonoDevelop
// types. The default constructors for the Solution / DotNetProject MonoDevelop types change
// static classes (they assume they're being created in an IDE).
// -------------------------------------------------------------------------------------------
}
}

View File

@ -4,6 +4,10 @@
<TargetFramework>net461</TargetFramework>
</PropertyGroup>
<ItemGroup>
<None Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.VisualStudio.Mac.LanguageServices.Razor\Microsoft.VisualStudio.Mac.LanguageServices.Razor.csproj" />
<ProjectReference Include="..\Microsoft.VisualStudio.Editor.Razor.Test.Common\Microsoft.VisualStudio.Editor.Razor.Test.Common.csproj" />

View File

@ -0,0 +1,4 @@
{
"methodDisplay": "method",
"shadowCopy": false
}

View File

@ -42,15 +42,6 @@
</ItemGroup>
<ItemGroup>
<!--
The extension project can not have a direct reference to its language service pieces. They are included via the manifest above.
This piece ensures that transitive builds/restores occurr for this project.
-->
<ProjectReference Include="..\..\src\Microsoft.VisualStudio.Mac.LanguageServices.Razor\Microsoft.VisualStudio.Mac.LanguageServices.Razor.csproj">
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
<PrivateAssets>true</PrivateAssets>
<OutputItemType>Content</OutputItemType>
<Targets>Build</Targets>
</ProjectReference>
<ProjectReference Include="..\..\src\Microsoft.VisualStudio.Mac.LanguageServices.Razor\Microsoft.VisualStudio.Mac.LanguageServices.Razor.csproj" />
</ItemGroup>
</Project>

View File

@ -19,5 +19,10 @@
<Assembly file="Microsoft.VisualStudio.Editor.Razor.dll" />
<Assembly file="Microsoft.CodeAnalysis.Razor.Workspaces.dll" />
<Assembly file="Microsoft.VisualStudio.Mac.LanguageServices.Razor.dll" />
</Extension>
</Extension>
<!-- Project Extensions -->
<Extension path = "/MonoDevelop/ProjectModel/ProjectModelExtensions">
<Class class="Microsoft.VisualStudio.Mac.RazorAddin.RazorProjectExtension" insertafter="FinalStep" />
</Extension>
</ExtensionModel>

View File

@ -0,0 +1,59 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.CodeAnalysis.Razor;
using Microsoft.VisualStudio.Mac.LanguageServices.Razor.ProjectSystem;
using MonoDevelop.Ide.Composition;
using MonoDevelop.Ide.TypeSystem;
using MonoDevelop.Projects;
namespace Microsoft.VisualStudio.Mac.RazorAddin
{
internal class RazorProjectExtension : ProjectExtension
{
private readonly object _lock = new object();
private readonly ForegroundDispatcher _foregroundDispatcher;
public RazorProjectExtension()
{
_foregroundDispatcher = CompositionManager.GetExportedValue<ForegroundDispatcher>();
}
protected override void OnBoundToSolution()
{
if (!(Project is DotNetProject dotNetProject))
{
return;
}
DotNetProjectHost projectHost;
lock (_lock)
{
if (Project.ExtendedProperties.Contains(typeof(DotNetProjectHost)))
{
// Already have a project host.
return;
}
var projectHostFactory = CompositionManager.GetExportedValue<DotNetProjectHostFactory>();
projectHost = projectHostFactory.Create(dotNetProject);
Project.ExtendedProperties[typeof(DotNetProjectHost)] = projectHost;
}
// Once a workspace is created for the solution we'll setup our project host for the current project. The Razor world
// shares a lifetime with the workspace (as Roslyn services) so we need to ensure it exists prior to wiring the host
// world to the Roslyn world.
TypeSystemService.GetWorkspaceAsync(Project.ParentSolution).ContinueWith(task =>
{
if (task.IsFaulted || task.IsCanceled)
{
// We only want to act if we could properly retrieve the workspace.
return;
}
projectHost.Subscribe();
},
_foregroundDispatcher.ForegroundScheduler);
}
}
}