diff --git a/benchmarks/Microsoft.AspNetCore.Razor.Performance/Microsoft.AspNetCore.Razor.Performance.csproj b/benchmarks/Microsoft.AspNetCore.Razor.Performance/Microsoft.AspNetCore.Razor.Performance.csproj
index e63049a74f..790364d92e 100644
--- a/benchmarks/Microsoft.AspNetCore.Razor.Performance/Microsoft.AspNetCore.Razor.Performance.csproj
+++ b/benchmarks/Microsoft.AspNetCore.Razor.Performance/Microsoft.AspNetCore.Razor.Performance.csproj
@@ -10,14 +10,12 @@
+
-
- Shared\RazorDiagnosticJsonConverter.cs
-
-
- Shared\TagHelperDescriptorJsonConverter.cs
+
+ Serialization\%(FileName)%(Extension)
diff --git a/benchmarks/Microsoft.AspNetCore.Razor.Performance/TagHelperSerializationBenchmark.cs b/benchmarks/Microsoft.AspNetCore.Razor.Performance/TagHelperSerializationBenchmark.cs
index bfee04164f..f1dcabe640 100644
--- a/benchmarks/Microsoft.AspNetCore.Razor.Performance/TagHelperSerializationBenchmark.cs
+++ b/benchmarks/Microsoft.AspNetCore.Razor.Performance/TagHelperSerializationBenchmark.cs
@@ -7,7 +7,7 @@ using System.IO;
using System.Text;
using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Razor.Language;
-using Microsoft.VisualStudio.LanguageServices.Razor;
+using Microsoft.VisualStudio.LanguageServices.Razor.Serialization;
using Newtonsoft.Json;
namespace Microsoft.AspNetCore.Razor.Performance
diff --git a/build/MPack.targets b/build/MPack.targets
index fe4fff7f16..54ace6edf4 100644
--- a/build/MPack.targets
+++ b/build/MPack.targets
@@ -70,7 +70,10 @@
-
+
+
+
+
diff --git a/build/dependencies.props b/build/dependencies.props
index a21fbae048..bb46a4acdc 100644
--- a/build/dependencies.props
+++ b/build/dependencies.props
@@ -52,6 +52,7 @@
2.7.0-beta3-62512-06
2.7.0-beta3-62512-06
2.7.0-beta3-62512-06
+ 2.7.0-beta3-62512-06
2.7.0-beta3-62512-06
2.7.0-beta3-62512-06
0.8.0
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/ExtensionInitializer.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/ExtensionInitializer.cs
new file mode 100644
index 0000000000..51f16b552f
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/ExtensionInitializer.cs
@@ -0,0 +1,29 @@
+// 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.AspNetCore.Mvc.Razor.Extensions.Version1_X
+{
+ internal class ExtensionInitializer : RazorExtensionInitializer
+ {
+ public override void Initialize(RazorProjectEngineBuilder builder)
+ {
+ if (builder == null)
+ {
+ throw new ArgumentNullException(nameof(builder));
+ }
+
+ if (builder.Configuration.ConfigurationName == "MVC-1.0")
+ {
+ RazorExtensions.Register(builder);
+ }
+ else if (builder.Configuration.ConfigurationName == "MVC-1.1")
+ {
+ RazorExtensions.Register(builder);
+ RazorExtensions.RegisterViewComponentTagHelpers(builder);
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/Properties/AssemblyInfo.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/Properties/AssemblyInfo.cs
index 8bed236c69..8233c62607 100644
--- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/Properties/AssemblyInfo.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/Properties/AssemblyInfo.cs
@@ -2,5 +2,10 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Runtime.CompilerServices;
+using Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X;
+using Microsoft.AspNetCore.Razor.Language;
+
+[assembly: ProvideRazorExtensionInitializer("MVC-1.0", typeof(ExtensionInitializer))]
+[assembly: ProvideRazorExtensionInitializer("MVC-1.1", typeof(ExtensionInitializer))]
[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/RazorExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/RazorExtensions.cs
index e8b4f0018c..18bfe99a61 100644
--- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/RazorExtensions.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/RazorExtensions.cs
@@ -45,6 +45,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
throw new ArgumentNullException(nameof(builder));
}
+ builder.Features.Add(new ViewComponentTagHelperDescriptorProvider());
+
builder.Features.Add(new ViewComponentTagHelperPass());
builder.AddTargetExtension(new ViewComponentTagHelperTargetExtension());
}
@@ -88,6 +90,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
EnsureDesignTime(builder);
+ builder.Features.Add(new ViewComponentTagHelperDescriptorProvider());
builder.Features.Add(new ViewComponentTagHelperPass());
builder.AddTargetExtension(new ViewComponentTagHelperTargetExtension());
}
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/ViewComponentTypeVisitor.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/ViewComponentTypeVisitor.cs
index fe70827712..90cb1eedca 100644
--- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/ViewComponentTypeVisitor.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X/ViewComponentTypeVisitor.cs
@@ -24,10 +24,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
var symbol = compilation.GetAssemblyOrModuleSymbol(reference) as IAssemblySymbol;
if (symbol != null)
{
- if (string.Equals(symbol.Identity.Name, ViewComponentTypes.Assembly, StringComparison.Ordinal) &&
- symbol.Identity.Version > ViewComponentTypes.AssemblyVersion)
+ if (string.Equals(symbol.Identity.Name, ViewComponentTypes.Assembly, StringComparison.Ordinal))
{
- enabled = true;
+ enabled = symbol.Identity.Version >= ViewComponentTypes.AssemblyVersion;
break;
}
}
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorExtensions.cs
index abf1b447f0..f1ca7455cc 100644
--- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorExtensions.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/RazorExtensions.cs
@@ -61,6 +61,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
InheritsDirective.Register(builder);
SectionDirective.Register(builder);
+ builder.Features.Add(new ViewComponentTagHelperDescriptorProvider());
+
builder.AddTargetExtension(new ViewComponentTagHelperTargetExtension());
builder.AddTargetExtension(new TemplateTargetExtension()
{
diff --git a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ViewComponentTypeVisitor.cs b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ViewComponentTypeVisitor.cs
index c26779a9c5..9b44634095 100644
--- a/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ViewComponentTypeVisitor.cs
+++ b/src/Microsoft.AspNetCore.Mvc.Razor.Extensions/ViewComponentTypeVisitor.cs
@@ -10,31 +10,14 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
{
internal class ViewComponentTypeVisitor : SymbolVisitor
{
- private static readonly Version SupportedVCTHMvcVersion = new Version(1, 1);
-
private readonly INamedTypeSymbol _viewComponentAttribute;
private readonly INamedTypeSymbol _nonViewComponentAttribute;
private readonly List _results;
public static ViewComponentTypeVisitor Create(Compilation compilation, List results)
{
- var enabled = false;
- foreach (var reference in compilation.References)
- {
- var symbol = compilation.GetAssemblyOrModuleSymbol(reference) as IAssemblySymbol;
- if (symbol != null)
- {
- if (string.Equals(symbol.Identity.Name, ViewComponentTypes.Assembly, StringComparison.Ordinal) &&
- symbol.Identity.Version > ViewComponentTypes.AssemblyVersion)
- {
- enabled = true;
- break;
- }
- }
- }
-
- var vcAttribute = enabled ? compilation.GetTypeByMetadataName(ViewComponentTypes.ViewComponentAttribute) : null;
- var nonVCAttribute = enabled ? compilation.GetTypeByMetadataName(ViewComponentTypes.NonViewComponentAttribute) : null;
+ var vcAttribute = compilation.GetTypeByMetadataName(ViewComponentTypes.ViewComponentAttribute);
+ var nonVCAttribute = compilation.GetTypeByMetadataName(ViewComponentTypes.NonViewComponentAttribute);
return new ViewComponentTypeVisitor(vcAttribute, nonVCAttribute, results);
}
diff --git a/src/Microsoft.AspNetCore.Razor.Language/RazorConfiguration.cs b/src/Microsoft.AspNetCore.Razor.Language/RazorConfiguration.cs
index f68db61f22..e8ef287c0c 100644
--- a/src/Microsoft.AspNetCore.Razor.Language/RazorConfiguration.cs
+++ b/src/Microsoft.AspNetCore.Razor.Language/RazorConfiguration.cs
@@ -7,15 +7,15 @@ using System.Linq;
namespace Microsoft.AspNetCore.Razor.Language
{
- public sealed class RazorConfiguration
+ public abstract class RazorConfiguration
{
- public static readonly RazorConfiguration Default = new RazorConfiguration(
+ public static readonly RazorConfiguration Default = new DefaultRazorConfiguration(
RazorLanguageVersion.Latest,
"unnamed",
Array.Empty());
- public RazorConfiguration(
- RazorLanguageVersion languageVersion,
+ public static RazorConfiguration Create(
+ RazorLanguageVersion languageVersion,
string configurationName,
IEnumerable extensions)
{
@@ -34,15 +34,32 @@ namespace Microsoft.AspNetCore.Razor.Language
throw new ArgumentNullException(nameof(extensions));
}
- LanguageVersion = languageVersion;
- ConfigurationName = configurationName;
- Extensions = extensions.ToArray();
+ return new DefaultRazorConfiguration(languageVersion, configurationName, extensions.ToArray());
}
- public string ConfigurationName { get; }
+ public abstract string ConfigurationName { get; }
- public IReadOnlyList Extensions { get; }
+ public abstract IReadOnlyList Extensions { get; }
- public RazorLanguageVersion LanguageVersion { get; }
+ public abstract RazorLanguageVersion LanguageVersion { get; }
+
+ private class DefaultRazorConfiguration : RazorConfiguration
+ {
+ public DefaultRazorConfiguration(
+ RazorLanguageVersion languageVersion,
+ string configurationName,
+ RazorExtension[] extensions)
+ {
+ LanguageVersion = languageVersion;
+ ConfigurationName = configurationName;
+ Extensions = extensions;
+ }
+
+ public override string ConfigurationName { get; }
+
+ public override IReadOnlyList Extensions { get; }
+
+ public override RazorLanguageVersion LanguageVersion { get; }
+ }
}
}
diff --git a/src/Microsoft.AspNetCore.Razor.Tools/DiscoverCommand.cs b/src/Microsoft.AspNetCore.Razor.Tools/DiscoverCommand.cs
index c846ef3692..5ff3f07d52 100644
--- a/src/Microsoft.AspNetCore.Razor.Tools/DiscoverCommand.cs
+++ b/src/Microsoft.AspNetCore.Razor.Tools/DiscoverCommand.cs
@@ -12,7 +12,7 @@ using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Razor;
using Microsoft.Extensions.CommandLineUtils;
-using Microsoft.VisualStudio.LanguageServices.Razor;
+using Microsoft.VisualStudio.LanguageServices.Razor.Serialization;
using Newtonsoft.Json;
namespace Microsoft.AspNetCore.Razor.Tools
@@ -115,7 +115,7 @@ namespace Microsoft.AspNetCore.Razor.Tools
}
var version = RazorLanguageVersion.Parse(Version.Value());
- var configuration = new RazorConfiguration(version, Configuration.Value(), extensions);
+ var configuration = RazorConfiguration.Create(version, Configuration.Value(), extensions);
var result = ExecuteCore(
configuration: configuration,
diff --git a/src/Microsoft.AspNetCore.Razor.Tools/GenerateCommand.cs b/src/Microsoft.AspNetCore.Razor.Tools/GenerateCommand.cs
index d19c566b76..b1db811079 100644
--- a/src/Microsoft.AspNetCore.Razor.Tools/GenerateCommand.cs
+++ b/src/Microsoft.AspNetCore.Razor.Tools/GenerateCommand.cs
@@ -8,7 +8,7 @@ using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.Extensions.CommandLineUtils;
-using Microsoft.VisualStudio.LanguageServices.Razor;
+using Microsoft.VisualStudio.LanguageServices.Razor.Serialization;
using Newtonsoft.Json;
namespace Microsoft.AspNetCore.Razor.Tools
@@ -58,7 +58,7 @@ namespace Microsoft.AspNetCore.Razor.Tools
}
var version = RazorLanguageVersion.Parse(Version.Value());
- var configuration = new RazorConfiguration(version, Configuration.Value(), extensions);
+ var configuration = RazorConfiguration.Create(version, Configuration.Value(), extensions);
var result = ExecuteCore(
configuration: configuration,
@@ -151,7 +151,7 @@ namespace Microsoft.AspNetCore.Razor.Tools
GetVirtualRazorProjectSystem(inputItems),
RazorProjectFileSystem.Create(projectDirectory),
});
-
+
var engine = RazorProjectEngine.Create(configuration, compositeFileSystem, b =>
{
b.Features.Add(new StaticTagHelperFeature() { TagHelpers = tagHelpers, });
diff --git a/src/Microsoft.AspNetCore.Razor.Tools/Microsoft.AspNetCore.Razor.Tools.csproj b/src/Microsoft.AspNetCore.Razor.Tools/Microsoft.AspNetCore.Razor.Tools.csproj
index 6aae7e8d50..e67f36e02f 100644
--- a/src/Microsoft.AspNetCore.Razor.Tools/Microsoft.AspNetCore.Razor.Tools.csproj
+++ b/src/Microsoft.AspNetCore.Razor.Tools/Microsoft.AspNetCore.Razor.Tools.csproj
@@ -13,12 +13,12 @@
-
- Shared\RazorDiagnosticJsonConverter.cs
-
-
+
Shared\TagHelperDescriptorJsonConverter.cs
+
+ Shared\RazorDiagnosticJsonConverter.cs
+
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultErrorReporter.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultErrorReporter.cs
index 664434a674..ff200966e8 100644
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultErrorReporter.cs
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/DefaultErrorReporter.cs
@@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
+using Microsoft.CodeAnalysis.Razor.ProjectSystem;
namespace Microsoft.CodeAnalysis.Razor
{
@@ -9,11 +10,31 @@ namespace Microsoft.CodeAnalysis.Razor
{
public override void ReportError(Exception exception)
{
+ if (exception == null)
+ {
+ throw new ArgumentNullException(nameof(exception));
+ }
+
// Do nothing.
}
- public override void ReportError(Exception exception, Project project)
+ public override void ReportError(Exception exception, ProjectSnapshot project)
{
+ if (exception == null)
+ {
+ throw new ArgumentNullException(nameof(exception));
+ }
+
+ // Do nothing.
+ }
+
+ public override void ReportError(Exception exception, Project workspaceProject)
+ {
+ if (exception == null)
+ {
+ throw new ArgumentNullException(nameof(exception));
+ }
+
// Do nothing.
}
}
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ErrorReporter.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ErrorReporter.cs
index 03bc44f61c..4f0b0dab81 100644
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ErrorReporter.cs
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ErrorReporter.cs
@@ -3,13 +3,16 @@
using System;
using Microsoft.CodeAnalysis.Host;
+using Microsoft.CodeAnalysis.Razor.ProjectSystem;
namespace Microsoft.CodeAnalysis.Razor
{
internal abstract class ErrorReporter : IWorkspaceService
{
public abstract void ReportError(Exception exception);
+
+ public abstract void ReportError(Exception exception, ProjectSnapshot project);
- public abstract void ReportError(Exception exception, Project project);
+ public abstract void ReportError(Exception exception, Project workspaceProject);
}
}
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/FallbackProjectEngineFactory.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/FallbackProjectEngineFactory.cs
new file mode 100644
index 0000000000..855acedde2
--- /dev/null
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/FallbackProjectEngineFactory.cs
@@ -0,0 +1,37 @@
+// 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.AspNetCore.Razor.Language;
+
+namespace Microsoft.CodeAnalysis.Razor
+{
+ [Export(typeof(IFallbackProjectEngineFactory))]
+ internal class FallbackProjectEngineFactory : IFallbackProjectEngineFactory
+ {
+ public RazorProjectEngine Create(RazorConfiguration configuration, RazorProjectFileSystem fileSystem, Action configure)
+ {
+ if (configuration == null)
+ {
+ throw new ArgumentNullException(nameof(configuration));
+ }
+
+ if (fileSystem == null)
+ {
+ throw new ArgumentNullException(nameof(fileSystem));
+ }
+
+ // This is a very basic implementation that will provide reasonable support without crashing.
+ // If the user falls into this situation, ideally they can realize that something is wrong and take
+ // action.
+ //
+ // This has no support for:
+ // - Tag Helpers
+ // - Imports
+ // - Default Imports
+ // - and will have a very limited set of directives
+ return RazorProjectEngine.Create(configuration, fileSystem, configure);
+ }
+ }
+}
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/IFallbackProjectEngineFactory.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/IFallbackProjectEngineFactory.cs
new file mode 100644
index 0000000000..ccdc1b80c1
--- /dev/null
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/IFallbackProjectEngineFactory.cs
@@ -0,0 +1,10 @@
+// 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
+{
+ // Used to create the 'fallback' project engine when we don't have a custom implementation.
+ internal interface IFallbackProjectEngineFactory : IProjectEngineFactory
+ {
+ }
+}
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectExtensibilityConfigurationFactory.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectExtensibilityConfigurationFactory.cs
deleted file mode 100644
index d1c21ff88f..0000000000
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectExtensibilityConfigurationFactory.cs
+++ /dev/null
@@ -1,142 +0,0 @@
-// Copyright (c) .NET Foundation. All rights reserved.
-// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-
-using System;
-using System.Collections.Generic;
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.AspNetCore.Razor.Language;
-
-namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
-{
- // This is hardcoded for now. A more complete design would fan out to a list of providers.
- internal class DefaultProjectExtensibilityConfigurationFactory : ProjectExtensibilityConfigurationFactory
- {
- private const string MvcAssemblyName = "Microsoft.AspNetCore.Mvc.Razor";
- private const string RazorV1AssemblyName = "Microsoft.AspNetCore.Razor";
- private const string RazorV2AssemblyName = "Microsoft.AspNetCore.Razor.Language";
-
- // Using MaxValue here so that we ignore patch and build numbers. We only want to compare major/minor.
- private static readonly Version MaxSupportedRazorVersion = new Version(2, 0, Int32.MaxValue, Int32.MaxValue);
- private static readonly Version MaxSupportedMvcVersion = new Version(2, 0, Int32.MaxValue, Int32.MaxValue);
-
- private static readonly Version DefaultRazorVersion = new Version(2, 0, 0, 0);
- private static readonly Version DefaultMvcVersion = new Version(2, 0, 0, 0);
-
- public async override Task GetConfigurationAsync(Project project, CancellationToken cancellationToken = default(CancellationToken))
- {
- if (project == null)
- {
- throw new ArgumentNullException(nameof(project));
- }
-
- var compilation = await project.GetCompilationAsync(cancellationToken);
- return GetConfiguration(compilation.ReferencedAssemblyNames);
- }
-
- // internal/separate for testing.
- internal ProjectExtensibilityConfiguration GetConfiguration(IEnumerable references)
- {
- // Avoiding ToDictionary here because we don't want a crash if there is a duplicate name.
- var assemblies = new Dictionary();
- foreach (var assembly in references)
- {
- assemblies[assembly.Name] = assembly;
- }
-
- // First we look for the V2+ Razor Assembly. If we find this then its version is the correct Razor version.
- AssemblyIdentity razorAssembly;
- if (assemblies.TryGetValue(RazorV2AssemblyName, out razorAssembly))
- {
- if (razorAssembly.Version == null || razorAssembly.Version > MaxSupportedRazorVersion)
- {
- // This is a newer Razor version than we know, treat it as a fallback case.
- razorAssembly = null;
- }
- }
- else if (assemblies.TryGetValue(RazorV1AssemblyName, out razorAssembly))
- {
- // This assembly only counts as the 'Razor' assembly if it's a version lower than 2.0.0.
- if (razorAssembly.Version == null || razorAssembly.Version >= new Version(2, 0, 0, 0))
- {
- razorAssembly = null;
- }
- }
-
- AssemblyIdentity mvcAssembly;
- if (assemblies.TryGetValue(MvcAssemblyName, out mvcAssembly))
- {
- if (mvcAssembly.Version == null || mvcAssembly.Version > MaxSupportedMvcVersion)
- {
- // This is a newer MVC version than we know, treat it as a fallback case.
- mvcAssembly = null;
- }
- }
-
- RazorLanguageVersion languageVersion = null;
- if (razorAssembly != null && mvcAssembly != null)
- {
- languageVersion = GetLanguageVersion(razorAssembly);
-
- // This means we've definitely found a supported Razor version and an MVC version.
- return new MvcExtensibilityConfiguration(
- languageVersion,
- ProjectExtensibilityConfigurationKind.ApproximateMatch,
- new ProjectExtensibilityAssembly(razorAssembly),
- new ProjectExtensibilityAssembly(mvcAssembly));
- }
-
- // If we get here it means we didn't find everything, so we have to guess.
- if (razorAssembly == null || razorAssembly.Version == null)
- {
- razorAssembly = new AssemblyIdentity(RazorV2AssemblyName, DefaultRazorVersion);
- }
-
- if (mvcAssembly == null || mvcAssembly.Version == null)
- {
- mvcAssembly = new AssemblyIdentity(MvcAssemblyName, DefaultMvcVersion);
- }
-
- if (languageVersion == null)
- {
- languageVersion = GetLanguageVersion(razorAssembly);
- }
-
- return new MvcExtensibilityConfiguration(
- languageVersion,
- ProjectExtensibilityConfigurationKind.Fallback,
- new ProjectExtensibilityAssembly(razorAssembly),
- new ProjectExtensibilityAssembly(mvcAssembly));
- }
-
- // Internal for testing
- internal static RazorLanguageVersion GetLanguageVersion(AssemblyIdentity razorAssembly)
- {
- // This is inferred from the assembly for now, the Razor language version will eventually flow from MSBuild.
-
- var razorAssemblyVersion = razorAssembly.Version;
- if (razorAssemblyVersion.Major == 1)
- {
- if (razorAssemblyVersion.Minor >= 1)
- {
- return RazorLanguageVersion.Version_1_1;
- }
-
- return RazorLanguageVersion.Version_1_0;
- }
-
- if (razorAssemblyVersion.Major == 2)
- {
- if (razorAssemblyVersion.Minor >= 1)
- {
- return RazorLanguageVersion.Version_2_1;
- }
-
- return RazorLanguageVersion.Version_2_0;
- }
-
- // Couldn't determine version based off of assembly, fallback to latest.
- return RazorLanguageVersion.Latest;
- }
- }
-}
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectExtensibilityConfigurationFactoryFactory.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectExtensibilityConfigurationFactoryFactory.cs
deleted file mode 100644
index 93d7400ee3..0000000000
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectExtensibilityConfigurationFactoryFactory.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (c) .NET Foundation. All rights reserved.
-// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-
-using System;
-using System.Composition;
-using Microsoft.CodeAnalysis.Host;
-using Microsoft.CodeAnalysis.Host.Mef;
-
-namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
-{
- [Shared]
- [ExportLanguageServiceFactory(typeof(ProjectExtensibilityConfigurationFactory), RazorLanguage.Name)]
- internal class DefaultProjectExtensibilityConfigurationFactoryFactory : ILanguageServiceFactory
- {
- public ILanguageService CreateLanguageService(HostLanguageServices languageServices)
- {
- if (languageServices == null)
- {
- throw new ArgumentNullException(nameof(languageServices));
- }
-
- return new DefaultProjectExtensibilityConfigurationFactory();
- }
- }
-}
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshot.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshot.cs
index 4edd7817c1..b8646429c6 100644
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshot.cs
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshot.cs
@@ -18,33 +18,62 @@ 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;
ComputedVersion = other.ComputedVersion;
- Configuration = other.Configuration;
+
+ FilePath = other.FilePath;
TagHelpers = other.TagHelpers;
+ HostProject = hostProject;
+ 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));
+ }
+
+ ComputedVersion = other.ComputedVersion;
+
+ FilePath = other.FilePath;
+ TagHelpers = other.TagHelpers;
+ HostProject = other.HostProject;
+ WorkspaceProject = workspaceProject;
+
+ Version = other.Version.GetNewerVersion();
}
private DefaultProjectSnapshot(ProjectSnapshotUpdateContext update, DefaultProjectSnapshot other)
@@ -59,16 +88,28 @@ 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;
TagHelpers = update.TagHelpers ?? Array.Empty();
+ 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 override HostProject HostProject { get; }
+
+ public override bool IsInitialized => WorkspaceProject != null;
+
+ public override VersionStamp Version { get; }
+
+ public override Project WorkspaceProject { get; }
public override IReadOnlyList TagHelpers { get; } = Array.Empty();
@@ -77,19 +118,40 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// 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)
{
@@ -99,7 +161,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
return new DefaultProjectSnapshot(update, this);
}
- public bool HasConfigurationChanged(ProjectSnapshot original)
+ public bool HasConfigurationChanged(DefaultProjectSnapshot original)
{
if (original == null)
{
@@ -119,4 +181,4 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
return !Enumerable.SequenceEqual(TagHelpers, original.TagHelpers);
}
}
-}
+}
\ No newline at end of file
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotManager.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotManager.cs
index 3534e3789c..ff6584fde5 100644
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotManager.cs
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotManager.cs
@@ -7,6 +7,22 @@ using System.Linq;
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
+ // The implementation of project snapshot manager abstracts over the Roslyn Project (WorkspaceProject)
+ // and information from the host's underlying project system (HostProject), to provide a unified and
+ // immutable view of the underlying project systems.
+ //
+ // The HostProject support all of the configuration that the Razor SDK exposes via the project system
+ // (language version, extensions, named configuration).
+ //
+ // The WorkspaceProject is needed to support our use of Roslyn Compilations for Tag Helpers and other
+ // C# based constructs.
+ //
+ // The implementation will create a ProjectSnapshot for each HostProject. Put another way, when we
+ // see a WorkspaceProject get created, we only care if we already have a HostProject for the same
+ // filepath.
+ //
+ // Our underlying HostProject infrastructure currently does not handle multiple TFMs (project with
+ // $(TargetFrameworks), so we just bind to the first WorkspaceProject we see for each HostProject.
internal class DefaultProjectSnapshotManager : ProjectSnapshotManagerBase
{
public override event EventHandler Changed;
@@ -17,8 +33,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
private readonly ProjectSnapshotWorkerQueue _workerQueue;
private readonly ProjectSnapshotWorker _worker;
- private readonly Dictionary _projects;
-
+ private readonly Dictionary _projects;
+
public DefaultProjectSnapshotManager(
ForegroundDispatcher foregroundDispatcher,
ErrorReporter errorReporter,
@@ -57,7 +73,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
_triggers = triggers.ToArray();
Workspace = workspace;
- _projects = new Dictionary();
+ _projects = new Dictionary(FilePathComparer.Instance);
+
_workerQueue = new ProjectSnapshotWorkerQueue(_foregroundDispatcher, this, worker);
for (var i = 0; i < _triggers.Length; i++)
@@ -70,63 +87,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,25 +101,203 @@ 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());
+ }
+
+ if (!object.Equals(snapshot.ComputedVersion, original.ComputedVersion))
+ {
+ NotifyListeners(new ProjectChangeEventArgs(snapshot, ProjectChangeKind.TagHelpersChanged));
+ }
+ }
+ }
+
+ public override void HostProjectAdded(HostProject hostProject)
+ {
+ if (hostProject == null)
+ {
+ throw new ArgumentNullException(nameof(hostProject));
+ }
+
+ _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))
+ {
+ 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());
}
- // Now we need to know if the changes that we applied are significant. If that's the case then
- // we need to notify listeners.
- if (snapshot.HasConfigurationChanged(original))
+ // 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 HostProjectBuildComplete(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;
+
+ // Notify the background worker so it can trigger tag helper discovery.
+ NotifyBackgroundWorker(snapshot.CreateUpdateContext());
+ }
+ }
+
+ public override void WorkspaceProjectAdded(Project workspaceProject)
+ {
+ if (workspaceProject == null)
+ {
+ throw new ArgumentNullException(nameof(workspaceProject));
+ }
+
+ _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.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());
+ }
if (snapshot.HaveTagHelpersChanged(original))
{
@@ -161,58 +306,136 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
}
}
- public override void ProjectRemoved(Project underlyingProject)
+ public override void WorkspaceProjectRemoved(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 snapshot))
- {
- _projects.Remove(underlyingProject.Id);
- // We need to notify listeners about every project removal.
- NotifyListeners(new ProjectChangeEventArgs(snapshot, ProjectChangeKind.Removed));
+ _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 ProjectBuildComplete(Project underlyingProject)
+ public override void ReportError(Exception exception)
{
- if (underlyingProject == null)
+ if (exception == null)
{
- throw new ArgumentNullException(nameof(underlyingProject));
+ throw new ArgumentNullException(nameof(exception));
}
- 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;
-
- // Notify the background worker so it can trigger tag helper discovery.
- NotifyBackgroundWorker(underlyingProject);
- }
+ _errorReporter.ReportError(exception);
}
- public override void ProjectsCleared()
+ public override void ReportError(Exception exception, ProjectSnapshot project)
{
- foreach (var kvp in _projects.ToArray())
+ if (exception == 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(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
@@ -226,15 +449,5 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
handler(this, e);
}
}
-
- public override void ReportError(Exception exception)
- {
- _errorReporter.ReportError(exception);
- }
-
- public override void ReportError(Exception exception, Project project)
- {
- _errorReporter.ReportError(exception, project);
- }
}
-}
+}
\ No newline at end of file
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotWorker.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotWorker.cs
index 5a9a3900bb..1b7480f940 100644
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotWorker.cs
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotWorker.cs
@@ -9,32 +9,22 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
internal class DefaultProjectSnapshotWorker : ProjectSnapshotWorker
{
- private readonly ProjectExtensibilityConfigurationFactory _configurationFactory;
private readonly ForegroundDispatcher _foregroundDispatcher;
private readonly TagHelperResolver _tagHelperResolver;
- public DefaultProjectSnapshotWorker(
- ForegroundDispatcher foregroundDispatcher,
- ProjectExtensibilityConfigurationFactory configurationFactory,
- TagHelperResolver tagHelperResolver)
+ public DefaultProjectSnapshotWorker(ForegroundDispatcher foregroundDispatcher, TagHelperResolver tagHelperResolver)
{
if (foregroundDispatcher == null)
{
throw new ArgumentNullException(nameof(foregroundDispatcher));
}
- if (configurationFactory == null)
- {
- throw new ArgumentNullException(nameof(configurationFactory));
- }
-
if (tagHelperResolver == null)
{
throw new ArgumentNullException(nameof(tagHelperResolver));
}
_foregroundDispatcher = foregroundDispatcher;
- _configurationFactory = configurationFactory;
_tagHelperResolver = tagHelperResolver;
}
@@ -54,16 +44,18 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
return ProjectUpdatesCoreAsync(update);
}
+ protected virtual void OnProcessingUpdate()
+ {
+ }
+
private async Task ProjectUpdatesCoreAsync(object state)
{
var update = (ProjectSnapshotUpdateContext)state;
- // We'll have more things to process here, but for now we're just hardcoding the configuration.
+ OnProcessingUpdate();
- var configuration = await _configurationFactory.GetConfigurationAsync(update.UnderlyingProject);
- update.Configuration = configuration;
-
- var result = await _tagHelperResolver.GetTagHelpersAsync(update.UnderlyingProject, CancellationToken.None);
+ var snapshot = new DefaultProjectSnapshot(update.HostProject, update.WorkspaceProject, update.Version);
+ var result = await _tagHelperResolver.GetTagHelpersAsync(snapshot, CancellationToken.None);
update.TagHelpers = result.Descriptors;
}
}
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotWorkerFactory.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotWorkerFactory.cs
index ca1946c19e..bd36bf361d 100644
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotWorkerFactory.cs
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotWorkerFactory.cs
@@ -26,10 +26,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
public ILanguageService CreateLanguageService(HostLanguageServices languageServices)
{
- return new DefaultProjectSnapshotWorker(
- _foregroundDispatcher,
- languageServices.GetRequiredService(),
- languageServices.GetRequiredService());
+ return new DefaultProjectSnapshotWorker(_foregroundDispatcher, languageServices.GetRequiredService());
}
}
}
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/FallbackRazorConfiguration.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/FallbackRazorConfiguration.cs
new file mode 100644
index 0000000000..707125158a
--- /dev/null
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/FallbackRazorConfiguration.cs
@@ -0,0 +1,78 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using Microsoft.AspNetCore.Razor.Language;
+
+namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
+{
+ internal class FallbackRazorConfiguration : RazorConfiguration
+ {
+ public static readonly RazorConfiguration MVC_1_0 = new FallbackRazorConfiguration(
+ RazorLanguageVersion.Version_1_0,
+ "MVC-1.0",
+ new[] { new FallbackRazorExtension("MVC-1.0"), });
+
+ public static readonly RazorConfiguration MVC_1_1 = new FallbackRazorConfiguration(
+ RazorLanguageVersion.Version_1_1,
+ "MVC-1.1",
+ new[] { new FallbackRazorExtension("MVC-1.1"), });
+
+ public static readonly RazorConfiguration MVC_2_0 = new FallbackRazorConfiguration(
+ RazorLanguageVersion.Version_2_0,
+ "MVC-2.0",
+ new[] { new FallbackRazorExtension("MVC-2.0"), });
+
+ public static RazorConfiguration SelectConfiguration(Version version)
+ {
+ if (version.Major == 1 && version.Minor == 0)
+ {
+ return MVC_1_0;
+ }
+ else if (version.Major == 1 && version.Minor == 1)
+ {
+ return MVC_1_1;
+ }
+ else if (version.Major == 2 && version.Minor == 0)
+ {
+ return MVC_2_0;
+ }
+ else
+ {
+ return MVC_2_0;
+ }
+ }
+
+ public FallbackRazorConfiguration(
+ RazorLanguageVersion languageVersion,
+ string configurationName,
+ RazorExtension[] extensions)
+ {
+ if (languageVersion == null)
+ {
+ throw new ArgumentNullException(nameof(languageVersion));
+ }
+
+ if (configurationName == null)
+ {
+ throw new ArgumentNullException(nameof(configurationName));
+ }
+
+ if (extensions == null)
+ {
+ throw new ArgumentNullException(nameof(extensions));
+ }
+
+ LanguageVersion = languageVersion;
+ ConfigurationName = configurationName;
+ Extensions = extensions;
+ }
+
+ public override string ConfigurationName { get; }
+
+ public override IReadOnlyList Extensions { get; }
+
+ public override RazorLanguageVersion LanguageVersion { get; }
+ }
+}
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/FallbackRazorExtension.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/FallbackRazorExtension.cs
new file mode 100644
index 0000000000..5080b0705d
--- /dev/null
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/FallbackRazorExtension.cs
@@ -0,0 +1,23 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.Razor.Language;
+
+namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
+{
+ internal class FallbackRazorExtension : RazorExtension
+ {
+ public FallbackRazorExtension(string extensionName)
+ {
+ if (extensionName == null)
+ {
+ throw new ArgumentNullException(nameof(extensionName));
+ }
+
+ ExtensionName = extensionName;
+ }
+
+ public override string ExtensionName { get; }
+ }
+}
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/HostProject.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/HostProject.cs
new file mode 100644
index 0000000000..cf3c1524b6
--- /dev/null
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/HostProject.cs
@@ -0,0 +1,31 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.Razor.Language;
+
+namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
+{
+ internal class HostProject
+ {
+ public HostProject(string projectFilePath, RazorConfiguration razorConfiguration)
+ {
+ if (projectFilePath == null)
+ {
+ throw new ArgumentNullException(nameof(projectFilePath));
+ }
+
+ if (razorConfiguration == null)
+ {
+ throw new ArgumentNullException(nameof(razorConfiguration));
+ }
+
+ FilePath = projectFilePath;
+ Configuration = razorConfiguration;
+ }
+
+ public RazorConfiguration Configuration { get; }
+
+ public string FilePath { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/MvcExtensibilityConfiguration.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/MvcExtensibilityConfiguration.cs
deleted file mode 100644
index dfe2b88904..0000000000
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/MvcExtensibilityConfiguration.cs
+++ /dev/null
@@ -1,86 +0,0 @@
-// Copyright (c) .NET Foundation. All rights reserved.
-// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using Microsoft.AspNetCore.Razor.Language;
-using Microsoft.Extensions.Internal;
-
-namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
-{
- internal class MvcExtensibilityConfiguration : ProjectExtensibilityConfiguration
- {
- public MvcExtensibilityConfiguration(
- RazorLanguageVersion languageVersion,
- ProjectExtensibilityConfigurationKind kind,
- ProjectExtensibilityAssembly razorAssembly,
- ProjectExtensibilityAssembly mvcAssembly)
- {
- if (razorAssembly == null)
- {
- throw new ArgumentNullException(nameof(razorAssembly));
- }
-
- if (mvcAssembly == null)
- {
- throw new ArgumentNullException(nameof(mvcAssembly));
- }
-
- Kind = kind;
- RazorAssembly = razorAssembly;
- MvcAssembly = mvcAssembly;
- LanguageVersion = languageVersion;
-
- Assemblies = new[] { RazorAssembly, MvcAssembly, };
- }
-
- public override IReadOnlyList Assemblies { get; }
-
- // MVC: '2.0.0' (fallback) | Razor Language '2.0.0'
- // or
- // MVC: '2.1.3' | Razor Language '2.1.3'
- public override string DisplayName => $"MVC: {MvcAssembly.Identity.Version.ToString(3)}" + (Kind == ProjectExtensibilityConfigurationKind.Fallback? " (fallback)" : string.Empty) + " | " + LanguageVersion;
-
- public override ProjectExtensibilityConfigurationKind Kind { get; }
-
- public override ProjectExtensibilityAssembly RazorAssembly { get; }
-
- public override RazorLanguageVersion LanguageVersion { get; }
-
- public ProjectExtensibilityAssembly MvcAssembly { get; }
-
- public override bool Equals(ProjectExtensibilityConfiguration other)
- {
- if (other == null)
- {
- return false;
- }
-
- // We're intentionally ignoring the 'Kind' here. That's mostly for diagnostics and doesn't influence any behavior.
- return LanguageVersion == other.LanguageVersion &&
- Enumerable.SequenceEqual(
- Assemblies.OrderBy(a => a.Identity.Name).Select(a => a.Identity),
- other.Assemblies.OrderBy(a => a.Identity.Name).Select(a => a.Identity),
- AssemblyIdentityEqualityComparer.NameAndVersion);
- }
-
- public override int GetHashCode()
- {
- var hash = new HashCodeCombiner();
- foreach (var assembly in Assemblies.OrderBy(a => a.Identity.Name))
- {
- hash.Add(assembly);
- }
-
- hash.Add(LanguageVersion);
-
- return hash;
- }
-
- public override string ToString()
- {
- return DisplayName;
- }
- }
-}
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectExtensibilityConfiguration.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectExtensibilityConfiguration.cs
deleted file mode 100644
index a868c4781d..0000000000
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectExtensibilityConfiguration.cs
+++ /dev/null
@@ -1,31 +0,0 @@
-// Copyright (c) .NET Foundation. All rights reserved.
-// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-
-using System;
-using System.Collections.Generic;
-using Microsoft.AspNetCore.Razor.Language;
-
-namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
-{
- internal abstract class ProjectExtensibilityConfiguration : IEquatable
- {
- public abstract IReadOnlyList Assemblies { get; }
-
- public abstract string DisplayName { get; }
-
- public abstract ProjectExtensibilityConfigurationKind Kind { get; }
-
- public abstract ProjectExtensibilityAssembly RazorAssembly { get; }
-
- public abstract RazorLanguageVersion LanguageVersion { get; }
-
- public abstract bool Equals(ProjectExtensibilityConfiguration other);
-
- public abstract override int GetHashCode();
-
- public override bool Equals(object obj)
- {
- return Equals(obj as ProjectExtensibilityConfiguration);
- }
- }
-}
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectExtensibilityConfigurationFactory.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectExtensibilityConfigurationFactory.cs
deleted file mode 100644
index 71d929dd29..0000000000
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectExtensibilityConfigurationFactory.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-// Copyright (c) .NET Foundation. All rights reserved.
-// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.CodeAnalysis.Host;
-
-namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
-{
- internal abstract class ProjectExtensibilityConfigurationFactory : ILanguageService
- {
- public abstract Task GetConfigurationAsync(Project project, CancellationToken cancellationToken = default(CancellationToken));
- }
-}
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectExtensibilityConfigurationKind.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectExtensibilityConfigurationKind.cs
deleted file mode 100644
index 0efc5e5e37..0000000000
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectExtensibilityConfigurationKind.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-// Copyright (c) .NET Foundation. All rights reserved.
-// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-
-namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
-{
- ///
- /// Describes how closely the configuration of Razor tooling matches the actual project dependencies.
- ///
- internal enum ProjectExtensibilityConfigurationKind
- {
- ApproximateMatch,
- Fallback,
- }
-}
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshot.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshot.cs
index 5976b72501..538b3cb9fa 100644
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshot.cs
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshot.cs
@@ -9,10 +9,18 @@ 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 IReadOnlyList TagHelpers { get; }
+
+ public abstract VersionStamp Version { get; }
+
+ public abstract Project WorkspaceProject { get; }
+
+ public abstract HostProject HostProject { get; }
}
-}
+}
\ No newline at end of file
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotManagerBase.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotManagerBase.cs
index 4dd392076b..026b27956f 100644
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotManagerBase.cs
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotManagerBase.cs
@@ -9,20 +9,28 @@ 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 HostProjectBuildComplete(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, Project project);
+ public abstract void ReportError(Exception exception, ProjectSnapshot project);
+
+ public abstract void ReportError(Exception exception, HostProject hostProject);
+
+ public abstract void ReportError(Exception exception, Project workspaceProject);
}
-}
+}
\ No newline at end of file
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotManagerExtensions.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotManagerExtensions.cs
index c926021eb0..d6299717ca 100644
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotManagerExtensions.cs
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotManagerExtensions.cs
@@ -13,7 +13,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
for (var i = 0; i< projects.Count; i++)
{
var project = projects[i];
- if (string.Equals(filePath, project.UnderlyingProject.FilePath, StringComparison.OrdinalIgnoreCase))
+ if (FilePathComparer.Instance.Equals(filePath, project.FilePath))
{
return project;
}
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotUpdateContext.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotUpdateContext.cs
index dec827bdd8..cddb3b08c1 100644
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotUpdateContext.cs
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotUpdateContext.cs
@@ -9,20 +9,37 @@ 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 IReadOnlyList TagHelpers { get; set; }
+
+ public VersionStamp Version { get; }
}
}
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotWorkerQueue.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotWorkerQueue.cs
index 0dcbe91284..69c0062f05 100644
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotWorkerQueue.cs
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotWorkerQueue.cs
@@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
-using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -16,7 +15,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
private readonly DefaultProjectSnapshotManager _projectManager;
private readonly ProjectSnapshotWorker _projectWorker;
- private readonly Dictionary _projects;
+ private readonly Dictionary _projects;
private Timer _timer;
public ProjectSnapshotWorkerQueue(ForegroundDispatcher foregroundDispatcher, DefaultProjectSnapshotManager projectManager, ProjectSnapshotWorker projectWorker)
@@ -40,7 +39,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
_projectManager = projectManager;
_projectWorker = projectWorker;
- _projects = new Dictionary();
+ _projects = new Dictionary(FilePathComparer.Instance);
}
public bool HasPendingNotifications
@@ -93,11 +92,11 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
}
}
- public void Enqueue(Project project)
+ public void Enqueue(ProjectSnapshotUpdateContext context)
{
- if (project == null)
+ if (context == null)
{
- throw new ArgumentNullException();
+ throw new ArgumentNullException(nameof(context));
}
_foregroundDispatcher.AssertForegroundThread();
@@ -106,7 +105,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
// We only want to store the last 'seen' version of any given project. That way when we pick one to process
// it's always the best version to use.
- _projects[project.Id] = project;
+ _projects[context.FilePath] = context;
StartWorker();
}
@@ -133,7 +132,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
OnStartingBackgroundWork();
- Project[] work;
+ ProjectSnapshotUpdateContext[] work;
lock (_projects)
{
work = _projects.Values.ToArray();
@@ -145,7 +144,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
try
{
- updates[i] = (new ProjectSnapshotUpdateContext(work[i]), null);
+ updates[i] = (work[i], null);
await _projectWorker.ProcessUpdateAsync(updates[i].context);
}
catch (Exception projectException)
@@ -196,7 +195,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
}
else
{
- _projectManager.ReportError(update.exception, update.context?.UnderlyingProject);
+ _projectManager.ReportError(update.exception, update.context?.WorkspaceProject);
}
}
}
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSystemRazorConfiguration.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSystemRazorConfiguration.cs
new file mode 100644
index 0000000000..43dfebe864
--- /dev/null
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSystemRazorConfiguration.cs
@@ -0,0 +1,43 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using Microsoft.AspNetCore.Razor.Language;
+
+namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
+{
+ internal class ProjectSystemRazorConfiguration : RazorConfiguration
+ {
+ public ProjectSystemRazorConfiguration(
+ RazorLanguageVersion languageVersion,
+ string configurationName,
+ RazorExtension[] extensions)
+ {
+ if (languageVersion == null)
+ {
+ throw new ArgumentNullException(nameof(languageVersion));
+ }
+
+ if (configurationName == null)
+ {
+ throw new ArgumentNullException(nameof(configurationName));
+ }
+
+ if (extensions == null)
+ {
+ throw new ArgumentNullException(nameof(extensions));
+ }
+
+ LanguageVersion = languageVersion;
+ ConfigurationName = configurationName;
+ Extensions = extensions;
+ }
+
+ public override string ConfigurationName { get; }
+
+ public override IReadOnlyList Extensions { get; }
+
+ public override RazorLanguageVersion LanguageVersion { get; }
+ }
+}
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSystemRazorExtension.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSystemRazorExtension.cs
new file mode 100644
index 0000000000..77f742c563
--- /dev/null
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSystemRazorExtension.cs
@@ -0,0 +1,23 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.Razor.Language;
+
+namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
+{
+ internal class ProjectSystemRazorExtension : RazorExtension
+ {
+ public ProjectSystemRazorExtension(string extensionName)
+ {
+ if (extensionName == null)
+ {
+ throw new ArgumentNullException(nameof(extensionName));
+ }
+
+ ExtensionName = extensionName;
+ }
+
+ public override string ExtensionName { get; }
+ }
+}
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/WorkspaceProjectSnapshotChangeTrigger.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/WorkspaceProjectSnapshotChangeTrigger.cs
index 1769da0197..fb2deeec32 100644
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/WorkspaceProjectSnapshotChangeTrigger.cs
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/WorkspaceProjectSnapshotChangeTrigger.cs
@@ -23,51 +23,43 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
Debug.Assert(solution != null);
- _projectManager.ProjectsCleared();
-
foreach (var project in solution.Projects)
{
- if (project.Language == LanguageNames.CSharp)
- {
- _projectManager.ProjectAdded(project);
- }
+ _projectManager.WorkspaceProjectAdded(project);
}
}
// Internal for testing
internal void Workspace_WorkspaceChanged(object sender, WorkspaceChangeEventArgs e)
{
- Project underlyingProject;
+ Project project;
switch (e.Kind)
{
case WorkspaceChangeKind.ProjectAdded:
{
- underlyingProject = e.NewSolution.GetProject(e.ProjectId);
- Debug.Assert(underlyingProject != null);
+ project = e.NewSolution.GetProject(e.ProjectId);
+ Debug.Assert(project != null);
- if (underlyingProject.Language == LanguageNames.CSharp)
- {
- _projectManager.ProjectAdded(underlyingProject);
- }
+ _projectManager.WorkspaceProjectAdded(project);
break;
}
case WorkspaceChangeKind.ProjectChanged:
case WorkspaceChangeKind.ProjectReloaded:
{
- underlyingProject = e.NewSolution.GetProject(e.ProjectId);
- Debug.Assert(underlyingProject != null);
+ project = e.NewSolution.GetProject(e.ProjectId);
+ Debug.Assert(project != null);
- _projectManager.ProjectChanged(underlyingProject);
+ _projectManager.WorkspaceProjectChanged(project);
break;
}
case WorkspaceChangeKind.ProjectRemoved:
{
- underlyingProject = e.OldSolution.GetProject(e.ProjectId);
- Debug.Assert(underlyingProject != null);
+ project = e.OldSolution.GetProject(e.ProjectId);
+ Debug.Assert(project != null);
- _projectManager.ProjectRemoved(underlyingProject);
+ _projectManager.WorkspaceProjectRemoved(project);
break;
}
@@ -76,6 +68,15 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
case WorkspaceChangeKind.SolutionCleared:
case WorkspaceChangeKind.SolutionReloaded:
case WorkspaceChangeKind.SolutionRemoved:
+
+ if (e.OldSolution != null)
+ {
+ foreach (var p in e.OldSolution.Projects)
+ {
+ _projectManager.WorkspaceProjectRemoved(p);
+ }
+ }
+
InitializeSolution(e.NewSolution);
break;
}
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/Properties/AssemblyInfo.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/Properties/AssemblyInfo.cs
index a73900fa42..d6cf7bdc7e 100644
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/Properties/AssemblyInfo.cs
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/Properties/AssemblyInfo.cs
@@ -2,7 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Runtime.CompilerServices;
-
+[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Razor.Performance, 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")]
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/RazorProjectEngineFactoryService.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/RazorProjectEngineFactoryService.cs
index 0388de4671..4defb21947 100644
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/RazorProjectEngineFactoryService.cs
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/RazorProjectEngineFactoryService.cs
@@ -4,11 +4,20 @@
using System;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis.Host;
+using Microsoft.CodeAnalysis.Razor.ProjectSystem;
namespace Microsoft.CodeAnalysis.Razor
{
internal abstract class RazorProjectEngineFactoryService : ILanguageService
{
- public abstract RazorProjectEngine Create(string projectPath, Action configure);
+ public abstract IProjectEngineFactory FindFactory(ProjectSnapshot project);
+
+ public abstract IProjectEngineFactory FindSerializableFactory(ProjectSnapshot project);
+
+ public abstract RazorProjectEngine Create(ProjectSnapshot project, Action configure);
+
+ public abstract RazorProjectEngine Create(ProjectSnapshot project, RazorProjectFileSystem fileSystem, Action configure);
+
+ public abstract RazorProjectEngine Create(string directoryPath, Action configure);
}
}
\ No newline at end of file
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/TagHelperResolutionResult.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/TagHelperResolutionResult.cs
index 21b553cde8..0e8c7b19ef 100644
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/TagHelperResolutionResult.cs
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/TagHelperResolutionResult.cs
@@ -1,6 +1,7 @@
// 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;
@@ -8,6 +9,8 @@ namespace Microsoft.CodeAnalysis.Razor
{
public sealed class TagHelperResolutionResult
{
+ internal static TagHelperResolutionResult Empty = new TagHelperResolutionResult(Array.Empty(), Array.Empty());
+
public TagHelperResolutionResult(IReadOnlyList descriptors, IReadOnlyList diagnostics)
{
Descriptors = descriptors;
diff --git a/src/Microsoft.CodeAnalysis.Razor.Workspaces/TagHelperResolver.cs b/src/Microsoft.CodeAnalysis.Razor.Workspaces/TagHelperResolver.cs
index d79ed4fab2..b369056f37 100644
--- a/src/Microsoft.CodeAnalysis.Razor.Workspaces/TagHelperResolver.cs
+++ b/src/Microsoft.CodeAnalysis.Razor.Workspaces/TagHelperResolver.cs
@@ -1,14 +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.Collections.Generic;
+using System.Linq;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis.Host;
+using Microsoft.CodeAnalysis.Razor.ProjectSystem;
namespace Microsoft.CodeAnalysis.Razor
{
internal abstract class TagHelperResolver : ILanguageService
{
- public abstract Task GetTagHelpersAsync(Project project, CancellationToken cancellationToken);
+ public abstract Task GetTagHelpersAsync(ProjectSnapshot project, CancellationToken cancellationToken = default);
+
+ protected virtual async Task GetTagHelpersAsync(ProjectSnapshot project, RazorProjectEngine engine)
+ {
+ if (project == null)
+ {
+ throw new ArgumentNullException(nameof(project));
+ }
+
+ if (engine == null)
+ {
+ throw new ArgumentNullException(nameof(engine));
+ }
+
+ if (project.WorkspaceProject == null)
+ {
+ return TagHelperResolutionResult.Empty;
+ }
+
+ var providers = engine.Engine.Features.OfType().ToArray();
+ if (providers.Length == 0)
+ {
+ return TagHelperResolutionResult.Empty;
+ }
+
+ var results = new List();
+ var context = TagHelperDescriptorProviderContext.Create(results);
+
+ var compilation = await project.WorkspaceProject.GetCompilationAsync().ConfigureAwait(false);
+ context.SetCompilation(compilation);
+
+ for (var i = 0; i < providers.Length; i++)
+ {
+ var provider = providers[i];
+ provider.Execute(context);
+ }
+
+ return new TagHelperResolutionResult(results, Array.Empty());
+ }
}
}
diff --git a/src/Microsoft.CodeAnalysis.Razor/FilePathComparer.cs b/src/Microsoft.CodeAnalysis.Razor/FilePathComparer.cs
new file mode 100644
index 0000000000..a0ca3cb9a3
--- /dev/null
+++ b/src/Microsoft.CodeAnalysis.Razor/FilePathComparer.cs
@@ -0,0 +1,30 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Runtime.InteropServices;
+
+namespace Microsoft.CodeAnalysis.Razor
+{
+ internal static class FilePathComparer
+ {
+ private static StringComparer _instance;
+
+ public static StringComparer Instance
+ {
+ get
+ {
+ if (_instance == null && RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ _instance = StringComparer.Ordinal;
+ }
+ else if (_instance == null)
+ {
+ _instance = StringComparer.OrdinalIgnoreCase;
+ }
+
+ return _instance;
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.CodeAnalysis.Remote.Razor/DefaultTagHelperResolver.cs b/src/Microsoft.CodeAnalysis.Remote.Razor/DefaultTagHelperResolver.cs
deleted file mode 100644
index 4aca5c8f7e..0000000000
--- a/src/Microsoft.CodeAnalysis.Remote.Razor/DefaultTagHelperResolver.cs
+++ /dev/null
@@ -1,54 +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.Threading;
-using System.Threading.Tasks;
-using Microsoft.AspNetCore.Mvc.Razor.Extensions;
-using Microsoft.AspNetCore.Razor.Language;
-using Microsoft.CodeAnalysis.Razor;
-
-namespace Microsoft.CodeAnalysis.Remote.Razor
-{
- internal class DefaultTagHelperResolver : TagHelperResolver
- {
- public DefaultTagHelperResolver(bool designTime)
- {
- DesignTime = designTime;
- }
-
- public bool DesignTime { get; }
-
- private TagHelperResolutionResult GetTagHelpers(Compilation compilation)
- {
- var descriptors = new List();
-
- var providers = new ITagHelperDescriptorProvider[]
- {
- new DefaultTagHelperDescriptorProvider() { DesignTime = DesignTime, },
- new ViewComponentTagHelperDescriptorProvider(),
- };
-
- var results = new List();
- var context = TagHelperDescriptorProviderContext.Create(results);
- context.SetCompilation(compilation);
-
- for (var i = 0; i < providers.Length; i++)
- {
- var provider = providers[i];
- provider.Execute(context);
- }
-
- var diagnostics = new List();
- var resolutionResult = new TagHelperResolutionResult(results, diagnostics);
-
- return resolutionResult;
- }
-
- public override async Task GetTagHelpersAsync(Project project, CancellationToken cancellationToken)
- {
- var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
- return GetTagHelpers(compilation);
- }
- }
-}
diff --git a/src/Microsoft.CodeAnalysis.Remote.Razor/Microsoft.CodeAnalysis.Remote.Razor.csproj b/src/Microsoft.CodeAnalysis.Remote.Razor/Microsoft.CodeAnalysis.Remote.Razor.csproj
index 7cf58777ef..369dd82020 100644
--- a/src/Microsoft.CodeAnalysis.Remote.Razor/Microsoft.CodeAnalysis.Remote.Razor.csproj
+++ b/src/Microsoft.CodeAnalysis.Remote.Razor/Microsoft.CodeAnalysis.Remote.Razor.csproj
@@ -7,12 +7,13 @@
-
+
+ Serialization\%(FileName)%(Extension)
+
-
diff --git a/src/Microsoft.CodeAnalysis.Remote.Razor/RazorLanguageService.cs b/src/Microsoft.CodeAnalysis.Remote.Razor/RazorLanguageService.cs
index 930abdee3a..f0bf9bdaf8 100644
--- a/src/Microsoft.CodeAnalysis.Remote.Razor/RazorLanguageService.cs
+++ b/src/Microsoft.CodeAnalysis.Remote.Razor/RazorLanguageService.cs
@@ -10,34 +10,22 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis.Razor;
-using Microsoft.VisualStudio.LanguageServices.Razor;
+using Microsoft.CodeAnalysis.Razor.ProjectSystem;
namespace Microsoft.CodeAnalysis.Remote.Razor
{
- internal class RazorLanguageService : ServiceHubServiceBase
+ internal class RazorLanguageService : RazorServiceBase
{
public RazorLanguageService(Stream stream, IServiceProvider serviceProvider)
- : base(serviceProvider, stream)
+ : base(stream, serviceProvider)
{
- Rpc.JsonSerializer.Converters.Add(new RazorDiagnosticJsonConverter());
-
- // Due to this issue - https://github.com/dotnet/roslyn/issues/16900#issuecomment-277378950
- // We need to manually start the RPC connection. Otherwise we'd be opting ourselves into
- // race condition prone call paths.
- Rpc.StartListening();
}
- public async Task GetTagHelpersAsync(Guid projectIdBytes, string projectDebugName, CancellationToken cancellationToken = default(CancellationToken))
+ public async Task GetTagHelpersAsync(ProjectSnapshotHandle projectHandle, string factoryTypeName, CancellationToken cancellationToken = default)
{
- var projectId = ProjectId.CreateFromSerialized(projectIdBytes, projectDebugName);
+ var project = await GetProjectSnapshotAsync(projectHandle, cancellationToken).ConfigureAwait(false);
- var solution = await GetSolutionAsync(cancellationToken).ConfigureAwait(false);
- var project = solution.GetProject(projectId);
-
- var resolver = new DefaultTagHelperResolver(designTime: true);
- var result = await resolver.GetTagHelpersAsync(project, cancellationToken).ConfigureAwait(false);
-
- return result;
+ return await RazorServices.TagHelperResolver.GetTagHelpersAsync(project, factoryTypeName, cancellationToken);
}
public Task> GetDirectivesAsync(Guid projectIdBytes, string projectDebugName, CancellationToken cancellationToken = default(CancellationToken))
diff --git a/src/Microsoft.CodeAnalysis.Remote.Razor/RazorServiceBase.cs b/src/Microsoft.CodeAnalysis.Remote.Razor/RazorServiceBase.cs
new file mode 100644
index 0000000000..8fdbdd81da
--- /dev/null
+++ b/src/Microsoft.CodeAnalysis.Remote.Razor/RazorServiceBase.cs
@@ -0,0 +1,74 @@
+// 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.IO;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Razor.Language;
+using Microsoft.CodeAnalysis.Razor;
+using Microsoft.CodeAnalysis.Razor.ProjectSystem;
+
+namespace Microsoft.CodeAnalysis.Remote.Razor
+{
+ internal abstract class RazorServiceBase : ServiceHubServiceBase
+ {
+ public RazorServiceBase(Stream stream, IServiceProvider serviceProvider)
+ : base(serviceProvider, stream)
+ {
+ RazorServices = new RazorServices();
+
+ Rpc.JsonSerializer.Converters.RegisterRazorConverters();
+
+ // Due to this issue - https://github.com/dotnet/roslyn/issues/16900#issuecomment-277378950
+ // We need to manually start the RPC connection. Otherwise we'd be opting ourselves into
+ // race condition prone call paths.
+ Rpc.StartListening();
+ }
+
+ protected RazorServices RazorServices { get; }
+
+ protected virtual async Task GetProjectSnapshotAsync(ProjectSnapshotHandle projectHandle, CancellationToken cancellationToken)
+ {
+ if (projectHandle == null)
+ {
+ throw new ArgumentNullException(nameof(projectHandle));
+ }
+
+ var solution = await GetSolutionAsync(cancellationToken).ConfigureAwait(false);
+ var workspaceProject = solution.GetProject(projectHandle.WorkspaceProjectId);
+
+ return new SerializedProjectSnapshot(projectHandle.FilePath, projectHandle.Configuration, workspaceProject);
+ }
+
+ private class SerializedProjectSnapshot : ProjectSnapshot
+ {
+ public SerializedProjectSnapshot(string filePath, RazorConfiguration configuration, Project workspaceProject)
+ {
+ FilePath = filePath;
+ Configuration = configuration;
+ HostProject = new HostProject(filePath, configuration);
+ WorkspaceProject = workspaceProject;
+ TagHelpers = Array.Empty();
+
+ IsInitialized = true;
+ Version = VersionStamp.Default;
+ }
+
+ public override RazorConfiguration Configuration { get; }
+
+ public override string FilePath { get; }
+
+ public override bool IsInitialized { get; }
+
+ public override VersionStamp Version { get; }
+
+ public override Project WorkspaceProject { get; }
+
+ public override HostProject HostProject { get; }
+
+ public override IReadOnlyList TagHelpers { get; }
+ }
+ }
+}
diff --git a/src/Microsoft.CodeAnalysis.Remote.Razor/RazorServices.cs b/src/Microsoft.CodeAnalysis.Remote.Razor/RazorServices.cs
new file mode 100644
index 0000000000..d3a60a400c
--- /dev/null
+++ b/src/Microsoft.CodeAnalysis.Remote.Razor/RazorServices.cs
@@ -0,0 +1,22 @@
+// 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
+{
+ // Provides access to Razor language and workspace services that are avialable in the OOP host.
+ //
+ // Since we don't have access to the workspace we only have access to some specific things
+ // that we can construct directly.
+ internal class RazorServices
+ {
+ public RazorServices()
+ {
+ FallbackProjectEngineFactory = new FallbackProjectEngineFactory();
+ TagHelperResolver = new RemoteTagHelperResolver(FallbackProjectEngineFactory);
+ }
+
+ public IFallbackProjectEngineFactory FallbackProjectEngineFactory { get; }
+
+ public RemoteTagHelperResolver TagHelperResolver { get; }
+ }
+}
diff --git a/src/Microsoft.CodeAnalysis.Remote.Razor/RemoteTagHelperResolver.cs b/src/Microsoft.CodeAnalysis.Remote.Razor/RemoteTagHelperResolver.cs
new file mode 100644
index 0000000000..5c8697db7f
--- /dev/null
+++ b/src/Microsoft.CodeAnalysis.Remote.Razor/RemoteTagHelperResolver.cs
@@ -0,0 +1,84 @@
+// 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.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Razor.Language;
+using Microsoft.CodeAnalysis.Razor.ProjectSystem;
+
+namespace Microsoft.CodeAnalysis.Razor
+{
+ internal class RemoteTagHelperResolver : TagHelperResolver
+ {
+ private readonly static RazorConfiguration DefaultConfiguration = FallbackRazorConfiguration.MVC_2_0;
+
+ private readonly IFallbackProjectEngineFactory _fallbackFactory;
+
+ public RemoteTagHelperResolver(IFallbackProjectEngineFactory fallbackFactory)
+ {
+ if (fallbackFactory == null)
+ {
+ throw new ArgumentNullException(nameof(fallbackFactory));
+ }
+
+ _fallbackFactory = fallbackFactory;
+ }
+
+ public override Task GetTagHelpersAsync(ProjectSnapshot project, CancellationToken cancellationToken = default)
+ {
+ throw new NotImplementedException();
+ }
+
+ public Task GetTagHelpersAsync(ProjectSnapshot project, string factoryTypeName, CancellationToken cancellationToken = default)
+ {
+ if (project == null)
+ {
+ throw new ArgumentNullException(nameof(project));
+ }
+
+ if (project.Configuration == null || project.WorkspaceProject == null)
+ {
+ return Task.FromResult(TagHelperResolutionResult.Empty);
+ }
+
+ var engine = CreateProjectEngine(project, factoryTypeName);
+ return GetTagHelpersAsync(project, engine);
+ }
+
+ internal RazorProjectEngine CreateProjectEngine(ProjectSnapshot project, string factoryTypeName)
+ {
+ // This section is really similar to the code DefaultProjectEngineFactoryService
+ // but with a few differences that are significant in the remote scenario
+ //
+ // Most notably, we are going to find the Tag Helpers using a compilation, and we have
+ // no editor settings.
+ Action configure = (b) =>
+ {
+ b.Features.Add(new DefaultTagHelperDescriptorProvider() { DesignTime = true });
+ };
+
+ // The default configuration currently matches MVC-2.0. Beyond MVC-2.0 we added SDK support for
+ // properly detecting project versions, so that's a good version to assume when we can't find a
+ // configuration.
+ var configuration = project?.Configuration ?? DefaultConfiguration;
+
+ // If there's no factory to handle the configuration then fall back to a very basic configuration.
+ //
+ // This will stop a crash from happening in this case (misconfigured project), but will still make
+ // it obvious to the user that something is wrong.
+ var factory = CreateFactory(configuration, factoryTypeName) ?? _fallbackFactory;
+ return factory.Create(configuration, RazorProjectFileSystem.Empty, configure);
+ }
+
+ private IProjectEngineFactory CreateFactory(RazorConfiguration configuration, string factoryTypeName)
+ {
+ if (factoryTypeName == null)
+ {
+ return null;
+ }
+
+ return (IProjectEngineFactory)Activator.CreateInstance(Type.GetType(factoryTypeName, throwOnError: true));
+ }
+ }
+}
diff --git a/src/Microsoft.VisualStudio.Editor.Razor/DefaultProjectEngineFactoryService.cs b/src/Microsoft.VisualStudio.Editor.Razor/DefaultProjectEngineFactoryService.cs
index 5d1373729c..a2c64907f7 100644
--- a/src/Microsoft.VisualStudio.Editor.Razor/DefaultProjectEngineFactoryService.cs
+++ b/src/Microsoft.VisualStudio.Editor.Razor/DefaultProjectEngineFactoryService.cs
@@ -7,85 +7,177 @@ 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;
-using MvcLatest = Microsoft.AspNetCore.Mvc.Razor.Extensions;
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;
+ private readonly Workspace _workspace;
+ private readonly IFallbackProjectEngineFactory _defaultFactory;
+ private readonly Lazy[] _customFactories;
+ private ProjectSnapshotManager _projectManager;
- public DefaultProjectEngineFactoryService(ProjectSnapshotManager projectManager)
+ public DefaultProjectEngineFactoryService(
+ Workspace workspace,
+ IFallbackProjectEngineFactory defaultFactory,
+ Lazy[] customFactories)
+ {
+ if (workspace == null)
+ {
+ throw new ArgumentNullException(nameof(workspace));
+ }
+
+ if (defaultFactory == null)
+ {
+ throw new ArgumentNullException(nameof(defaultFactory));
+ }
+
+ if (customFactories == null)
+ {
+ throw new ArgumentNullException(nameof(customFactories));
+ }
+
+ _workspace = workspace;
+ _defaultFactory = defaultFactory;
+ _customFactories = customFactories;
+ }
+
+ // Internal for testing
+ internal DefaultProjectEngineFactoryService(
+ ProjectSnapshotManager projectManager,
+ IFallbackProjectEngineFactory defaultFactory,
+ Lazy[] customFactories)
{
if (projectManager == null)
{
throw new ArgumentNullException(nameof(projectManager));
}
+ if (defaultFactory == null)
+ {
+ throw new ArgumentNullException(nameof(defaultFactory));
+ }
+
+ if (customFactories == null)
+ {
+ throw new ArgumentNullException(nameof(customFactories));
+ }
+
_projectManager = projectManager;
+ _defaultFactory = defaultFactory;
+ _customFactories = customFactories;
}
- public override RazorProjectEngine Create(string projectPath, Action configure)
+ public override IProjectEngineFactory FindFactory(ProjectSnapshot project)
{
- if (projectPath == null)
+ if (project == null)
{
- throw new ArgumentNullException(nameof(projectPath));
+ throw new ArgumentNullException(nameof(project));
}
- // In 15.5 we expect projectPath to be a directory, NOT the path to the csproj.
- var project = FindProject(projectPath);
- var configuration = (project?.Configuration as MvcExtensibilityConfiguration) ?? DefaultConfiguration;
- var razorLanguageVersion = configuration.LanguageVersion;
-
- var razorConfiguration = new RazorConfiguration(razorLanguageVersion, "unnamed", Array.Empty());
- var fileSystem = RazorProjectFileSystem.Create(projectPath);
-
- RazorProjectEngine projectEngine;
- if (razorLanguageVersion.Major == 1)
- {
- projectEngine = RazorProjectEngine.Create(razorConfiguration, fileSystem, b =>
- {
- configure?.Invoke(b);
-
- Mvc1_X.RazorExtensions.Register(b);
-
- if (configuration.MvcAssembly.Identity.Version.Minor >= 1)
- {
- Mvc1_X.RazorExtensions.RegisterViewComponentTagHelpers(b);
- }
- });
- }
- else
- {
- projectEngine = RazorProjectEngine.Create(razorConfiguration, fileSystem, b =>
- {
- configure?.Invoke(b);
-
- MvcLatest.RazorExtensions.Register(b);
- });
- }
-
- return projectEngine;
+ return SelectFactory(project.Configuration ?? DefaultConfiguration, requireSerializable: false);
}
- private ProjectSnapshot FindProject(string directory)
+ public override IProjectEngineFactory FindSerializableFactory(ProjectSnapshot project)
+ {
+ if (project == null)
+ {
+ throw new ArgumentNullException(nameof(project));
+ }
+
+ return SelectFactory(project.Configuration ?? DefaultConfiguration, requireSerializable: true);
+ }
+
+ public override RazorProjectEngine Create(ProjectSnapshot project, Action configure)
+ {
+ if (project == null)
+ {
+ throw new ArgumentNullException(nameof(project));
+ }
+
+ return CreateCore(project, RazorProjectFileSystem.Create(Path.GetDirectoryName(project.FilePath)), configure);
+ }
+
+ public override RazorProjectEngine Create(string directoryPath, Action configure)
+ {
+ if (directoryPath == null)
+ {
+ throw new ArgumentNullException(nameof(directoryPath));
+ }
+
+ var project = FindProjectByDirectory(directoryPath);
+ return CreateCore(project, RazorProjectFileSystem.Create(directoryPath), configure);
+ }
+
+ public override RazorProjectEngine Create(ProjectSnapshot project, RazorProjectFileSystem fileSystem, Action configure)
+ {
+ if (project == null)
+ {
+ throw new ArgumentNullException(nameof(project));
+ }
+
+ if (fileSystem == null)
+ {
+ throw new ArgumentNullException(nameof(fileSystem));
+ }
+
+ return CreateCore(project, fileSystem, configure);
+ }
+
+ private RazorProjectEngine CreateCore(ProjectSnapshot project, RazorProjectFileSystem fileSystem, Action configure)
+ {
+ // When we're running in the editor, the editor provides a configure delegate that will include
+ // the editor settings and tag helpers.
+ //
+ // This service is only used in process in Visual Studio, and any other callers should provide these
+ // things also.
+ configure = configure ?? ((b) => { });
+
+ // The default configuration currently matches MVC-2.0. Beyond MVC-2.0 we added SDK support for
+ // properly detecting project versions, so that's a good version to assume when we can't find a
+ // configuration.
+ var configuration = project?.Configuration ?? DefaultConfiguration;
+
+ // If there's no factory to handle the configuration then fall back to a very basic configuration.
+ //
+ // This will stop a crash from happening in this case (misconfigured project), but will still make
+ // it obvious to the user that something is wrong.
+ var factory = SelectFactory(configuration) ?? _defaultFactory;
+ return factory.Create(configuration, fileSystem, configure);
+ }
+
+ private IProjectEngineFactory SelectFactory(RazorConfiguration configuration, bool requireSerializable = false)
+ {
+ for (var i = 0; i < _customFactories.Length; i++)
+ {
+ var factory = _customFactories[i];
+ if (string.Equals(configuration.ConfigurationName, factory.Metadata.ConfigurationName))
+ {
+ return requireSerializable && !factory.Metadata.SupportsSerialization ? null : factory.Value;
+ }
+ }
+
+ return null;
+ }
+
+ private ProjectSnapshot FindProjectByDirectory(string directory)
{
directory = NormalizeDirectoryPath(directory);
+ if (_projectManager == null)
+ {
+ _projectManager = _workspace.Services.GetLanguageServices(RazorLanguage.Name).GetRequiredService();
+ }
+
var projects = _projectManager.Projects;
for (var i = 0; i < projects.Count; i++)
{
var project = projects[i];
- if (project.UnderlyingProject.FilePath != null)
+ if (project.FilePath != null)
{
- if (string.Equals(directory, NormalizeDirectoryPath(Path.GetDirectoryName(project.UnderlyingProject.FilePath)), StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(directory, NormalizeDirectoryPath(Path.GetDirectoryName(project.FilePath)), StringComparison.OrdinalIgnoreCase))
{
return project;
}
diff --git a/src/Microsoft.VisualStudio.Editor.Razor/DefaultProjectEngineFactoryServiceFactory.cs b/src/Microsoft.VisualStudio.Editor.Razor/DefaultProjectEngineFactoryServiceFactory.cs
index babed97a5c..7f22134479 100644
--- a/src/Microsoft.VisualStudio.Editor.Razor/DefaultProjectEngineFactoryServiceFactory.cs
+++ b/src/Microsoft.VisualStudio.Editor.Razor/DefaultProjectEngineFactoryServiceFactory.cs
@@ -1,6 +1,10 @@
// 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.Composition;
+using System.Linq;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Razor;
@@ -11,9 +15,34 @@ namespace Microsoft.VisualStudio.Editor.Razor
[ExportLanguageServiceFactory(typeof(RazorProjectEngineFactoryService), RazorLanguage.Name, ServiceLayer.Default)]
internal class DefaultProjectEngineFactoryServiceFactory : ILanguageServiceFactory
{
+ private readonly Lazy[] _customFactories;
+ private readonly IFallbackProjectEngineFactory _fallbackFactory;
+
+ [ImportingConstructor]
+ public DefaultProjectEngineFactoryServiceFactory(
+ IFallbackProjectEngineFactory fallbackFactory,
+ [ImportMany] IEnumerable> customFactories)
+ {
+ if (fallbackFactory == null)
+ {
+ throw new ArgumentNullException(nameof(fallbackFactory));
+ }
+
+ if (customFactories == null)
+ {
+ throw new ArgumentNullException(nameof(customFactories));
+ }
+
+ _fallbackFactory = fallbackFactory;
+ _customFactories = customFactories.ToArray();
+ }
+
public ILanguageService CreateLanguageService(HostLanguageServices languageServices)
{
- return new DefaultProjectEngineFactoryService(languageServices.GetRequiredService());
+ return new DefaultProjectEngineFactoryService(
+ languageServices.WorkspaceServices.Workspace,
+ _fallbackFactory,
+ _customFactories);
}
}
}
\ No newline at end of file
diff --git a/src/Microsoft.VisualStudio.Editor.Razor/DefaultTagHelperResolver.cs b/src/Microsoft.VisualStudio.Editor.Razor/DefaultTagHelperResolver.cs
index ab49d1e27d..35b5c26567 100644
--- a/src/Microsoft.VisualStudio.Editor.Razor/DefaultTagHelperResolver.cs
+++ b/src/Microsoft.VisualStudio.Editor.Razor/DefaultTagHelperResolver.cs
@@ -2,59 +2,45 @@
// 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.Mvc.Razor.Extensions;
using Microsoft.AspNetCore.Razor.Language;
-using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Razor;
+using Microsoft.CodeAnalysis.Razor.ProjectSystem;
namespace Microsoft.VisualStudio.Editor.Razor
{
internal class DefaultTagHelperResolver : TagHelperResolver
{
- // Hack for testability. The view component visitor will normally just no op if we're not referencing
- // an appropriate version of MVC.
- internal bool ForceEnableViewComponentDiscovery { get; set; }
+ private readonly RazorProjectEngineFactoryService _engineFactory;
- public override async Task GetTagHelpersAsync(Project project, CancellationToken cancellationToken)
+ public DefaultTagHelperResolver(RazorProjectEngineFactoryService engineFactory)
+ {
+ if (engineFactory == null)
+ {
+ throw new ArgumentNullException(nameof(engineFactory));
+ }
+
+ _engineFactory = engineFactory;
+ }
+
+ public override Task GetTagHelpersAsync(ProjectSnapshot project, CancellationToken cancellationToken = default)
{
if (project == null)
{
throw new ArgumentNullException(nameof(project));
}
- var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
- var result = GetTagHelpers(compilation);
- return result;
- }
-
- // Internal for testing
- internal TagHelperResolutionResult GetTagHelpers(Compilation compilation)
- {
- var descriptors = new List();
-
- var providers = new ITagHelperDescriptorProvider[]
+ if (project.Configuration == null || project.WorkspaceProject == null)
{
- new DefaultTagHelperDescriptorProvider() { DesignTime = true, },
- new ViewComponentTagHelperDescriptorProvider() { ForceEnabled = ForceEnableViewComponentDiscovery },
- };
-
- var results = new List();
- var context = TagHelperDescriptorProviderContext.Create(results);
- context.SetCompilation(compilation);
-
- for (var i = 0; i < providers.Length; i++)
- {
- var provider = providers[i];
- provider.Execute(context);
+ return Task.FromResult(TagHelperResolutionResult.Empty);
}
- var diagnostics = new List();
- var resolutionResult = new TagHelperResolutionResult(results, diagnostics);
-
- return resolutionResult;
+ var engine = _engineFactory.Create(project, RazorProjectFileSystem.Empty, b =>
+ {
+ b.Features.Add(new DefaultTagHelperDescriptorProvider() { DesignTime = true, });
+ });
+ return GetTagHelpersAsync(project, engine);
}
}
}
diff --git a/src/Microsoft.VisualStudio.Editor.Razor/DefaultTagHelperResolverFactory.cs b/src/Microsoft.VisualStudio.Editor.Razor/DefaultTagHelperResolverFactory.cs
index 50e2e96179..1a238ad473 100644
--- a/src/Microsoft.VisualStudio.Editor.Razor/DefaultTagHelperResolverFactory.cs
+++ b/src/Microsoft.VisualStudio.Editor.Razor/DefaultTagHelperResolverFactory.cs
@@ -14,7 +14,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
{
public ILanguageService CreateLanguageService(HostLanguageServices languageServices)
{
- return new DefaultTagHelperResolver();
+ return new DefaultTagHelperResolver(languageServices.GetRequiredService());
}
}
}
\ No newline at end of file
diff --git a/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioDocumentTracker.cs b/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioDocumentTracker.cs
index f1b8593f6f..c6e6937b18 100644
--- a/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioDocumentTracker.cs
+++ b/src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioDocumentTracker.cs
@@ -91,7 +91,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
_textViews = new List();
}
- internal override ProjectExtensibilityConfiguration Configuration => _project?.Configuration;
+ public override RazorConfiguration Configuration => _project?.Configuration;
public override EditorSettings EditorSettings => _workspaceEditorSettings.Current;
@@ -99,7 +99,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
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;
@@ -196,7 +196,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
internal void ProjectManager_Changed(object sender, ProjectChangeEventArgs e)
{
if (_projectPath != null &&
- string.Equals(_projectPath, e.Project.UnderlyingProject.FilePath, StringComparison.OrdinalIgnoreCase))
+ string.Equals(_projectPath, e.Project.FilePath, StringComparison.OrdinalIgnoreCase))
{
if (e.Kind == ProjectChangeKind.TagHelpersChanged)
{
diff --git a/src/Microsoft.VisualStudio.Editor.Razor/LegacyProjectEngineFactory_1_0.cs b/src/Microsoft.VisualStudio.Editor.Razor/LegacyProjectEngineFactory_1_0.cs
new file mode 100644
index 0000000000..9b3b92eb89
--- /dev/null
+++ b/src/Microsoft.VisualStudio.Editor.Razor/LegacyProjectEngineFactory_1_0.cs
@@ -0,0 +1,32 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Reflection;
+using Microsoft.AspNetCore.Razor.Language;
+using Microsoft.CodeAnalysis.Razor;
+
+namespace Microsoft.VisualStudio.Editor.Razor
+{
+ [ExportCustomProjectEngineFactory("MVC-1.0", SupportsSerialization = true)]
+ internal class LegacyProjectEngineFactory_1_0 : IProjectEngineFactory
+ {
+ private const string AssemblyName = "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X";
+
+ public RazorProjectEngine Create(RazorConfiguration configuration, RazorProjectFileSystem fileSystem, Action configure)
+ {
+ // Rewrite the assembly name into a full name just like this one, but with the name of the MVC design time assembly.
+ var assemblyName = new AssemblyName(typeof(LegacyProjectEngineFactory_1_0).Assembly.FullName);
+ assemblyName.Name = AssemblyName;
+
+ var extension = new AssemblyExtension(configuration.ConfigurationName, Assembly.Load(assemblyName));
+ var initializer = extension.CreateInitializer();
+
+ return RazorProjectEngine.Create(configuration, fileSystem, b =>
+ {
+ initializer.Initialize(b);
+ configure?.Invoke(b);
+ });
+ }
+ }
+}
diff --git a/src/Microsoft.VisualStudio.Editor.Razor/LegacyProjectEngineFactory_1_1.cs b/src/Microsoft.VisualStudio.Editor.Razor/LegacyProjectEngineFactory_1_1.cs
new file mode 100644
index 0000000000..d01e6999a8
--- /dev/null
+++ b/src/Microsoft.VisualStudio.Editor.Razor/LegacyProjectEngineFactory_1_1.cs
@@ -0,0 +1,32 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Reflection;
+using Microsoft.AspNetCore.Razor.Language;
+using Microsoft.CodeAnalysis.Razor;
+
+namespace Microsoft.VisualStudio.Editor.Razor
+{
+ [ExportCustomProjectEngineFactory("MVC-1.1", SupportsSerialization = true)]
+ internal class LegacyProjectEngineFactory_1_1 : IProjectEngineFactory
+ {
+ private const string AssemblyName = "Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X";
+
+ public RazorProjectEngine Create(RazorConfiguration configuration, RazorProjectFileSystem fileSystem, Action configure)
+ {
+ // Rewrite the assembly name into a full name just like this one, but with the name of the MVC design time assembly.
+ var assemblyName = new AssemblyName(typeof(LegacyProjectEngineFactory_1_1).Assembly.FullName);
+ assemblyName.Name = AssemblyName;
+
+ var extension = new AssemblyExtension(configuration.ConfigurationName, Assembly.Load(assemblyName));
+ var initializer = extension.CreateInitializer();
+
+ return RazorProjectEngine.Create(configuration, fileSystem, b =>
+ {
+ initializer.Initialize(b);
+ configure?.Invoke(b);
+ });
+ }
+ }
+}
diff --git a/src/Microsoft.VisualStudio.Editor.Razor/LegacyProjectEngineFactory_2_0.cs b/src/Microsoft.VisualStudio.Editor.Razor/LegacyProjectEngineFactory_2_0.cs
new file mode 100644
index 0000000000..ebf9e144f5
--- /dev/null
+++ b/src/Microsoft.VisualStudio.Editor.Razor/LegacyProjectEngineFactory_2_0.cs
@@ -0,0 +1,31 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Reflection;
+using Microsoft.AspNetCore.Razor.Language;
+using Microsoft.CodeAnalysis.Razor;
+
+namespace Microsoft.VisualStudio.Editor.Razor
+{
+ [ExportCustomProjectEngineFactory("MVC-2.0", SupportsSerialization = true)]
+ internal class LegacyProjectEngineFactory_2_0 : IProjectEngineFactory
+ {
+ private const string AssemblyName = "Microsoft.AspNetCore.Mvc.Razor.Extensions";
+ public RazorProjectEngine Create(RazorConfiguration configuration, RazorProjectFileSystem fileSystem, Action configure)
+ {
+ // Rewrite the assembly name into a full name just like this one, but with the name of the MVC design time assembly.
+ var assemblyName = new AssemblyName(typeof(LegacyProjectEngineFactory_2_0).Assembly.FullName);
+ assemblyName.Name = AssemblyName;
+
+ var extension = new AssemblyExtension(configuration.ConfigurationName, Assembly.Load(assemblyName));
+ var initializer = extension.CreateInitializer();
+
+ return RazorProjectEngine.Create(configuration, fileSystem, b =>
+ {
+ initializer.Initialize(b);
+ configure?.Invoke(b);
+ });
+ }
+ }
+}
diff --git a/src/Microsoft.VisualStudio.Editor.Razor/LegacyProjectEngineFactory_2_1.cs b/src/Microsoft.VisualStudio.Editor.Razor/LegacyProjectEngineFactory_2_1.cs
new file mode 100644
index 0000000000..df8d04667c
--- /dev/null
+++ b/src/Microsoft.VisualStudio.Editor.Razor/LegacyProjectEngineFactory_2_1.cs
@@ -0,0 +1,33 @@
+// 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.Reflection;
+using Microsoft.AspNetCore.Razor.Language;
+using Microsoft.CodeAnalysis.Razor;
+
+namespace Microsoft.VisualStudio.Editor.Razor
+{
+ // Currently we provide a fixed configuration for 2.1, but this is a point-in-time issue. We plan
+ // to make the 2.1 configuration more flexible and less hardcoded.
+ [ExportCustomProjectEngineFactory("MVC-2.1", SupportsSerialization = true)]
+ internal class LegacyProjectEngineFactory_2_1 : IProjectEngineFactory
+ {
+ private const string AssemblyName = "Microsoft.AspNetCore.Mvc.Razor.Extensions";
+ public RazorProjectEngine Create(RazorConfiguration configuration, RazorProjectFileSystem fileSystem, Action configure)
+ {
+ // Rewrite the assembly name into a full name just like this one, but with the name of the MVC design time assembly.
+ var assemblyName = new AssemblyName(typeof(LegacyProjectEngineFactory_2_1).Assembly.FullName);
+ assemblyName.Name = AssemblyName;
+
+ var extension = new AssemblyExtension(configuration.ConfigurationName, Assembly.Load(assemblyName));
+ var initializer = extension.CreateInitializer();
+
+ return RazorProjectEngine.Create(configuration, fileSystem, b =>
+ {
+ initializer.Initialize(b);
+ configure?.Invoke(b);
+ });
+ }
+ }
+}
diff --git a/src/Microsoft.VisualStudio.Editor.Razor/Microsoft.VisualStudio.Editor.Razor.csproj b/src/Microsoft.VisualStudio.Editor.Razor/Microsoft.VisualStudio.Editor.Razor.csproj
index 0c367a461e..aa50d39965 100644
--- a/src/Microsoft.VisualStudio.Editor.Razor/Microsoft.VisualStudio.Editor.Razor.csproj
+++ b/src/Microsoft.VisualStudio.Editor.Razor/Microsoft.VisualStudio.Editor.Razor.csproj
@@ -14,8 +14,6 @@
-
-
diff --git a/src/Microsoft.VisualStudio.Editor.Razor/VisualStudioDocumentTracker.cs b/src/Microsoft.VisualStudio.Editor.Razor/VisualStudioDocumentTracker.cs
index efd1016bd1..7cde4da716 100644
--- a/src/Microsoft.VisualStudio.Editor.Razor/VisualStudioDocumentTracker.cs
+++ b/src/Microsoft.VisualStudio.Editor.Razor/VisualStudioDocumentTracker.cs
@@ -6,7 +6,6 @@ using System.Collections.Generic;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Razor.Editor;
-using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
@@ -16,7 +15,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
{
public abstract event EventHandler ContextChanged;
- internal abstract ProjectExtensibilityConfiguration Configuration { get; }
+ public abstract RazorConfiguration Configuration { get; }
public abstract EditorSettings EditorSettings { get; }
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/OOPTagHelperResolver.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/OOPTagHelperResolver.cs
index 857cce5557..c1e2ae56c7 100644
--- a/src/Microsoft.VisualStudio.LanguageServices.Razor/OOPTagHelperResolver.cs
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/OOPTagHelperResolver.cs
@@ -6,6 +6,7 @@ using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Razor;
+using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using Microsoft.VisualStudio.Editor.Razor;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@@ -15,53 +16,67 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
internal class OOPTagHelperResolver : TagHelperResolver
{
private readonly DefaultTagHelperResolver _defaultResolver;
+ private readonly RazorProjectEngineFactoryService _engineFactory;
+ private readonly ErrorReporter _errorReporter;
private readonly Workspace _workspace;
- public OOPTagHelperResolver(Workspace workspace)
+ public OOPTagHelperResolver(RazorProjectEngineFactoryService engineFactory, ErrorReporter errorReporter, Workspace workspace)
{
+ if (engineFactory == null)
+ {
+ throw new ArgumentNullException(nameof(engineFactory));
+ }
+
+ if (errorReporter == null)
+ {
+ throw new ArgumentNullException(nameof(errorReporter));
+ }
+
if (workspace == null)
{
throw new ArgumentNullException(nameof(workspace));
}
+ _engineFactory = engineFactory;
+ _errorReporter = errorReporter;
_workspace = workspace;
- _defaultResolver = new DefaultTagHelperResolver();
+
+ _defaultResolver = new DefaultTagHelperResolver(_engineFactory);
}
- public override async Task GetTagHelpersAsync(Project project, CancellationToken cancellationToken)
+ public override async Task GetTagHelpersAsync(ProjectSnapshot project, CancellationToken cancellationToken = default)
{
if (project == null)
{
throw new ArgumentNullException(nameof(project));
}
+ if (project.Configuration == null || project.WorkspaceProject == null)
+ {
+ return TagHelperResolutionResult.Empty;
+ }
+
+ // Not every custom factory supports the OOP host. Our priority system should work like this:
+ //
+ // 1. Use custom factory out of process
+ // 2. Use custom factory in process
+ // 3. Use fallback factory in process
+ //
+ // Calling into RazorTemplateEngineFactoryService.Create will accomplish #2 and #3 in one step.
+ var factory = _engineFactory.FindSerializableFactory(project);
+
try
{
TagHelperResolutionResult result = null;
-
- // We're being defensive here because the OOP host can return null for the client/session/operation
- // when it's disconnected (user stops the process).
- var client = await RazorLanguageServiceClientFactory.CreateAsync(_workspace, cancellationToken);
- if (client != null)
+ if (factory != null)
{
- using (var session = await client.CreateSessionAsync(project.Solution))
- {
- if (session != null)
- {
- var jsonObject = await session.InvokeAsync(
- "GetTagHelpersAsync",
- new object[] { project.Id.Id, "Foo", },
- cancellationToken).ConfigureAwait(false);
-
- result = GetTagHelperResolutionResult(jsonObject);
- }
- }
+ result = await ResolveTagHelpersOutOfProcessAsync(factory, project);
}
if (result == null)
{
// Was unable to get tag helpers OOP, fallback to default behavior.
- result = await _defaultResolver.GetTagHelpersAsync(project, cancellationToken);
+ result = await ResolveTagHelpersInProcessAsync(project);
}
return result;
@@ -76,11 +91,61 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
}
}
- private TagHelperResolutionResult GetTagHelperResolutionResult(JObject jsonObject)
+ protected virtual async Task ResolveTagHelpersOutOfProcessAsync(IProjectEngineFactory factory, ProjectSnapshot project)
+ {
+ // We're being overly defensive here because the OOP host can return null for the client/session/operation
+ // when it's disconnected (user stops the process).
+ //
+ // This will change in the future to an easier to consume API but for VS RTM this is what we have.
+ try
+ {
+ var client = await RazorLanguageServiceClientFactory.CreateAsync(_workspace, CancellationToken.None);
+ if (client != null)
+ {
+ using (var session = await client.CreateSessionAsync(project.WorkspaceProject.Solution))
+ {
+ if (session != null)
+ {
+ var args = new object[]
+ {
+ Serialize(project),
+ factory == null ? null : factory.GetType().AssemblyQualifiedName,
+ };
+
+ var json = await session.InvokeAsync("GetTagHelpersAsync", args, CancellationToken.None).ConfigureAwait(false);
+ return Deserialize(json);
+ }
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ // We silence exceptions from the OOP host because we don't want to bring down VS for an OOP failure.
+ // We will retry all failures in process anyway, so if there's a real problem that isn't unique to OOP
+ // then it will report a crash in VS.
+ _errorReporter.ReportError(ex, project);
+ }
+
+ return null;
+ }
+
+ protected virtual Task ResolveTagHelpersInProcessAsync(ProjectSnapshot project)
+ {
+ return _defaultResolver.GetTagHelpersAsync(project);
+ }
+
+ private JObject Serialize(ProjectSnapshot snapshot)
{
var serializer = new JsonSerializer();
- serializer.Converters.Add(TagHelperDescriptorJsonConverter.Instance);
- serializer.Converters.Add(RazorDiagnosticJsonConverter.Instance);
+ serializer.Converters.RegisterRazorConverters();
+
+ return JObject.FromObject(snapshot, serializer);
+ }
+
+ private TagHelperResolutionResult Deserialize(JObject jsonObject)
+ {
+ var serializer = new JsonSerializer();
+ serializer.Converters.RegisterRazorConverters();
using (var reader = jsonObject.CreateReader())
{
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/OOPTagHelperResolverFactory.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/OOPTagHelperResolverFactory.cs
index d3265f7778..b6b4e93d52 100644
--- a/src/Microsoft.VisualStudio.LanguageServices.Razor/OOPTagHelperResolverFactory.cs
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/OOPTagHelperResolverFactory.cs
@@ -14,8 +14,10 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
{
public ILanguageService CreateLanguageService(HostLanguageServices languageServices)
{
- var workspace = languageServices.WorkspaceServices.Workspace;
- return new OOPTagHelperResolver(workspace);
+ return new OOPTagHelperResolver(
+ languageServices.GetRequiredService(),
+ languageServices.WorkspaceServices.GetRequiredService(),
+ languageServices.WorkspaceServices.Workspace);
}
}
}
\ No newline at end of file
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/DefaultRazorProjectHost.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/DefaultRazorProjectHost.cs
new file mode 100644
index 0000000000..122505b002
--- /dev/null
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/DefaultRazorProjectHost.cs
@@ -0,0 +1,128 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.ComponentModel.Composition;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Threading.Tasks.Dataflow;
+using Microsoft.AspNetCore.Razor.Language;
+using Microsoft.VisualStudio.LanguageServices;
+using Microsoft.VisualStudio.ProjectSystem;
+
+namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
+{
+ // Somewhat similar to https://github.com/dotnet/project-system/blob/fa074d228dcff6dae9e48ce43dd4a3a5aa22e8f0/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/LanguageServices/LanguageServiceHost.cs
+ //
+ // This class is responsible for intializing the Razor ProjectSnapshotManager for cases where
+ // MSBuild provides configuration support (>= 2.1).
+ [AppliesTo("DotNetCoreRazor & DotNetCoreRazorConfiguration")]
+ [Export(ExportContractNames.Scopes.UnconfiguredProject, typeof(IProjectDynamicLoadComponent))]
+ internal class DefaultRazorProjectHost : RazorProjectHostBase
+ {
+ private IDisposable _subscription;
+
+ [ImportingConstructor]
+ public DefaultRazorProjectHost(
+ IUnconfiguredProjectCommonServices commonServices,
+ [Import(typeof(VisualStudioWorkspace))] Workspace workspace)
+ : base(commonServices, workspace)
+ {
+ }
+
+ // Internal for testing
+ internal DefaultRazorProjectHost(
+ IUnconfiguredProjectCommonServices commonServices,
+ Workspace workspace,
+ ProjectSnapshotManagerBase projectManager)
+ : base(commonServices, workspace, projectManager)
+ {
+ }
+
+ protected override async Task InitializeCoreAsync(CancellationToken cancellationToken)
+ {
+ await base.InitializeCoreAsync(cancellationToken).ConfigureAwait(false);
+
+ // Don't try to evaluate any properties here since the project is still loading and we require access
+ // to the UI thread to push our updates.
+ //
+ // Just subscribe and handle the notification later.
+ // Don't try to evaluate any properties here since the project is still loading and we require access
+ // to the UI thread to push our updates.
+ //
+ // Just subscribe and handle the notification later.
+ var receiver = new ActionBlock>(OnProjectChanged);
+ _subscription = CommonServices.ActiveConfiguredProjectSubscription.JointRuleSource.SourceBlock.LinkTo(
+ receiver,
+ initialDataAsNew: true,
+ suppressVersionOnlyUpdates: true,
+ ruleNames: new string[] { Rules.RazorGeneral.SchemaName, Rules.RazorConfiguration.SchemaName, Rules.RazorExtension.SchemaName });
+ }
+
+ protected override async Task DisposeCoreAsync(bool initialized)
+ {
+ await base.DisposeCoreAsync(initialized).ConfigureAwait(false);
+
+ if (initialized)
+ {
+ _subscription.Dispose();
+ }
+ }
+
+ // Internal for testing
+ internal async Task OnProjectChanged(IProjectVersionedValue update)
+ {
+ if (IsDisposing || IsDisposed)
+ {
+ return;
+ }
+
+ await CommonServices.TasksService.LoadedProjectAsync(async () =>
+ {
+ 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 (!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);
+ });
+ }, registerFaultHandler: true);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/FallbackRazorProjectHost.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/FallbackRazorProjectHost.cs
new file mode 100644
index 0000000000..8e7fb4d493
--- /dev/null
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/FallbackRazorProjectHost.cs
@@ -0,0 +1,146 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.ComponentModel.Composition;
+using System.IO;
+using System.Reflection.Metadata;
+using System.Reflection.PortableExecutable;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Threading.Tasks.Dataflow;
+using Microsoft.VisualStudio.LanguageServices;
+using Microsoft.VisualStudio.ProjectSystem;
+using ResolvedCompilationReference = Microsoft.CodeAnalysis.Razor.ProjectSystem.ManageProjectSystemSchema.ResolvedCompilationReference;
+
+namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
+{
+ // Somewhat similar to https://github.com/dotnet/project-system/blob/fa074d228dcff6dae9e48ce43dd4a3a5aa22e8f0/src/Microsoft.VisualStudio.ProjectSystem.Managed/ProjectSystem/LanguageServices/LanguageServiceHost.cs
+ //
+ // This class is responsible for intializing the Razor ProjectSnapshotManager for cases where
+ // MSBuild does not provides configuration support (SDK < 2.1).
+ [AppliesTo("(DotNetCoreRazor | DotNetCoreWeb) & !DotNetCoreRazorConfiguration")]
+ [Export(ExportContractNames.Scopes.UnconfiguredProject, typeof(IProjectDynamicLoadComponent))]
+ internal class FallbackRazorProjectHost : RazorProjectHostBase
+ {
+ private const string MvcAssemblyName = "Microsoft.AspNetCore.Mvc.Razor";
+ private const string MvcAssemblyFileName = "Microsoft.AspNetCore.Mvc.Razor.dll";
+
+ private IDisposable _subscription;
+
+ [ImportingConstructor]
+ public FallbackRazorProjectHost(
+ IUnconfiguredProjectCommonServices commonServices,
+ [Import(typeof(VisualStudioWorkspace))] Workspace workspace)
+ : base(commonServices, workspace)
+ {
+ }
+
+ // Internal for testing
+ internal FallbackRazorProjectHost(
+ IUnconfiguredProjectCommonServices commonServices,
+ Workspace workspace,
+ ProjectSnapshotManagerBase projectManager)
+ : base(commonServices, workspace, projectManager)
+ {
+ }
+
+ protected override async Task InitializeCoreAsync(CancellationToken cancellationToken)
+ {
+ await base.InitializeCoreAsync(cancellationToken).ConfigureAwait(false);
+
+ // Don't try to evaluate any properties here since the project is still loading and we require access
+ // to the UI thread to push our updates.
+ //
+ // Just subscribe and handle the notification later.
+ var receiver = new ActionBlock>(OnProjectChanged);
+ _subscription = CommonServices.ActiveConfiguredProjectSubscription.JointRuleSource.SourceBlock.LinkTo(
+ receiver,
+ initialDataAsNew: true,
+ suppressVersionOnlyUpdates: true,
+ ruleNames: new string[] { ResolvedCompilationReference.SchemaName },
+ linkOptions: new DataflowLinkOptions() { PropagateCompletion = true });
+ }
+
+ protected override async Task DisposeCoreAsync(bool initialized)
+ {
+ await base.DisposeCoreAsync(initialized).ConfigureAwait(false);
+
+ if (initialized)
+ {
+ _subscription.Dispose();
+ }
+ }
+
+ // Internal for testing
+ internal async Task OnProjectChanged(IProjectVersionedValue update)
+ {
+ if (IsDisposing || IsDisposed)
+ {
+ return;
+ }
+
+ await CommonServices.TasksService.LoadedProjectAsync(async () =>
+ {
+ await ExecuteWithLock(async () =>
+ {
+ 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);
+ });
+ }, registerFaultHandler: true);
+ }
+
+ // virtual for overriding in tests
+ protected virtual Version GetAssemblyVersion(string filePath)
+ {
+ return ReadAssemblyVersion(filePath);
+ }
+
+ private static Version ReadAssemblyVersion(string filePath)
+ {
+ try
+ {
+ using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete))
+ using (var reader = new PEReader(stream))
+ {
+ var metadataReader = reader.GetMetadataReader();
+
+ var assemblyDefinition = metadataReader.GetAssemblyDefinition();
+ return assemblyDefinition.Version;
+ }
+ }
+ catch
+ {
+ // We're purposely silencing any kinds of I/O exceptions here, just in case something wacky is going on.
+ return null;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/IUnconfiguredProjectCommonServices.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/IUnconfiguredProjectCommonServices.cs
new file mode 100644
index 0000000000..f520a6e046
--- /dev/null
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/IUnconfiguredProjectCommonServices.cs
@@ -0,0 +1,32 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using 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; }
+
+ IProjectAsynchronousTasksService TasksService { get; }
+
+ IProjectThreadingService ThreadingService { get; }
+
+ UnconfiguredProject UnconfiguredProject { get; }
+ }
+}
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/ManageProjectSystemSchema.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/ManageProjectSystemSchema.cs
new file mode 100644
index 0000000000..79138c8ac6
--- /dev/null
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/ManageProjectSystemSchema.cs
@@ -0,0 +1,16 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
+{
+ // Well-Known Schema and property names defined by the ManagedProjectSystem
+ internal static class ManageProjectSystemSchema
+ {
+ public static class ResolvedCompilationReference
+ {
+ public static readonly string SchemaName = "ResolvedCompilationReference";
+
+ public static readonly string ItemName = "ResolvedCompilationReference";
+ }
+ }
+}
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/RazorProjectHostBase.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/RazorProjectHostBase.cs
new file mode 100644
index 0000000000..5419eb73af
--- /dev/null
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/RazorProjectHostBase.cs
@@ -0,0 +1,190 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.ComponentModel.Composition;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.LanguageServices;
+using Microsoft.VisualStudio.ProjectSystem;
+using Microsoft.VisualStudio.Threading;
+
+namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
+{
+ internal abstract class RazorProjectHostBase : OnceInitializedOnceDisposedAsync, IProjectDynamicLoadComponent
+ {
+ private readonly Workspace _workspace;
+ private readonly AsyncSemaphore _lock;
+
+ private ProjectSnapshotManagerBase _projectManager;
+ private HostProject _current;
+
+ public RazorProjectHostBase(
+ IUnconfiguredProjectCommonServices commonServices,
+ [Import(typeof(VisualStudioWorkspace))] Workspace workspace)
+ : base(commonServices.ThreadingService.JoinableTaskContext)
+ {
+ if (commonServices == null)
+ {
+ throw new ArgumentNullException(nameof(commonServices));
+ }
+
+ if (workspace == null)
+ {
+ throw new ArgumentNullException(nameof(workspace));
+ }
+
+ CommonServices = commonServices;
+ _workspace = workspace;
+
+ _lock = new AsyncSemaphore(initialCount: 1);
+ }
+
+ // Internal for testing
+ protected RazorProjectHostBase(
+ IUnconfiguredProjectCommonServices commonServices,
+ Workspace workspace,
+ ProjectSnapshotManagerBase projectManager)
+ : base(commonServices.ThreadingService.JoinableTaskContext)
+ {
+ if (commonServices == null)
+ {
+ throw new ArgumentNullException(nameof(commonServices));
+ }
+
+ if (workspace == null)
+ {
+ throw new ArgumentNullException(nameof(workspace));
+ }
+
+ if (projectManager == null)
+ {
+ throw new ArgumentNullException(nameof(projectManager));
+ }
+
+ CommonServices = commonServices;
+ _workspace = workspace;
+ _projectManager = projectManager;
+
+ _lock = new AsyncSemaphore(initialCount: 1);
+ }
+
+ protected IUnconfiguredProjectCommonServices CommonServices { get; }
+
+ // internal for tests. The product will call through the IProjectDynamicLoadComponent interface.
+ internal Task LoadAsync()
+ {
+ return InitializeAsync();
+ }
+
+ protected override Task InitializeCoreAsync(CancellationToken cancellationToken)
+ {
+ CommonServices.UnconfiguredProject.ProjectRenaming += UnconfiguredProject_ProjectRenaming;
+
+ return Task.CompletedTask;
+ }
+
+ protected override async Task DisposeCoreAsync(bool initialized)
+ {
+ if (initialized)
+ {
+ CommonServices.UnconfiguredProject.ProjectRenaming -= UnconfiguredProject_ProjectRenaming;
+
+ await ExecuteWithLock(async () =>
+ {
+ if (_current != null)
+ {
+ await UpdateProjectUnsafeAsync(null).ConfigureAwait(false);
+ }
+ });
+ }
+ }
+
+ // Internal for tests
+ internal async Task OnProjectRenamingAsync()
+ {
+ // When a project gets renamed we expect any rules watched by the derived class to fire.
+ //
+ // However, the project snapshot manager uses the project Fullpath as the key. We want to just
+ // reinitialize the HostProject with the same configuration and settings here, but the updated
+ // FilePath.
+ await ExecuteWithLock(async () =>
+ {
+ if (_current != null)
+ {
+ var old = _current;
+ await UpdateProjectUnsafeAsync(null).ConfigureAwait(false);
+
+ var filePath = CommonServices.UnconfiguredProject.FullPath;
+ await UpdateProjectUnsafeAsync(new HostProject(filePath, old.Configuration)).ConfigureAwait(false);
+ }
+ });
+ }
+
+ // Should only be called from the UI thread.
+ private ProjectSnapshotManagerBase GetProjectManager()
+ {
+ CommonServices.ThreadingService.VerifyOnUIThread();
+
+ if (_projectManager == null)
+ {
+ _projectManager = (ProjectSnapshotManagerBase)_workspace.Services.GetLanguageServices(RazorLanguage.Name).GetRequiredService();
+ }
+
+ return _projectManager;
+ }
+
+ // Must be called inside the lock.
+ protected async Task UpdateProjectUnsafeAsync(HostProject project)
+ {
+ await CommonServices.ThreadingService.SwitchToUIThread();
+ var projectManager = GetProjectManager();
+
+ if (_current == null && project == null)
+ {
+ // This is a no-op. This project isn't using Razor.
+ }
+ else if (_current == null && project != null)
+ {
+ projectManager.HostProjectAdded(project);
+ }
+ else if (_current != null && project == null)
+ {
+ projectManager.HostProjectRemoved(_current);
+ }
+ else
+ {
+ projectManager.HostProjectChanged(project);
+ }
+
+ _current = project;
+ }
+
+ protected async Task ExecuteWithLock(Func func)
+ {
+ using (JoinableCollection.Join())
+ {
+ using (await _lock.EnterAsync().ConfigureAwait(false))
+ {
+ var task = JoinableFactory.RunAsync(func);
+ await task.Task.ConfigureAwait(false);
+ }
+ }
+ }
+
+ Task IProjectDynamicLoadComponent.LoadAsync()
+ {
+ return InitializeAsync();
+ }
+
+ Task IProjectDynamicLoadComponent.UnloadAsync()
+ {
+ return DisposeAsync();
+ }
+
+ private async Task UnconfiguredProject_ProjectRenaming(object sender, ProjectRenamedEventArgs args)
+ {
+ await OnProjectRenamingAsync().ConfigureAwait(false);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/Rules/RazorConfiguration.xaml b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/Rules/RazorConfiguration.xaml
new file mode 100644
index 0000000000..9632054a75
--- /dev/null
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/Rules/RazorConfiguration.xaml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/Rules/RazorExtension.xaml b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/Rules/RazorExtension.xaml
new file mode 100644
index 0000000000..f68eef7107
--- /dev/null
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/Rules/RazorExtension.xaml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/Rules/RazorGeneral.xaml b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/Rules/RazorGeneral.xaml
new file mode 100644
index 0000000000..a2e5150160
--- /dev/null
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/Rules/RazorGeneral.xaml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/Rules/RazorProjectProperties.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/Rules/RazorProjectProperties.cs
index b08397ffb9..cc8f28a6f1 100644
--- a/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/Rules/RazorProjectProperties.cs
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/Rules/RazorProjectProperties.cs
@@ -1,4 +1,4 @@
-// Copyright (c) .NET Foundation. All rights reserved.
+// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.ComponentModel.Composition;
@@ -31,4 +31,4 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem.Rules
{
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/UnconfiguredProjectCommonServices.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/UnconfiguredProjectCommonServices.cs
new file mode 100644
index 0000000000..d84595a1c3
--- /dev/null
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/UnconfiguredProjectCommonServices.cs
@@ -0,0 +1,96 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.ComponentModel.Composition;
+using Microsoft.VisualStudio.ProjectSystem;
+using Microsoft.VisualStudio.ProjectSystem.References;
+
+namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
+{
+ [Export(typeof(IUnconfiguredProjectCommonServices))]
+ internal class UnconfiguredProjectCommonServices : IUnconfiguredProjectCommonServices
+ {
+ private readonly ActiveConfiguredProject _activeConfiguredProject;
+ private readonly ActiveConfiguredProject _activeConfiguredProjectAssemblyReferences;
+ private readonly ActiveConfiguredProject _activeConfiguredProjectPackageReferences;
+ private readonly ActiveConfiguredProject _activeConfiguredProjectProperties;
+
+ [ImportingConstructor]
+ public UnconfiguredProjectCommonServices(
+ [Import(ExportContractNames.Scopes.UnconfiguredProject)] IProjectAsynchronousTasksService tasksService,
+ IProjectThreadingService threadingService,
+ UnconfiguredProject unconfiguredProject,
+ IActiveConfiguredProjectSubscriptionService activeConfiguredProjectSubscription,
+ ActiveConfiguredProject activeConfiguredProject,
+ ActiveConfiguredProject activeConfiguredProjectAssemblyReferences,
+ ActiveConfiguredProject activeConfiguredProjectPackageReferences,
+ ActiveConfiguredProject activeConfiguredProjectRazorProperties)
+ {
+ if (tasksService == null)
+ {
+ throw new ArgumentNullException(nameof(tasksService));
+ }
+
+ 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));
+ }
+
+ TasksService = tasksService;
+ 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 IProjectAsynchronousTasksService TasksService { get; }
+
+ public IProjectThreadingService ThreadingService { get; }
+
+ public UnconfiguredProject UnconfiguredProject { get; }
+ }
+}
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/JsonConverterCollectionExtensions.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/JsonConverterCollectionExtensions.cs
new file mode 100644
index 0000000000..1f008fd63d
--- /dev/null
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/JsonConverterCollectionExtensions.cs
@@ -0,0 +1,27 @@
+// 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.VisualStudio.LanguageServices.Razor.Serialization;
+using Newtonsoft.Json;
+
+namespace Microsoft.CodeAnalysis.Razor
+{
+ internal static class JsonConverterCollectionExtensions
+ {
+ public static void RegisterRazorConverters(this JsonConverterCollection collection)
+ {
+ if (collection == null)
+ {
+ throw new ArgumentNullException(nameof(collection));
+ }
+
+ collection.Add(TagHelperDescriptorJsonConverter.Instance);
+ collection.Add(RazorDiagnosticJsonConverter.Instance);
+ collection.Add(RazorExtensionJsonConverter.Instance);
+ collection.Add(RazorConfigurationJsonConverter.Instance);
+ collection.Add(ProjectSnapshotJsonConverter.Instance);
+ collection.Add(ProjectSnapshotHandleJsonConverter.Instance);
+ }
+ }
+}
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/ProjectSnapshotHandle.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/ProjectSnapshotHandle.cs
new file mode 100644
index 0000000000..d186db2ec8
--- /dev/null
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/ProjectSnapshotHandle.cs
@@ -0,0 +1,29 @@
+// 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 sealed class ProjectSnapshotHandle
+ {
+ public ProjectSnapshotHandle(string filePath, RazorConfiguration configuration, ProjectId workspaceProjectId)
+ {
+ if (filePath == null)
+ {
+ throw new ArgumentNullException(nameof(filePath));
+ }
+
+ FilePath = filePath;
+ Configuration = configuration;
+ WorkspaceProjectId = workspaceProjectId;
+ }
+
+ public RazorConfiguration Configuration { get; }
+
+ public string FilePath { get; }
+
+ public ProjectId WorkspaceProjectId { get; }
+ }
+}
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/ProjectSnapshotHandleJsonConverter.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/ProjectSnapshotHandleJsonConverter.cs
new file mode 100644
index 0000000000..5b72763912
--- /dev/null
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/ProjectSnapshotHandleJsonConverter.cs
@@ -0,0 +1,73 @@
+// 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 Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Razor.ProjectSystem;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace Microsoft.VisualStudio.LanguageServices.Razor.Serialization
+{
+ internal class ProjectSnapshotHandleJsonConverter : JsonConverter
+ {
+ public static readonly ProjectSnapshotHandleJsonConverter Instance = new ProjectSnapshotHandleJsonConverter();
+
+ public override bool CanConvert(Type objectType)
+ {
+ return typeof(ProjectSnapshotHandle).IsAssignableFrom(objectType);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ if (reader.TokenType != JsonToken.StartObject)
+ {
+ return null;
+ }
+
+ var obj = JObject.Load(reader);
+ var filePath = obj[nameof(ProjectSnapshotHandle.FilePath)].Value();
+ var configuration = obj[nameof(ProjectSnapshotHandle.Configuration)].ToObject(serializer);
+
+ var id = obj[nameof(ProjectSnapshotHandle.WorkspaceProjectId)].Value();
+ var workspaceProjectId = id == null ? null : ProjectId.CreateFromSerialized(Guid.Parse(id));
+
+ return new ProjectSnapshotHandle(filePath, configuration, workspaceProjectId);
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var handle = (ProjectSnapshotHandle)value;
+
+ writer.WriteStartObject();
+
+ writer.WritePropertyName(nameof(ProjectSnapshotHandle.FilePath));
+ writer.WriteValue(handle.FilePath);
+
+ if (handle.Configuration == null)
+ {
+ writer.WritePropertyName(nameof(ProjectSnapshotHandle.Configuration));
+ writer.WriteNull();
+ }
+ else
+ {
+ writer.WritePropertyName(nameof(ProjectSnapshotHandle.Configuration));
+ serializer.Serialize(writer, handle.Configuration);
+ }
+
+ if (handle.WorkspaceProjectId == null)
+ {
+ writer.WritePropertyName(nameof(ProjectSnapshotHandle.WorkspaceProjectId));
+ writer.WriteNull();
+ }
+ else
+ {
+ writer.WritePropertyName(nameof(ProjectSnapshotHandle.WorkspaceProjectId));
+ writer.WriteValue(handle.WorkspaceProjectId.Id);
+ }
+
+ writer.WriteEndObject();
+ }
+ }
+}
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/ProjectSnapshotJsonConverter.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/ProjectSnapshotJsonConverter.cs
new file mode 100644
index 0000000000..988d42b44d
--- /dev/null
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/ProjectSnapshotJsonConverter.cs
@@ -0,0 +1,40 @@
+// 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.ProjectSystem;
+using Newtonsoft.Json;
+
+namespace Microsoft.VisualStudio.LanguageServices.Razor.Serialization
+{
+ // We can't truly serialize a snapshot because it has access to a Workspace Project\
+ //
+ // Instead we serialize to a ProjectSnapshotHandle and then use that to re-create the snapshot
+ // inside the remote host.
+ internal class ProjectSnapshotJsonConverter : JsonConverter
+ {
+ public static readonly ProjectSnapshotJsonConverter Instance = new ProjectSnapshotJsonConverter();
+
+ public override bool CanRead => false;
+
+ public override bool CanWrite => true;
+
+ public override bool CanConvert(Type objectType)
+ {
+ return typeof(ProjectSnapshot).IsAssignableFrom(objectType);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ throw new NotSupportedException();
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var project = (ProjectSnapshot)value;
+ var handle = new ProjectSnapshotHandle(project.FilePath, project.Configuration, project.WorkspaceProject?.Id);
+
+ ProjectSnapshotHandleJsonConverter.Instance.WriteJson(writer, handle, serializer);
+ }
+ }
+}
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/RazorConfigurationJsonConverter.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/RazorConfigurationJsonConverter.cs
new file mode 100644
index 0000000000..26be8c52b7
--- /dev/null
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/RazorConfigurationJsonConverter.cs
@@ -0,0 +1,53 @@
+// 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 Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace Microsoft.VisualStudio.LanguageServices.Razor.Serialization
+{
+ internal class RazorConfigurationJsonConverter : JsonConverter
+ {
+ public static readonly RazorConfigurationJsonConverter Instance = new RazorConfigurationJsonConverter();
+
+ public override bool CanConvert(Type objectType)
+ {
+ return typeof(RazorConfiguration).IsAssignableFrom(objectType);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ if (reader.TokenType != JsonToken.StartObject)
+ {
+ return null;
+ }
+
+ var obj = JObject.Load(reader);
+ var configurationName = obj[nameof(RazorConfiguration.ConfigurationName)].Value();
+ var languageVersion = obj[nameof(RazorConfiguration.LanguageVersion)].Value();
+ var extensions = obj[nameof(RazorConfiguration.Extensions)].ToObject(serializer);
+
+ return RazorConfiguration.Create(RazorLanguageVersion.Parse(languageVersion), configurationName, extensions);
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var configuration = (RazorConfiguration)value;
+
+ writer.WriteStartObject();
+
+ writer.WritePropertyName(nameof(RazorConfiguration.ConfigurationName));
+ writer.WriteValue(configuration.ConfigurationName);
+
+ writer.WritePropertyName(nameof(RazorConfiguration.LanguageVersion));
+ writer.WriteValue(configuration.LanguageVersion.ToString());
+
+ writer.WritePropertyName(nameof(RazorConfiguration.Extensions));
+ serializer.Serialize(writer, configuration.Extensions);
+
+ writer.WriteEndObject();
+ }
+ }
+}
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/RazorDiagnosticJsonConverter.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/RazorDiagnosticJsonConverter.cs
similarity index 97%
rename from src/Microsoft.VisualStudio.LanguageServices.Razor/RazorDiagnosticJsonConverter.cs
rename to src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/RazorDiagnosticJsonConverter.cs
index 83873949e6..029530c471 100644
--- a/src/Microsoft.VisualStudio.LanguageServices.Razor/RazorDiagnosticJsonConverter.cs
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/RazorDiagnosticJsonConverter.cs
@@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Razor.Language;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
-namespace Microsoft.VisualStudio.LanguageServices.Razor
+namespace Microsoft.VisualStudio.LanguageServices.Razor.Serialization
{
internal class RazorDiagnosticJsonConverter : JsonConverter
{
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/RazorExtensionJsonConverter.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/RazorExtensionJsonConverter.cs
new file mode 100644
index 0000000000..0d84b8c939
--- /dev/null
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/RazorExtensionJsonConverter.cs
@@ -0,0 +1,45 @@
+// 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 Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace Microsoft.VisualStudio.LanguageServices.Razor.Serialization
+{
+ internal class RazorExtensionJsonConverter : JsonConverter
+ {
+ public static readonly RazorExtensionJsonConverter Instance = new RazorExtensionJsonConverter();
+
+ public override bool CanConvert(Type objectType)
+ {
+ return typeof(RazorExtension).IsAssignableFrom(objectType);
+ }
+
+ public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
+ {
+ if (reader.TokenType != JsonToken.StartObject)
+ {
+ return null;
+ }
+
+ var obj = JObject.Load(reader);
+ var extensionName = obj[nameof(RazorExtension.ExtensionName)].Value();
+
+ return new SerializedRazorExtension(extensionName);
+ }
+
+ public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
+ {
+ var extension = (RazorExtension)value;
+
+ writer.WriteStartObject();
+
+ writer.WritePropertyName(nameof(RazorExtension.ExtensionName));
+ writer.WriteValue(extension.ExtensionName);
+
+ writer.WriteEndObject();
+ }
+ }
+}
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/SerializedRazorExtension.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/SerializedRazorExtension.cs
new file mode 100644
index 0000000000..0f0292534f
--- /dev/null
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/SerializedRazorExtension.cs
@@ -0,0 +1,23 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using Microsoft.AspNetCore.Razor.Language;
+
+namespace Microsoft.VisualStudio.LanguageServices.Razor.Serialization
+{
+ internal class SerializedRazorExtension : RazorExtension
+ {
+ public SerializedRazorExtension(string extensionName)
+ {
+ if (extensionName == null)
+ {
+ throw new ArgumentNullException(nameof(extensionName));
+ }
+
+ ExtensionName = extensionName;
+ }
+
+ public override string ExtensionName { get; }
+ }
+}
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/TagHelperDescriptorJsonConverter.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/TagHelperDescriptorJsonConverter.cs
similarity index 99%
rename from src/Microsoft.VisualStudio.LanguageServices.Razor/TagHelperDescriptorJsonConverter.cs
rename to src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/TagHelperDescriptorJsonConverter.cs
index 406ea9a85f..271af9e1fd 100644
--- a/src/Microsoft.VisualStudio.LanguageServices.Razor/TagHelperDescriptorJsonConverter.cs
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/Serialization/TagHelperDescriptorJsonConverter.cs
@@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Razor.Language;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
-namespace Microsoft.VisualStudio.LanguageServices.Razor
+namespace Microsoft.VisualStudio.LanguageServices.Razor.Serialization
{
internal class TagHelperDescriptorJsonConverter : JsonConverter
{
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/VisualStudioErrorReporter.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/VisualStudioErrorReporter.cs
index 8a847f5a3a..766494cf1b 100644
--- a/src/Microsoft.VisualStudio.LanguageServices.Razor/VisualStudioErrorReporter.cs
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/VisualStudioErrorReporter.cs
@@ -4,6 +4,7 @@
using System;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Razor;
+using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using Microsoft.VisualStudio.Shell.Interop;
namespace Microsoft.VisualStudio.LanguageServices.Razor
@@ -40,7 +41,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
}
}
- public override void ReportError(Exception exception, Project project)
+ public override void ReportError(Exception exception, ProjectSnapshot project)
{
if (exception == null)
{
@@ -53,7 +54,25 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
var hr = activityLog.LogEntry(
(uint)__ACTIVITYLOG_ENTRYTYPE.ALE_ERROR,
"Razor Language Services",
- $"Error encountered from project '{project?.Name}':{Environment.NewLine}{exception}");
+ $"Error encountered from project '{project?.FilePath}':{Environment.NewLine}{exception}");
+ ErrorHandler.ThrowOnFailure(hr);
+ }
+ }
+
+ public override void ReportError(Exception exception, Project workspaceProject)
+ {
+ if (exception == null)
+ {
+ return;
+ }
+
+ var activityLog = GetActivityLog();
+ if (activityLog != null)
+ {
+ var hr = activityLog.LogEntry(
+ (uint)__ACTIVITYLOG_ENTRYTYPE.ALE_ERROR,
+ "Razor Language Services",
+ $"Error encountered from project '{workspaceProject?.Name}' '{workspaceProject?.FilePath}':{Environment.NewLine}{exception}");
ErrorHandler.ThrowOnFailure(hr);
}
}
diff --git a/src/Microsoft.VisualStudio.LanguageServices.Razor/VsSolutionUpdatesProjectSnapshotChangeTrigger.cs b/src/Microsoft.VisualStudio.LanguageServices.Razor/VsSolutionUpdatesProjectSnapshotChangeTrigger.cs
index 91297d852b..ae66202b22 100644
--- a/src/Microsoft.VisualStudio.LanguageServices.Razor/VsSolutionUpdatesProjectSnapshotChangeTrigger.cs
+++ b/src/Microsoft.VisualStudio.LanguageServices.Razor/VsSolutionUpdatesProjectSnapshotChangeTrigger.cs
@@ -85,16 +85,14 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
// This gets called when the project has finished building.
public int UpdateProjectCfg_Done(IVsHierarchy pHierProj, IVsCfg pCfgProj, IVsCfg pCfgSln, uint dwAction, int fSuccess, int fCancel)
{
- var projectName = _projectService.GetProjectName(pHierProj);
var projectPath = _projectService.GetProjectPath(pHierProj);
// Get the corresponding roslyn project by matching the project name and the project path.
- foreach (var project in _projectManager.Workspace.CurrentSolution.Projects)
+ foreach (var projectSnapshot in _projectManager.Projects)
{
- if (string.Equals(projectName, project.Name, StringComparison.Ordinal) &&
- string.Equals(projectPath, project.FilePath, StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(projectPath, projectSnapshot.FilePath, StringComparison.OrdinalIgnoreCase))
{
- _projectManager.ProjectBuildComplete(project);
+ _projectManager.HostProjectBuildComplete(projectSnapshot.HostProject);
break;
}
}
diff --git a/src/Microsoft.VisualStudio.Mac.LanguageServices.Razor/ProjectBuildChangeTrigger.cs b/src/Microsoft.VisualStudio.Mac.LanguageServices.Razor/ProjectBuildChangeTrigger.cs
index e874735740..14f0e7515b 100644
--- a/src/Microsoft.VisualStudio.Mac.LanguageServices.Razor/ProjectBuildChangeTrigger.cs
+++ b/src/Microsoft.VisualStudio.Mac.LanguageServices.Razor/ProjectBuildChangeTrigger.cs
@@ -96,16 +96,14 @@ namespace Microsoft.VisualStudio.Mac.LanguageServices.Razor
return;
}
- var projectName = _projectService.GetProjectName(projectItem);
var projectPath = _projectService.GetProjectPath(projectItem);
// Get the corresponding roslyn project by matching the project name and the project path.
- foreach (var project in _projectManager.Workspace.CurrentSolution.Projects)
+ foreach (var projectSnapshot in _projectManager.Projects)
{
- if (string.Equals(projectName, project.Name, StringComparison.Ordinal) &&
- string.Equals(projectPath, project.FilePath, StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(projectPath, projectSnapshot.FilePath, StringComparison.OrdinalIgnoreCase))
{
- _projectManager.ProjectBuildComplete(project);
+ _projectManager.HostProjectBuildComplete(projectSnapshot.HostProject);
break;
}
}
diff --git a/src/Microsoft.VisualStudio.Mac.LanguageServices.Razor/Properties/Resources.Designer.cs b/src/Microsoft.VisualStudio.Mac.LanguageServices.Razor/Properties/Resources.Designer.cs
index 3e486a9d9e..9b9b6fb28f 100644
--- a/src/Microsoft.VisualStudio.Mac.LanguageServices.Razor/Properties/Resources.Designer.cs
+++ b/src/Microsoft.VisualStudio.Mac.LanguageServices.Razor/Properties/Resources.Designer.cs
@@ -52,6 +52,22 @@ namespace Microsoft.VisualStudio.Mac.LanguageServices.Razor
internal static string FormatRazorLanguageServiceProjectError(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("RazorLanguageServiceProjectError"), p0);
+ ///
+ /// Error encountered from project '{0}':
+ /// {1}
+ ///
+ internal static string RazorLanguageServiceProjectSnapshotError
+ {
+ get => GetString("RazorLanguageServiceProjectSnapshotError");
+ }
+
+ ///
+ /// Error encountered from project '{0}':
+ /// {1}
+ ///
+ internal static string FormatRazorLanguageServiceProjectSnapshotError(object p0, object p1)
+ => string.Format(CultureInfo.CurrentCulture, GetString("RazorLanguageServiceProjectSnapshotError"), p0, p1);
+
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
diff --git a/src/Microsoft.VisualStudio.Mac.LanguageServices.Razor/Resources.resx b/src/Microsoft.VisualStudio.Mac.LanguageServices.Razor/Resources.resx
index 9b004fd397..b5fa0a3947 100644
--- a/src/Microsoft.VisualStudio.Mac.LanguageServices.Razor/Resources.resx
+++ b/src/Microsoft.VisualStudio.Mac.LanguageServices.Razor/Resources.resx
@@ -126,4 +126,8 @@
Razor Language Service error encountered from project '{0}'.
+
+ Error encountered from project '{0}':
+{1}
+
\ No newline at end of file
diff --git a/src/Microsoft.VisualStudio.Mac.LanguageServices.Razor/VisualStudioErrorReporter.cs b/src/Microsoft.VisualStudio.Mac.LanguageServices.Razor/VisualStudioErrorReporter.cs
index 5cf0a05c77..6535dfa999 100644
--- a/src/Microsoft.VisualStudio.Mac.LanguageServices.Razor/VisualStudioErrorReporter.cs
+++ b/src/Microsoft.VisualStudio.Mac.LanguageServices.Razor/VisualStudioErrorReporter.cs
@@ -5,6 +5,7 @@ using System;
using System.Diagnostics;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Razor;
+using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using MonoDevelop.Core;
namespace Microsoft.VisualStudio.Mac.LanguageServices.Razor
@@ -36,5 +37,18 @@ namespace Microsoft.VisualStudio.Mac.LanguageServices.Razor
Resources.FormatRazorLanguageServiceProjectError(project?.Name),
exception);
}
+
+ public override void ReportError(Exception exception, ProjectSnapshot project)
+ {
+ if (exception == null)
+ {
+ Debug.Fail("Null exceptions should not be reported.");
+ return;
+ }
+
+ LoggingService.LogError(
+ Resources.FormatRazorLanguageServiceProjectSnapshotError(project?.FilePath, exception),
+ exception);
+ }
}
}
diff --git a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/Microsoft.CodeAnalysis.Razor.Workspaces.Test.csproj b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/Microsoft.CodeAnalysis.Razor.Workspaces.Test.csproj
index d1ad2a9fab..1967009f1f 100644
--- a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/Microsoft.CodeAnalysis.Razor.Workspaces.Test.csproj
+++ b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/Microsoft.CodeAnalysis.Razor.Workspaces.Test.csproj
@@ -19,6 +19,7 @@
+
diff --git a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultProjectExtensibilityConfigurationFactoryTest.cs b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultProjectExtensibilityConfigurationFactoryTest.cs
deleted file mode 100644
index 633ac9f797..0000000000
--- a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultProjectExtensibilityConfigurationFactoryTest.cs
+++ /dev/null
@@ -1,245 +0,0 @@
-// Copyright (c) .NET Foundation. All rights reserved.
-// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-
-using System;
-using Microsoft.AspNetCore.Razor.Language;
-using Xunit;
-
-namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
-{
- public class DefaultProjectExtensibilityConfigurationFactoryTest
- {
- public static TheoryData LanguageVersionMappingData
- {
- get
- {
- return new TheoryData
- {
- { new AssemblyIdentity("Microsoft.AspNetCore.Razor", new Version("1.0.0.0")), RazorLanguageVersion.Version_1_0 },
- { new AssemblyIdentity("Microsoft.AspNetCore.Razor", new Version("1.1.0.0")), RazorLanguageVersion.Version_1_1 },
- { new AssemblyIdentity("Microsoft.AspNetCore.Razor", new Version("2.0.0.0")), RazorLanguageVersion.Version_2_0 },
- { new AssemblyIdentity("Microsoft.AspNetCore.Razor", new Version("2.1.0.0")), RazorLanguageVersion.Version_2_1 },
- };
- }
- }
-
- [Theory]
- [MemberData(nameof(LanguageVersionMappingData))]
- public void GetLanguageVersion_MapsExactVersionsCorrectly(AssemblyIdentity assemblyIdentity, RazorLanguageVersion expectedVersion)
- {
- // Act
- var languageVersion = DefaultProjectExtensibilityConfigurationFactory.GetLanguageVersion(assemblyIdentity);
-
- // Assert
- Assert.Same(expectedVersion, languageVersion);
- }
-
- [Fact]
- public void GetLanguageVersion_MapsFuture_1_0_VersionsCorrectly()
- {
- // Arrange
- var assemblyIdentity = new AssemblyIdentity("Microsoft.AspNetCore.Razor", new Version("1.3.0.0"));
-
- // Act
- var languageVersion = DefaultProjectExtensibilityConfigurationFactory.GetLanguageVersion(assemblyIdentity);
-
- // Assert
- Assert.Same(RazorLanguageVersion.Version_1_1, languageVersion);
- }
-
- [Fact]
- public void GetLanguageVersion_MapsFuture_2_0_VersionsCorrectly()
- {
- // Arrange
- var assemblyIdentity = new AssemblyIdentity("Microsoft.AspNetCore.Razor", new Version("2.3.0.0"));
-
- // Act
- var languageVersion = DefaultProjectExtensibilityConfigurationFactory.GetLanguageVersion(assemblyIdentity);
-
- // Assert
- Assert.Same(RazorLanguageVersion.Latest, languageVersion);
- }
-
- [Theory]
- [InlineData("1.0.0.0", "1.0.0.0")]
- [InlineData("1.1.0.0", "1.1.0.0")]
- [InlineData("2.0.0.0", "2.0.0.0")]
- [InlineData("2.0.2.0", "2.0.2.0")]
- public void GetConfiguration_FindsSupportedConfiguration_ForNewRazor(string razorVersion, string mvcVersion)
- {
- // Arrange
- var references = new AssemblyIdentity[]
- {
- new AssemblyIdentity("Microsoft.AspNetCore.Razor.Language", new Version(razorVersion)),
- new AssemblyIdentity("Microsoft.AspNetCore.Mvc.Razor", new Version(mvcVersion)),
- };
-
- var factory = new DefaultProjectExtensibilityConfigurationFactory();
-
- // Act
- var result = factory.GetConfiguration(references);
-
- // Assert
- var configuration = Assert.IsType(result);
- Assert.Equal(ProjectExtensibilityConfigurationKind.ApproximateMatch, configuration.Kind);
- Assert.Equal(razorVersion, configuration.RazorAssembly.Identity.Version.ToString());
- Assert.Equal(mvcVersion, configuration.MvcAssembly.Identity.Version.ToString());
- }
-
- [Theory]
- [InlineData("1.0.0.0", "1.0.0.0")]
- [InlineData("1.1.0.0", "1.1.0.0")]
- [InlineData("1.9.9.9", "2.0.0.0")] // MVC version is ignored
- public void GetConfiguration_FindsSupportedConfiguration_ForOldRazor(string razorVersion, string mvcVersion)
- {
- // Arrange
- var references = new AssemblyIdentity[]
- {
- new AssemblyIdentity("Microsoft.AspNetCore.Razor", new Version(razorVersion)),
- new AssemblyIdentity("Microsoft.AspNetCore.Mvc.Razor", new Version(mvcVersion)),
- };
-
- var factory = new DefaultProjectExtensibilityConfigurationFactory();
-
- // Act
- var result = factory.GetConfiguration(references);
-
- // Assert
- var configuration = Assert.IsType(result);
- Assert.Equal(ProjectExtensibilityConfigurationKind.ApproximateMatch, configuration.Kind);
- Assert.Equal(razorVersion, configuration.RazorAssembly.Identity.Version.ToString());
- Assert.Equal(mvcVersion, configuration.MvcAssembly.Identity.Version.ToString());
- }
-
- [Fact]
- public void GetConfiguration_RazorVersion_NewAssemblyWinsOverOld()
- {
- // Arrange
- var references = new AssemblyIdentity[]
- {
- new AssemblyIdentity("Microsoft.AspNetCore.Razor", new Version("1.0.0.0")),
- new AssemblyIdentity("Microsoft.AspNetCore.Razor.Language", new Version("2.0.0.0")),
- new AssemblyIdentity("Microsoft.AspNetCore.Mvc.Razor", new Version("2.0.0.0")),
- };
-
- var factory = new DefaultProjectExtensibilityConfigurationFactory();
-
- // Act
- var result = factory.GetConfiguration(references);
-
- // Assert
- var configuration = Assert.IsType(result);
- Assert.Equal(ProjectExtensibilityConfigurationKind.ApproximateMatch, configuration.Kind);
- Assert.Equal("2.0.0.0", configuration.RazorAssembly.Identity.Version.ToString());
- Assert.Equal("2.0.0.0", configuration.MvcAssembly.Identity.Version.ToString());
- }
-
- [Fact]
- public void GetConfiguration_RazorVersion_OldAssemblyIgnoredPastV1()
- {
- // Arrange
- var references = new AssemblyIdentity[]
- {
- new AssemblyIdentity("Microsoft.AspNetCore.Razor", new Version("2.0.0.0")),
- new AssemblyIdentity("Microsoft.AspNetCore.Mvc.Razor", new Version("2.0.0.0")),
- };
-
- var factory = new DefaultProjectExtensibilityConfigurationFactory();
-
- // Act
- var result = factory.GetConfiguration(references);
-
- // Assert
- var configuration = Assert.IsType(result);
- Assert.Equal(ProjectExtensibilityConfigurationKind.Fallback, configuration.Kind);
- Assert.Equal("2.0.0.0", configuration.RazorAssembly.Identity.Version.ToString());
- Assert.Equal("2.0.0.0", configuration.MvcAssembly.Identity.Version.ToString());
- }
-
- [Fact]
- public void GetConfiguration_NoRazorVersion_ChoosesDefault()
- {
- // Arrange
- var references = new AssemblyIdentity[]
- {
- new AssemblyIdentity("Microsoft.AspNetCore.Mvc.Razor", new Version("2.0.0.0")),
- };
-
- var factory = new DefaultProjectExtensibilityConfigurationFactory();
-
- // Act
- var result = factory.GetConfiguration(references);
-
- // Assert
- var configuration = Assert.IsType(result);
- Assert.Equal(ProjectExtensibilityConfigurationKind.Fallback, configuration.Kind);
- Assert.Equal("2.0.0.0", configuration.RazorAssembly.Identity.Version.ToString());
- Assert.Equal("2.0.0.0", configuration.MvcAssembly.Identity.Version.ToString());
- }
-
- [Fact]
- public void GetConfiguration_UnsupportedRazorVersion_ChoosesDefault()
- {
- // Arrange
- var references = new AssemblyIdentity[]
- {
- new AssemblyIdentity("Microsoft.AspNetCore.Razor.Language", new Version("3.0.0.0")),
- new AssemblyIdentity("Microsoft.AspNetCore.Mvc.Razor", new Version("2.0.0.0")),
- };
-
- var factory = new DefaultProjectExtensibilityConfigurationFactory();
-
- // Act
- var result = factory.GetConfiguration(references);
-
- // Assert
- var configuration = Assert.IsType(result);
- Assert.Equal(ProjectExtensibilityConfigurationKind.Fallback, configuration.Kind);
- Assert.Equal("2.0.0.0", configuration.RazorAssembly.Identity.Version.ToString());
- Assert.Equal("2.0.0.0", configuration.MvcAssembly.Identity.Version.ToString());
- }
-
- [Fact]
- public void GetConfiguration_NoMvcVersion_ChoosesDefault()
- {
- // Arrange
- var references = new AssemblyIdentity[]
- {
- new AssemblyIdentity("Microsoft.AspNetCore.Razor.Language", new Version("2.0.0.0")),
- };
-
- var factory = new DefaultProjectExtensibilityConfigurationFactory();
-
- // Act
- var result = factory.GetConfiguration(references);
-
- // Assert
- var configuration = Assert.IsType(result);
- Assert.Equal(ProjectExtensibilityConfigurationKind.Fallback, configuration.Kind);
- Assert.Equal("2.0.0.0", configuration.RazorAssembly.Identity.Version.ToString());
- Assert.Equal("2.0.0.0", configuration.MvcAssembly.Identity.Version.ToString());
- }
-
- [Fact]
- public void GetConfiguration_UnsupportedMvcVersion_ChoosesDefault()
- {
- // Arrange
- var references = new AssemblyIdentity[]
- {
- new AssemblyIdentity("Microsoft.AspNetCore.Razor.Language", new Version("2.0.0.0")),
- new AssemblyIdentity("Microsoft.AspNetCore.Mvc.Razor", new Version("3.0.0.0")),
- };
-
- var factory = new DefaultProjectExtensibilityConfigurationFactory();
-
- // Act
- var result = factory.GetConfiguration(references);
-
- // Assert
- var configuration = Assert.IsType(result);
- Assert.Equal(ProjectExtensibilityConfigurationKind.Fallback, configuration.Kind);
- Assert.Equal("2.0.0.0", configuration.RazorAssembly.Identity.Version.ToString());
- Assert.Equal("2.0.0.0", configuration.MvcAssembly.Identity.Version.ToString());
- }
- }
-}
diff --git a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultProjectSnapshotManagerTest.cs b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultProjectSnapshotManagerTest.cs
deleted file mode 100644
index eda57ab8ba..0000000000
--- a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultProjectSnapshotManagerTest.cs
+++ /dev/null
@@ -1,331 +0,0 @@
-// Copyright (c) .NET Foundation. All rights reserved.
-// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-
-using System.Collections.Generic;
-using System.Linq;
-using Moq;
-using Xunit;
-
-namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
-{
- public class DefaultProjectSnapshotManagerTest
- {
- public DefaultProjectSnapshotManagerTest()
- {
- Workspace = TestWorkspace.Create();
- ProjectManager = new TestProjectSnapshotManager(Enumerable.Empty(), Workspace);
- }
-
- private TestProjectSnapshotManager ProjectManager { get; }
-
- private Workspace Workspace { get; }
-
- [Fact]
- public void ProjectAdded_AddsProject_NotifiesListeners_AndStartsBackgroundWorker()
- {
- // Arrange
- var project = Workspace.CurrentSolution.AddProject("Test", "Test", LanguageNames.CSharp);
-
- // Act
- ProjectManager.ProjectAdded(project);
-
- // Assert
- var snapshot = ProjectManager.GetSnapshot(project.Id);
- Assert.True(snapshot.IsDirty);
-
- Assert.True(ProjectManager.ListenersNotified);
- Assert.True(ProjectManager.WorkerStarted);
- }
-
- [Fact]
- public void ProjectChanged_MadeDirty_RetainsComputedState_NotifiesListeners_AndStartsBackgroundWorker()
- {
- // Arrange
- var project = Workspace.CurrentSolution.AddProject("Test", "Test", LanguageNames.CSharp);
- ProjectManager.ProjectAdded(project);
- ProjectManager.Reset();
-
- // Adding some computed state
- var configuration = Mock.Of();
- ProjectManager.ProjectUpdated(new ProjectSnapshotUpdateContext(project) { Configuration = configuration });
- ProjectManager.Reset();
-
- project = project.WithAssemblyName("Test1"); // Simulate a project change
-
- // Act
- ProjectManager.ProjectChanged(project);
-
- // Assert
- var snapshot = ProjectManager.GetSnapshot(project.Id);
- Assert.True(snapshot.IsDirty);
- Assert.Same(configuration, snapshot.Configuration);
-
- Assert.False(ProjectManager.ListenersNotified);
- Assert.True(ProjectManager.WorkerStarted);
- }
-
- [Fact]
- public void ProjectChanged_BackgroundUpdate_MadeClean_WithSignificantChanges_NotifiesListeners_AndDoesNotStartBackgroundWorker()
- {
- // Arrange
- var project = Workspace.CurrentSolution.AddProject("Test", "Test", LanguageNames.CSharp);
- ProjectManager.ProjectAdded(project);
- ProjectManager.Reset();
-
- var configuration = Mock.Of();
-
- // Act
- ProjectManager.ProjectUpdated(new ProjectSnapshotUpdateContext(project) { Configuration = configuration });
-
- // Assert
- var snapshot = ProjectManager.GetSnapshot(project.Id);
- Assert.False(snapshot.IsDirty);
- Assert.Same(configuration, snapshot.Configuration);
-
- Assert.True(ProjectManager.ListenersNotified);
- Assert.False(ProjectManager.WorkerStarted);
- }
-
- [Fact]
- public void ProjectChanged_BackgroundUpdate_MadeClean_WithoutSignificantChanges_NotifiesListeners_AndDoesNotStartBackgroundWorker()
- {
- // Arrange
- var project = Workspace.CurrentSolution.AddProject("Test", "Test", LanguageNames.CSharp);
- ProjectManager.ProjectAdded(project);
- ProjectManager.Reset();
-
- var configuration = Mock.Of();
- ProjectManager.ProjectUpdated(new ProjectSnapshotUpdateContext(project) { Configuration = configuration });
- ProjectManager.Reset();
-
- project = project.WithAssemblyName("Test1"); // Simulate a project change
- ProjectManager.ProjectChanged(project);
- ProjectManager.Reset();
-
- // Act
- ProjectManager.ProjectUpdated(new ProjectSnapshotUpdateContext(project) { Configuration = configuration });
-
- // Assert
- var snapshot = ProjectManager.GetSnapshot(project.Id);
- Assert.False(snapshot.IsDirty);
- Assert.Same(configuration, snapshot.Configuration);
-
- Assert.False(ProjectManager.ListenersNotified);
- Assert.False(ProjectManager.WorkerStarted);
- }
-
- [Fact]
- public void ProjectChanged_BackgroundUpdate_StillDirty_WithSignificantChanges_NotifiesListeners_AndStartsBackgroundWorker()
- {
- // Arrange
- var project = Workspace.CurrentSolution.AddProject("Test", "Test", LanguageNames.CSharp);
- ProjectManager.ProjectAdded(project);
- ProjectManager.Reset();
-
- var configuration = Mock.Of();
-
- // Compute an update for "Test"
- var update = new ProjectSnapshotUpdateContext(project) { Configuration = configuration };
-
- project = project.WithAssemblyName("Test1"); // Simulate a project change
- ProjectManager.ProjectChanged(project);
- ProjectManager.Reset();
-
- // Act
- ProjectManager.ProjectUpdated(update);
-
- // Assert
- var snapshot = ProjectManager.GetSnapshot(project.Id);
- Assert.True(snapshot.IsDirty);
- Assert.Same(configuration, snapshot.Configuration);
-
- Assert.True(ProjectManager.ListenersNotified);
- Assert.True(ProjectManager.WorkerStarted);
- }
-
- [Fact]
- public void ProjectChanged_BackgroundUpdate_StillDirty_WithoutSignificantChanges_NotifiesListeners_AndStartsBackgroundWorker()
- {
- // Arrange
- var project = Workspace.CurrentSolution.AddProject("Test", "Test", LanguageNames.CSharp);
- ProjectManager.ProjectAdded(project);
- ProjectManager.Reset();
-
- var configuration = Mock.Of();
- ProjectManager.ProjectUpdated(new ProjectSnapshotUpdateContext(project) { Configuration = configuration });
-
- project = project.WithAssemblyName("Test1"); // Simulate a project change
- ProjectManager.ProjectChanged(project);
- ProjectManager.Reset();
-
- // Compute an update for "Test1"
- var update = new ProjectSnapshotUpdateContext(project) { Configuration = configuration };
-
- project = project.WithAssemblyName("Test2"); // Simulate a project change
- ProjectManager.ProjectChanged(project);
- ProjectManager.Reset();
-
- // Act
- ProjectManager.ProjectUpdated(update); // Still dirty because the project changed while computing the update
-
- // Assert
- var snapshot = ProjectManager.GetSnapshot(project.Id);
- Assert.True(snapshot.IsDirty);
- Assert.Same(configuration, snapshot.Configuration);
-
- Assert.False(ProjectManager.ListenersNotified);
- Assert.True(ProjectManager.WorkerStarted);
- }
-
- [Fact]
- public void ProjectChanged_IgnoresUnknownProject()
- {
- // Arrange
- var project = Workspace.CurrentSolution.AddProject("Test", "Test", LanguageNames.CSharp);
-
- // Act
- ProjectManager.ProjectChanged(project);
-
- // Assert
- Assert.Empty(ProjectManager.Projects);
-
- Assert.False(ProjectManager.ListenersNotified);
- Assert.False(ProjectManager.WorkerStarted);
- }
-
- [Fact]
- public void ProjectChanged_WithComputedState_IgnoresUnknownProject()
- {
- // Arrange
- var project = Workspace.CurrentSolution.AddProject("Test", "Test", LanguageNames.CSharp);
-
- // Act
- ProjectManager.ProjectUpdated(new ProjectSnapshotUpdateContext(project));
-
- // Assert
- Assert.Empty(ProjectManager.Projects);
-
- Assert.False(ProjectManager.ListenersNotified);
- Assert.False(ProjectManager.WorkerStarted);
- }
-
- [Fact]
- public void ProjectBuildComplete_KnownProject_NotifiesBackgroundWorker()
- {
- // Arrange
- var project = Workspace.CurrentSolution.AddProject("Test", "Test", LanguageNames.CSharp);
- ProjectManager.ProjectAdded(project);
- ProjectManager.Reset();
-
- // Act
- ProjectManager.ProjectBuildComplete(project);
-
- // Assert
- Assert.False(ProjectManager.ListenersNotified);
- Assert.True(ProjectManager.WorkerStarted);
- }
-
- [Fact]
- public void ProjectBuildComplete_IgnoresUnknownProject()
- {
- // Arrange
- var project = Workspace.CurrentSolution.AddProject("Test", "Test", LanguageNames.CSharp);
-
- // Act
- ProjectManager.ProjectBuildComplete(project);
-
- // Assert
- Assert.Empty(ProjectManager.Projects);
-
- Assert.False(ProjectManager.ListenersNotified);
- Assert.False(ProjectManager.WorkerStarted);
- }
-
- [Fact]
- public void ProjectRemoved_RemovesProject_NotifiesListeners_DoesNotStartBackgroundWorker()
- {
- // Arrange
- var project = Workspace.CurrentSolution.AddProject("Test", "Test", LanguageNames.CSharp);
-
- ProjectManager.ProjectAdded(project);
- ProjectManager.Reset();
-
- // Act
- ProjectManager.ProjectRemoved(project);
-
- // Assert
- Assert.Empty(ProjectManager.Projects);
-
- Assert.True(ProjectManager.ListenersNotified);
- Assert.False(ProjectManager.WorkerStarted);
- }
-
- [Fact]
- public void ProjectRemoved_IgnoresUnknownProject()
- {
- // Arrange
- var project = Workspace.CurrentSolution.AddProject("Test", "Test", LanguageNames.CSharp);
-
- // Act
- ProjectManager.ProjectRemoved(project);
-
- // Assert
- Assert.Empty(ProjectManager.Projects);
-
- Assert.False(ProjectManager.ListenersNotified);
- Assert.False(ProjectManager.WorkerStarted);
- }
-
- [Fact]
- public void ProjectsCleared_RemovesProject_NotifiesListeners_DoesNotStartBackgroundWorker()
- {
- // Arrange
- var project = Workspace.CurrentSolution.AddProject("Test", "Test", LanguageNames.CSharp);
-
- ProjectManager.ProjectAdded(project);
- ProjectManager.Reset();
-
- // Act
- ProjectManager.ProjectsCleared();
-
- // Assert
- Assert.Empty(ProjectManager.Projects);
-
- Assert.True(ProjectManager.ListenersNotified);
- Assert.False(ProjectManager.WorkerStarted);
- }
-
- private class TestProjectSnapshotManager : DefaultProjectSnapshotManager
- {
- public TestProjectSnapshotManager(IEnumerable triggers, Workspace workspace)
- : base(Mock.Of(), Mock.Of(), Mock.Of(), triggers, workspace)
- {
- }
-
- public bool ListenersNotified { get; private set; }
-
- public bool WorkerStarted { get; private set; }
-
- public DefaultProjectSnapshot GetSnapshot(ProjectId id)
- {
- return Projects.Cast().FirstOrDefault(s => s.UnderlyingProject.Id == id);
- }
-
- public void Reset()
- {
- ListenersNotified = false;
- WorkerStarted = false;
- }
-
- protected override void NotifyListeners(ProjectChangeEventArgs e)
- {
- ListenersNotified = true;
- }
-
- protected override void NotifyBackgroundWorker(Project project)
- {
- WorkerStarted = true;
- }
- }
- }
-}
diff --git a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultProjectSnapshotTest.cs b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultProjectSnapshotTest.cs
index e0d04fb0e8..796b1a4e99 100644
--- a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultProjectSnapshotTest.cs
+++ b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultProjectSnapshotTest.cs
@@ -2,12 +2,7 @@
// 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 +10,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);
Assert.Equal(original.TagHelpers, snapshot.TagHelpers);
@@ -37,23 +33,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)
+ var anotherProject = GetWorkspaceProject("Test1");
+ var update = new ProjectSnapshotUpdateContext(original.FilePath, hostProject, anotherProject, original.Version)
{
- Configuration = Mock.Of(),
TagHelpers = Array.Empty(),
};
// 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);
Assert.Same(update.TagHelpers, snapshot.TagHelpers);
}
@@ -61,12 +55,13 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
public void HaveTagHelpersChanged_NoUpdatesToTagHelpers_ReturnsFalse()
{
// Arrange
- var underlyingProject = GetProject("Test1");
- var original = new DefaultProjectSnapshot(underlyingProject);
+ var hostProject = new HostProject("Test1.csproj", RazorConfiguration.Default);
+ var workspaceProject = GetWorkspaceProject("Test1");
+ var original = new DefaultProjectSnapshot(hostProject, workspaceProject);
- var anotherProject = GetProject("Test1");
- var update = new ProjectSnapshotUpdateContext(anotherProject);
- var snapshot = original.WithProjectChange(update);
+ var anotherProject = GetWorkspaceProject("Test1");
+ var update = new ProjectSnapshotUpdateContext("Test1.csproj", hostProject, anotherProject, VersionStamp.Default);
+ var snapshot = original.WithComputedUpdate(update);
// Act
var result = snapshot.HaveTagHelpersChanged(original);
@@ -79,11 +74,12 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
public void HaveTagHelpersChanged_TagHelpersUpdated_ReturnsTrue()
{
// Arrange
- var underlyingProject = GetProject("Test1");
- var original = new DefaultProjectSnapshot(underlyingProject);
+ var hostProject = new HostProject("Test1.csproj", RazorConfiguration.Default);
+ var workspaceProject = GetWorkspaceProject("Test1");
+ var original = new DefaultProjectSnapshot(hostProject, workspaceProject);
- var anotherProject = GetProject("Test1");
- var update = new ProjectSnapshotUpdateContext(anotherProject)
+ var anotherProject = GetWorkspaceProject("Test1");
+ var update = new ProjectSnapshotUpdateContext("Test1.csproj", hostProject, anotherProject, VersionStamp.Default)
{
TagHelpers = new[]
{
@@ -91,7 +87,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
TagHelperDescriptorBuilder.Create("Two", "TestAssembly").Build(),
},
};
- var snapshot = original.WithProjectChange(update);
+ var snapshot = original.WithComputedUpdate(update);
// Act
var result = snapshot.HaveTagHelpersChanged(original);
@@ -100,7 +96,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
Assert.True(result);
}
- private Project GetProject(string name)
+ private Project GetWorkspaceProject(string name)
{
Project project = null;
TestWorkspace.Create(workspace =>
diff --git a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/WorkspaceProjectSnapshotChangeTriggerTest.cs b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/WorkspaceProjectSnapshotChangeTriggerTest.cs
index 293f6e8bf0..86872c08ea 100644
--- a/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/WorkspaceProjectSnapshotChangeTriggerTest.cs
+++ b/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/WorkspaceProjectSnapshotChangeTriggerTest.cs
@@ -14,33 +14,53 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
public WorkspaceProjectSnapshotChangeTriggerTest()
{
- Solution emptySolution = null;
- Project project1 = null;
- Project project2 = null;
- Project project3 = null;
- Solution solutionWithTwoProjects = null;
- Solution solutionWithOneProject = null;
+ Workspace = TestWorkspace.Create();
+ EmptySolution = Workspace.CurrentSolution.GetIsolatedSolution();
- Workspace = TestWorkspace.Create(ws =>
- {
- emptySolution = ws.CurrentSolution.GetIsolatedSolution();
- project1 = ws.CurrentSolution.AddProject("One", "One", LanguageNames.CSharp);
- project2 = project1.Solution.AddProject("Two", "Two", LanguageNames.CSharp);
- solutionWithTwoProjects = project2.Solution;
+ var projectId1 = ProjectId.CreateNewId("One");
+ var projectId2 = ProjectId.CreateNewId("Two");
+ var projectId3 = ProjectId.CreateNewId("Three");
- project3 = emptySolution.GetIsolatedSolution().AddProject("Three", "Three", LanguageNames.CSharp);
- solutionWithOneProject = project3.Solution;
- });
+ SolutionWithTwoProjects = Workspace.CurrentSolution
+ .AddProject(ProjectInfo.Create(
+ projectId1,
+ VersionStamp.Default,
+ "One",
+ "One",
+ LanguageNames.CSharp,
+ filePath: "One.csproj"))
+ .AddProject(ProjectInfo.Create(
+ projectId2,
+ VersionStamp.Default,
+ "Two",
+ "Two",
+ LanguageNames.CSharp,
+ filePath: "Two.csproj"));
- EmptySolution = emptySolution;
- ProjectNumberOne = project1;
- ProjectNumberTwo = project2;
- ProjectNumberThree = project3;
- SolutionWithTwoProjects = solutionWithTwoProjects;
- SolutionWithOneProject = solutionWithOneProject;
+ SolutionWithOneProject = EmptySolution.GetIsolatedSolution()
+ .AddProject(ProjectInfo.Create(
+ projectId3,
+ VersionStamp.Default,
+ "Three",
+ "Three",
+ LanguageNames.CSharp,
+ filePath: "Three.csproj"));
+ ProjectNumberOne = SolutionWithTwoProjects.GetProject(projectId1);
+ ProjectNumberTwo = SolutionWithTwoProjects.GetProject(projectId2);
+ ProjectNumberThree = SolutionWithOneProject.GetProject(projectId3);
+
+ HostProjectOne = new HostProject("One.csproj", FallbackRazorConfiguration.MVC_1_1);
+ HostProjectTwo = new HostProject("Two.csproj", FallbackRazorConfiguration.MVC_1_1);
+ HostProjectThree = new HostProject("Three.csproj", FallbackRazorConfiguration.MVC_1_1);
}
+ private HostProject HostProjectOne { get; }
+
+ private HostProject HostProjectTwo { get; }
+
+ private HostProject HostProjectThree { get; }
+
private Solution EmptySolution { get; }
private Solution SolutionWithOneProject { get; }
@@ -66,7 +86,9 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Arrange
var trigger = new WorkspaceProjectSnapshotChangeTrigger();
var projectManager = new TestProjectSnapshotManager(new[] { trigger }, Workspace);
-
+ projectManager.HostProjectAdded(HostProjectOne);
+ projectManager.HostProjectAdded(HostProjectTwo);
+
var e = new WorkspaceChangeEventArgs(kind, oldSolution: EmptySolution, newSolution: SolutionWithTwoProjects);
// Act
@@ -74,9 +96,9 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Assert
Assert.Collection(
- projectManager.Projects.OrderBy(p => p.UnderlyingProject.Name),
- p => Assert.Equal(ProjectNumberOne.Id, p.UnderlyingProject.Id),
- p => Assert.Equal(ProjectNumberTwo.Id, p.UnderlyingProject.Id));
+ projectManager.Projects.OrderBy(p => p.WorkspaceProject.Name),
+ p => Assert.Equal(ProjectNumberOne.Id, p.WorkspaceProject.Id),
+ p => Assert.Equal(ProjectNumberTwo.Id, p.WorkspaceProject.Id));
}
[Theory]
@@ -90,21 +112,25 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Arrange
var trigger = new WorkspaceProjectSnapshotChangeTrigger();
var projectManager = new TestProjectSnapshotManager(new[] { trigger }, Workspace);
+ projectManager.HostProjectAdded(HostProjectOne);
+ projectManager.HostProjectAdded(HostProjectTwo);
+ projectManager.HostProjectAdded(HostProjectThree);
// Initialize with a project. This will get removed.
var e = new WorkspaceChangeEventArgs(WorkspaceChangeKind.SolutionAdded, oldSolution: EmptySolution, newSolution: SolutionWithOneProject);
trigger.Workspace_WorkspaceChanged(Workspace, e);
- e = new WorkspaceChangeEventArgs(kind, oldSolution: EmptySolution, newSolution: SolutionWithTwoProjects);
+ e = new WorkspaceChangeEventArgs(kind, oldSolution: SolutionWithOneProject, newSolution: SolutionWithTwoProjects);
// Act
trigger.Workspace_WorkspaceChanged(Workspace, e);
// Assert
Assert.Collection(
- projectManager.Projects.OrderBy(p => p.UnderlyingProject.Name),
- p => Assert.Equal(ProjectNumberOne.Id, p.UnderlyingProject.Id),
- p => Assert.Equal(ProjectNumberTwo.Id, p.UnderlyingProject.Id));
+ projectManager.Projects.OrderBy(p => p.WorkspaceProject?.Name),
+ p => Assert.Null(p.WorkspaceProject),
+ p => Assert.Equal(ProjectNumberOne.Id, p.WorkspaceProject.Id),
+ p => Assert.Equal(ProjectNumberTwo.Id, p.WorkspaceProject.Id));
}
[Theory]
@@ -115,6 +141,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Arrange
var trigger = new WorkspaceProjectSnapshotChangeTrigger();
var projectManager = new TestProjectSnapshotManager(new[] { trigger }, Workspace);
+ projectManager.HostProjectAdded(HostProjectOne);
+ projectManager.HostProjectAdded(HostProjectTwo);
// Initialize with some projects.
var e = new WorkspaceChangeEventArgs(WorkspaceChangeKind.SolutionAdded, oldSolution: EmptySolution, newSolution: SolutionWithTwoProjects);
@@ -128,13 +156,13 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Assert
Assert.Collection(
- projectManager.Projects.OrderBy(p => p.UnderlyingProject.Name),
+ projectManager.Projects.OrderBy(p => p.WorkspaceProject.Name),
p =>
{
- Assert.Equal(ProjectNumberOne.Id, p.UnderlyingProject.Id);
- Assert.Equal("Changed", p.UnderlyingProject.AssemblyName);
+ Assert.Equal(ProjectNumberOne.Id, p.WorkspaceProject.Id);
+ Assert.Equal("Changed", p.WorkspaceProject.AssemblyName);
},
- p => Assert.Equal(ProjectNumberTwo.Id, p.UnderlyingProject.Id));
+ p => Assert.Equal(ProjectNumberTwo.Id, p.WorkspaceProject.Id));
}
[Fact]
@@ -143,6 +171,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Arrange
var trigger = new WorkspaceProjectSnapshotChangeTrigger();
var projectManager = new TestProjectSnapshotManager(new[] { trigger }, Workspace);
+ projectManager.HostProjectAdded(HostProjectOne);
+ projectManager.HostProjectAdded(HostProjectTwo);
// Initialize with some projects project.
var e = new WorkspaceChangeEventArgs(WorkspaceChangeKind.SolutionAdded, oldSolution: EmptySolution, newSolution: SolutionWithTwoProjects);
@@ -156,8 +186,9 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Assert
Assert.Collection(
- projectManager.Projects.OrderBy(p => p.UnderlyingProject.Name),
- p => Assert.Equal(ProjectNumberTwo.Id, p.UnderlyingProject.Id));
+ projectManager.Projects.OrderBy(p => p.WorkspaceProject?.Name),
+ p => Assert.Null(p.WorkspaceProject),
+ p => Assert.Equal(ProjectNumberTwo.Id, p.WorkspaceProject.Id));
}
[Fact]
@@ -166,6 +197,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Arrange
var trigger = new WorkspaceProjectSnapshotChangeTrigger();
var projectManager = new TestProjectSnapshotManager(new[] { trigger }, Workspace);
+ projectManager.HostProjectAdded(HostProjectThree);
var solution = SolutionWithOneProject;
var e = new WorkspaceChangeEventArgs(WorkspaceChangeKind.ProjectAdded, oldSolution: EmptySolution, newSolution: solution, projectId: ProjectNumberThree.Id);
@@ -175,19 +207,21 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Assert
Assert.Collection(
- projectManager.Projects.OrderBy(p => p.UnderlyingProject.Name),
- p => Assert.Equal(ProjectNumberThree.Id, p.UnderlyingProject.Id));
+ projectManager.Projects.OrderBy(p => p.WorkspaceProject.Name),
+ p => Assert.Equal(ProjectNumberThree.Id, p.WorkspaceProject.Id));
}
private class TestProjectSnapshotManager : DefaultProjectSnapshotManager
{
- public TestProjectSnapshotManager(IEnumerable triggers, Workspace workspace)
+ public TestProjectSnapshotManager(IEnumerable triggers, Workspace workspace)
: base(Mock.Of(), Mock.Of(), new TestProjectSnapshotWorker(), triggers, workspace)
{
}
- protected override void NotifyBackgroundWorker(Project project)
+ protected override void NotifyBackgroundWorker(ProjectSnapshotUpdateContext context)
{
+ Assert.NotNull(context.HostProject);
+ Assert.NotNull(context.WorkspaceProject);
}
}
diff --git a/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultImportDocumentManagerIntegrationTest.cs b/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultImportDocumentManagerIntegrationTest.cs
index 5494ba8a4a..7a818f8c54 100644
--- a/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultImportDocumentManagerIntegrationTest.cs
+++ b/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultImportDocumentManagerIntegrationTest.cs
@@ -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 Microsoft.AspNetCore.Mvc.Razor.Extensions;
+using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis.Razor;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using Moq;
@@ -21,7 +23,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
var testImportsPath = "C:\\path\\to\\project\\_ViewImports.cshtml";
var tracker = Mock.Of(t => t.FilePath == filePath && t.ProjectPath == projectPath);
var anotherTracker = Mock.Of(t => t.FilePath == anotherFilePath && t.ProjectPath == projectPath);
- var templateEngineFactoryService = GetProjectEngineFactoryService();
+ var projectEngineFactoryService = GetProjectEngineFactoryService();
var fileChangeTracker = new Mock();
fileChangeTracker.Setup(f => f.FilePath).Returns(testImportsPath);
var fileChangeTrackerFactory = new Mock();
@@ -36,7 +38,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
.Returns(Mock.Of());
var called = false;
- var manager = new DefaultImportDocumentManager(Dispatcher, new DefaultErrorReporter(), fileChangeTrackerFactory.Object, templateEngineFactoryService);
+ var manager = new DefaultImportDocumentManager(Dispatcher, new DefaultErrorReporter(), fileChangeTrackerFactory.Object, projectEngineFactoryService);
manager.OnSubscribed(tracker);
manager.OnSubscribed(anotherTracker);
manager.Changed += (sender, args) =>
@@ -63,7 +65,18 @@ namespace Microsoft.VisualStudio.Editor.Razor
var projectManager = new Mock();
projectManager.Setup(p => p.Projects).Returns(Array.Empty());
- var service = new DefaultProjectEngineFactoryService(projectManager.Object);
+ var projectEngineFactory = new Mock();
+ projectEngineFactory.Setup(s => s.Create(It.IsAny(), It.IsAny(), It.IsAny>()))
+ .Returns>(
+ (c, fs, b) => RazorProjectEngine.Create(
+ RazorConfiguration.Default,
+ fs,
+ builder => RazorExtensions.Register(builder)));
+
+ var service = new DefaultProjectEngineFactoryService(
+ projectManager.Object,
+ projectEngineFactory.Object,
+ new Lazy[0]);
return service;
}
}
diff --git a/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultImportDocumentManagerTest.cs b/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultImportDocumentManagerTest.cs
index c32bbd0af3..0afd8a633a 100644
--- a/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultImportDocumentManagerTest.cs
+++ b/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultImportDocumentManagerTest.cs
@@ -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 Microsoft.AspNetCore.Mvc.Razor.Extensions;
+using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis.Razor;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using Moq;
@@ -18,7 +20,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
var filePath = "C:\\path\\to\\project\\Views\\Home\\file.cshtml";
var projectPath = "C:\\path\\to\\project\\project.csproj";
var tracker = Mock.Of(t => t.FilePath == filePath && t.ProjectPath == projectPath);
- var templateEngineFactoryService = GetTemplateEngineFactoryService();
+ var projectEngineService = GetProjectEngineFactoryService();
var fileChangeTracker1 = new Mock();
fileChangeTracker1.Setup(f => f.StartListening()).Verifiable();
var fileChangeTrackerFactory = new Mock();
@@ -39,7 +41,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
.Returns(fileChangeTracker3.Object)
.Verifiable();
- var manager = new DefaultImportDocumentManager(Dispatcher, new DefaultErrorReporter(), fileChangeTrackerFactory.Object, templateEngineFactoryService);
+ var manager = new DefaultImportDocumentManager(Dispatcher, new DefaultErrorReporter(), fileChangeTrackerFactory.Object, projectEngineService);
// Act
manager.OnSubscribed(tracker);
@@ -58,7 +60,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
var filePath = "C:\\path\\to\\project\\file.cshtml";
var projectPath = "C:\\path\\to\\project\\project.csproj";
var tracker = Mock.Of(t => t.FilePath == filePath && t.ProjectPath == projectPath);
- var templateEngineFactoryService = GetTemplateEngineFactoryService();
+ var projectEngineService = GetProjectEngineFactoryService();
var callCount = 0;
var fileChangeTrackerFactory = new Mock();
@@ -67,7 +69,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
.Returns(Mock.Of())
.Callback(() => callCount++);
- var manager = new DefaultImportDocumentManager(Dispatcher, new DefaultErrorReporter(), fileChangeTrackerFactory.Object, templateEngineFactoryService);
+ var manager = new DefaultImportDocumentManager(Dispatcher, new DefaultErrorReporter(), fileChangeTrackerFactory.Object, projectEngineService);
manager.OnSubscribed(tracker); // Start tracking the import.
var anotherFilePath = "C:\\path\\to\\project\\anotherFile.cshtml";
@@ -87,7 +89,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
var filePath = "C:\\path\\to\\project\\file.cshtml";
var projectPath = "C:\\path\\to\\project\\project.csproj";
var tracker = Mock.Of(t => t.FilePath == filePath && t.ProjectPath == projectPath);
- var templateEngineFactoryService = GetTemplateEngineFactoryService();
+ var projectEngineService = GetProjectEngineFactoryService();
var fileChangeTracker = new Mock();
fileChangeTracker.Setup(f => f.StopListening()).Verifiable();
@@ -97,7 +99,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
.Returns(fileChangeTracker.Object)
.Verifiable();
- var manager = new DefaultImportDocumentManager(Dispatcher, new DefaultErrorReporter(), fileChangeTrackerFactory.Object, templateEngineFactoryService);
+ var manager = new DefaultImportDocumentManager(Dispatcher, new DefaultErrorReporter(), fileChangeTrackerFactory.Object, projectEngineService);
manager.OnSubscribed(tracker); // Start tracking the import.
// Act
@@ -115,7 +117,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
var filePath = "C:\\path\\to\\project\\file.cshtml";
var projectPath = "C:\\path\\to\\project\\project.csproj";
var tracker = Mock.Of(t => t.FilePath == filePath && t.ProjectPath == projectPath);
- var templateEngineFactoryService = GetTemplateEngineFactoryService();
+ var projectEngineService = GetProjectEngineFactoryService();
var fileChangeTracker = new Mock();
fileChangeTracker
@@ -126,7 +128,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
.Setup(f => f.Create(It.IsAny()))
.Returns(fileChangeTracker.Object);
- var manager = new DefaultImportDocumentManager(Dispatcher, new DefaultErrorReporter(), fileChangeTrackerFactory.Object, templateEngineFactoryService);
+ var manager = new DefaultImportDocumentManager(Dispatcher, new DefaultErrorReporter(), fileChangeTrackerFactory.Object, projectEngineService);
manager.OnSubscribed(tracker); // Starts tracking import for the first document.
var anotherFilePath = "C:\\path\\to\\project\\anotherFile.cshtml";
@@ -137,12 +139,23 @@ namespace Microsoft.VisualStudio.Editor.Razor
manager.OnUnsubscribed(tracker);
}
- private RazorProjectEngineFactoryService GetTemplateEngineFactoryService()
+ private RazorProjectEngineFactoryService GetProjectEngineFactoryService()
{
var projectManager = new Mock();
projectManager.Setup(p => p.Projects).Returns(Array.Empty());
- var service = new DefaultProjectEngineFactoryService(projectManager.Object);
+ var projectEngineFactory = new Mock();
+ projectEngineFactory.Setup(s => s.Create(It.IsAny(), It.IsAny(), It.IsAny>()))
+ .Returns>(
+ (c, fs, b) => RazorProjectEngine.Create(
+ RazorConfiguration.Default,
+ fs,
+ builder => RazorExtensions.Register(builder)));
+
+ var service = new DefaultProjectEngineFactoryService(
+ projectManager.Object,
+ projectEngineFactory.Object,
+ new Lazy[0]);
return service;
}
}
diff --git a/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultProjectEngineFactoryServiceTest.cs b/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultProjectEngineFactoryServiceTest.cs
index a9ef8baba3..1afad18177 100644
--- a/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultProjectEngineFactoryServiceTest.cs
+++ b/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultProjectEngineFactoryServiceTest.cs
@@ -3,6 +3,7 @@
using System;
using System.Linq;
+using System.Reflection;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Razor;
@@ -26,30 +27,67 @@ 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);
+
+ HostProject_For_2_1 = new HostProject(
+ "/TestPath/SomePath/Test.csproj",
+ new ProjectSystemRazorConfiguration(RazorLanguageVersion.Version_2_1, "MVC-2.1", Array.Empty()));
+ HostProject_For_UnknownConfiguration = new HostProject(
+ "/TestPath/SomePath/Test.csproj",
+ new ProjectSystemRazorConfiguration(RazorLanguageVersion.Version_2_1, "Blazor-0.1", Array.Empty()));
+
+
+ CustomFactories = new Lazy[]
+ {
+ new Lazy(
+ () => new LegacyProjectEngineFactory_1_0(),
+ typeof(LegacyProjectEngineFactory_1_0).GetCustomAttribute()),
+ new Lazy(
+ () => new LegacyProjectEngineFactory_1_1(),
+ typeof(LegacyProjectEngineFactory_1_1).GetCustomAttribute()),
+ new Lazy(
+ () => new LegacyProjectEngineFactory_2_0(),
+ typeof(LegacyProjectEngineFactory_2_0).GetCustomAttribute()),
+ new Lazy(
+ () => new LegacyProjectEngineFactory_2_1(),
+ typeof(LegacyProjectEngineFactory_2_1).GetCustomAttribute()),
+ };
+
+ FallbackFactory = new FallbackProjectEngineFactory();
}
- // We don't actually look at the project, we rely on the ProjectStateManager
- public Project Project { get; }
+ private Lazy[] CustomFactories { get; }
- public Workspace Workspace { get; }
+ private IFallbackProjectEngineFactory FallbackFactory { get; }
+
+ private HostProject HostProject_For_1_0 { get; }
+
+ private HostProject HostProject_For_1_1 { get; }
+
+ private HostProject HostProject_For_2_0 { get; }
+
+ private HostProject HostProject_For_2_1 { get; }
+
+ private HostProject HostProject_For_UnknownConfiguration { 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()
+ public void Create_CreatesDesignTimeTemplateEngine_ForVersion2_1()
{
// 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_1);
+ projectManager.WorkspaceProjectAdded(WorkspaceProject);
- var factoryService = new DefaultProjectEngineFactoryService(projectManager);
+ var factoryService = new DefaultProjectEngineFactoryService(projectManager, FallbackFactory, CustomFactories);
// Act
var engine = factoryService.Create("/TestPath/SomePath/", b =>
@@ -59,6 +97,30 @@ namespace Microsoft.VisualStudio.Editor.Razor
// Assert
Assert.Single(engine.Engine.Features.OfType());
+ Assert.Single(engine.Engine.Features.OfType());
+ Assert.Single(engine.Engine.Features.OfType());
+ Assert.Single(engine.Engine.Features.OfType());
+ }
+
+ [Fact]
+ public void Create_CreatesDesignTimeTemplateEngine_ForVersion2_0()
+ {
+ // Arrange
+ var projectManager = new TestProjectSnapshotManager(Workspace);
+ projectManager.HostProjectAdded(HostProject_For_2_0);
+ projectManager.WorkspaceProjectAdded(WorkspaceProject);
+
+ var factoryService = new DefaultProjectEngineFactoryService(projectManager, FallbackFactory, CustomFactories);
+
+ // Act
+ var engine = factoryService.Create("/TestPath/SomePath/", b =>
+ {
+ b.Features.Add(new MyCoolNewFeature());
+ });
+
+ // Assert
+ Assert.Single(engine.Engine.Features.OfType());
+ Assert.Single(engine.Engine.Features.OfType());
Assert.Single(engine.Engine.Features.OfType());
Assert.Single(engine.Engine.Features.OfType());
}
@@ -68,17 +130,10 @@ 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);
+ var factoryService = new DefaultProjectEngineFactoryService(projectManager, FallbackFactory, CustomFactories);
// Act
var engine = factoryService.Create("/TestPath/SomePath/", b =>
@@ -88,6 +143,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
// Assert
Assert.Single(engine.Engine.Features.OfType());
+ Assert.Single(engine.Engine.Features.OfType());
Assert.Single(engine.Engine.Features.OfType());
Assert.Single(engine.Engine.Features.OfType());
}
@@ -97,17 +153,10 @@ 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);
+ var factoryService = new DefaultProjectEngineFactoryService(projectManager, FallbackFactory, CustomFactories);
// Act
var engine = factoryService.Create("/TestPath/SomePath/", b =>
@@ -117,26 +166,41 @@ namespace Microsoft.VisualStudio.Editor.Razor
// Assert
Assert.Single(engine.Engine.Features.OfType());
- Assert.Single(engine.Engine.Features.OfType());
- Assert.Empty(engine.Engine.Features.OfType());
+ Assert.Empty(engine.Engine.Features.OfType());
+ Assert.Empty(engine.Engine.Features.OfType());
+ Assert.Empty(engine.Engine.Features.OfType());
}
[Fact]
- public void Create_HigherMvcVersion_UsesLatest()
+ public void Create_UnknownProject_UsesVersion2_0()
{
// Arrange
var projectManager = new TestProjectSnapshotManager(Workspace);
- projectManager.ProjectAdded(Project);
- projectManager.ProjectUpdated(new ProjectSnapshotUpdateContext(Project)
+
+ var factoryService = new DefaultProjectEngineFactoryService(projectManager, FallbackFactory, CustomFactories);
+
+ // Act
+ var engine = factoryService.Create("/TestPath/DifferentPath/", b =>
{
- 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")))),
+ b.Features.Add(new MyCoolNewFeature());
});
- var factoryService = new DefaultProjectEngineFactoryService(projectManager);
+ // Assert
+ Assert.Single(engine.Engine.Features.OfType());
+ Assert.Single(engine.Engine.Features.OfType());
+ Assert.Single(engine.Engine.Features.OfType());
+ Assert.Single(engine.Engine.Features.OfType());
+ }
+
+ [Fact]
+ public void Create_ForUnknownConfiguration_UsesFallbackFactory()
+ {
+ // Arrange
+ var projectManager = new TestProjectSnapshotManager(Workspace);
+ projectManager.HostProjectAdded(HostProject_For_UnknownConfiguration);
+ projectManager.WorkspaceProjectAdded(WorkspaceProject);
+
+ var factoryService = new DefaultProjectEngineFactoryService(projectManager, FallbackFactory, CustomFactories);
// Act
var engine = factoryService.Create("/TestPath/SomePath/", b =>
@@ -146,49 +210,10 @@ namespace Microsoft.VisualStudio.Editor.Razor
// Assert
Assert.Single(engine.Engine.Features.OfType());
- Assert.Single(engine.Engine.Features.OfType());
- Assert.Single(engine.Engine.Features.OfType());
- }
-
- [Fact]
- public void Create_UnknownProjectPath_UsesLatest()
- {
- // Arrange
- var projectManager = new TestProjectSnapshotManager(Workspace);
-
- var factoryService = new DefaultProjectEngineFactoryService(projectManager);
-
- // Act
- var engine = factoryService.Create("/TestPath/DifferentPath/", b =>
- {
- b.Features.Add(new MyCoolNewFeature());
- });
-
- // Assert
- Assert.Single(engine.Engine.Features.OfType());
- Assert.Single(engine.Engine.Features.OfType());
- Assert.Single(engine.Engine.Features.OfType());
- }
-
- [Fact]
- public void Create_MvcReferenceNotFound_UsesLatest()
- {
- // Arrange
- var projectManager = new TestProjectSnapshotManager(Workspace);
- projectManager.ProjectAdded(Project);
-
- var factoryService = new DefaultProjectEngineFactoryService(projectManager);
-
- // Act
- var engine = factoryService.Create("/TestPath/DifferentPath/", b =>
- {
- b.Features.Add(new MyCoolNewFeature());
- });
-
- // Assert
- Assert.Single(engine.Engine.Features.OfType());
- Assert.Single(engine.Engine.Features.OfType());
- Assert.Single(engine.Engine.Features.OfType());
+ Assert.Empty(engine.Engine.Features.OfType());
+ Assert.Empty(engine.Engine.Features.OfType());
+ Assert.Empty(engine.Engine.Features.OfType());
+ Assert.Empty(engine.Engine.Features.OfType());
}
private class MyCoolNewFeature : IRazorEngineFeature
diff --git a/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultTagHelperResolverTest.cs b/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultTagHelperResolverTest.cs
deleted file mode 100644
index 4d872b8b6f..0000000000
--- a/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultTagHelperResolverTest.cs
+++ /dev/null
@@ -1,63 +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.Reflection;
-using Microsoft.AspNetCore.Razor.TagHelpers;
-using Microsoft.CodeAnalysis;
-using Microsoft.CodeAnalysis.CSharp;
-using Xunit;
-
-namespace Microsoft.VisualStudio.Editor.Razor
-{
- public class DefaultTagHelperResolverTest
- {
- private static readonly Assembly Assembly = typeof(DefaultTagHelperResolverTest).GetTypeInfo().Assembly;
-
- [Fact]
- public void GetTagHelpers_DiscoversViewComponentTagHelpers()
- {
- // Arrange
- var code = @"
-public class TestViewComponent
-{
- public string Invoke(string foo, string bar) => null;
-}";
- var syntaxTree = CSharpSyntaxTree.ParseText(code);
- var compilation = TestCompilation.Create(Assembly, syntaxTree);
- var tagHelperResolver = new DefaultTagHelperResolver()
- {
- ForceEnableViewComponentDiscovery = true
- };
-
- // Act
- var result = tagHelperResolver.GetTagHelpers(compilation);
-
- // Assert
- Assert.Empty(result.Diagnostics);
- Assert.Equal(1, result.Descriptors.Count);
- }
-
- [Fact]
- public void GetTagHelpers_DiscoversTagHelpers()
- {
- // Arrange
- var code = $@"
-public class TestTagHelper : {typeof(TagHelper).FullName}
-{{
-}}";
- var syntaxTree = CSharpSyntaxTree.ParseText(code);
- var compilation = TestCompilation.Create(Assembly, syntaxTree);
- var tagHelperResolver = new DefaultTagHelperResolver()
- {
- ForceEnableViewComponentDiscovery = true
- };
-
- // Act
- var result = tagHelperResolver.GetTagHelpers(compilation);
-
- // Assert
- Assert.Empty(result.Diagnostics);
- Assert.Equal(1, result.Descriptors.Count);
- }
- }
-}
diff --git a/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultVisualStudioDocumentTrackerTest.cs b/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultVisualStudioDocumentTrackerTest.cs
index c37040a3a3..e0c178f772 100644
--- a/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultVisualStudioDocumentTrackerTest.cs
+++ b/test/Microsoft.VisualStudio.Editor.Razor.Test/DefaultVisualStudioDocumentTrackerTest.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
+using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Razor;
using Microsoft.CodeAnalysis.Razor.Editor;
@@ -64,7 +65,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
});
var documentTracker = new DefaultVisualStudioDocumentTracker(Dispatcher, FilePath, ProjectPath, ProjectManager, WorkspaceEditorSettings, workspace, TextBuffer, ImportDocumentManager);
- var projectSnapshot = new DefaultProjectSnapshot(project);
+ var projectSnapshot = new DefaultProjectSnapshot(new HostProject(project.FilePath, RazorConfiguration.Default), project);
var projectChangedArgs = new ProjectChangeEventArgs(projectSnapshot, ProjectChangeKind.Changed);
var called = false;
@@ -92,7 +93,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
});
var documentTracker = new DefaultVisualStudioDocumentTracker(Dispatcher, FilePath, ProjectPath, ProjectManager, WorkspaceEditorSettings, workspace, TextBuffer, ImportDocumentManager);
- var projectSnapshot = new DefaultProjectSnapshot(project);
+ var projectSnapshot = new DefaultProjectSnapshot(new HostProject(project.FilePath, RazorConfiguration.Default), project);
var projectChangedArgs = new ProjectChangeEventArgs(projectSnapshot, ProjectChangeKind.TagHelpersChanged);
var called = false;
@@ -120,7 +121,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
});
var documentTracker = new DefaultVisualStudioDocumentTracker(Dispatcher, FilePath, ProjectPath, ProjectManager, WorkspaceEditorSettings, workspace, TextBuffer, ImportDocumentManager);
- var projectSnapshot = new DefaultProjectSnapshot(project);
+ var projectSnapshot = new DefaultProjectSnapshot(new HostProject(project.FilePath, RazorConfiguration.Default), project);
var projectChangedArgs = new ProjectChangeEventArgs(projectSnapshot, ProjectChangeKind.Changed);
var called = false;
diff --git a/test/Microsoft.VisualStudio.Editor.Razor.Test/Microsoft.VisualStudio.Editor.Razor.Test.csproj b/test/Microsoft.VisualStudio.Editor.Razor.Test/Microsoft.VisualStudio.Editor.Razor.Test.csproj
index bd3366db65..d7a967f2b8 100644
--- a/test/Microsoft.VisualStudio.Editor.Razor.Test/Microsoft.VisualStudio.Editor.Razor.Test.csproj
+++ b/test/Microsoft.VisualStudio.Editor.Razor.Test/Microsoft.VisualStudio.Editor.Razor.Test.csproj
@@ -15,6 +15,8 @@
+
+
diff --git a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Microsoft.VisualStudio.LanguageServices.Razor.Test.csproj b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Microsoft.VisualStudio.LanguageServices.Razor.Test.csproj
index d182c95d31..1f76acc35b 100644
--- a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Microsoft.VisualStudio.LanguageServices.Razor.Test.csproj
+++ b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/Microsoft.VisualStudio.LanguageServices.Razor.Test.csproj
@@ -18,6 +18,8 @@
+
+
diff --git a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/OOPTagHelperResolverTest.cs b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/OOPTagHelperResolverTest.cs
new file mode 100644
index 0000000000..cb9a4e8a6b
--- /dev/null
+++ b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/OOPTagHelperResolverTest.cs
@@ -0,0 +1,177 @@
+// 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.Reflection;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Razor.Language;
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Razor;
+using Microsoft.CodeAnalysis.Razor.ProjectSystem;
+using Microsoft.VisualStudio.Editor.Razor;
+using Moq;
+using Xunit;
+
+namespace Microsoft.VisualStudio.LanguageServices.Razor
+{
+ public class OOPTagHelperResolverTest
+ {
+ public OOPTagHelperResolverTest()
+ {
+ HostProject_For_2_0 = new HostProject("Test.csproj", FallbackRazorConfiguration.MVC_2_0);
+ HostProject_For_NonSerializableConfiguration = new HostProject(
+ "Test.csproj",
+ new ProjectSystemRazorConfiguration(RazorLanguageVersion.Version_2_1, "Blazor-0.1", Array.Empty()));
+
+ CustomFactories = new Lazy[]
+ {
+ new Lazy(
+ () => new LegacyProjectEngineFactory_2_0(),
+ typeof(LegacyProjectEngineFactory_2_0).GetCustomAttribute()),
+
+ // We don't really use this factory, we just use it to ensure that the call is going to go out of process.
+ new Lazy(
+ () => new LegacyProjectEngineFactory_2_1(),
+ new ExportCustomProjectEngineFactoryAttribute("Blazor-0.1") { SupportsSerialization = false, }),
+ };
+
+ FallbackFactory = new FallbackProjectEngineFactory();
+
+ Workspace = new AdhocWorkspace();
+
+ var info = ProjectInfo.Create(ProjectId.CreateNewId("Test"), VersionStamp.Default, "Test", "Test", LanguageNames.CSharp, filePath: "Test.csproj");
+ WorkspaceProject = Workspace.CurrentSolution.AddProject(info).GetProject(info.Id);
+
+ ErrorReporter = new DefaultErrorReporter();
+ ProjectManager = new TestProjectSnapshotManager(Workspace);
+ EngineFactory = new DefaultProjectEngineFactoryService(ProjectManager, FallbackFactory, CustomFactories);
+ }
+
+ private ErrorReporter ErrorReporter { get; }
+
+ private RazorProjectEngineFactoryService EngineFactory { get; }
+
+ private Lazy[] CustomFactories { get; }
+
+ private IFallbackProjectEngineFactory FallbackFactory { get; }
+
+ private HostProject HostProject_For_2_0 { get; }
+
+ private HostProject HostProject_For_NonSerializableConfiguration { get; }
+
+ private ProjectSnapshotManagerBase ProjectManager { get; }
+
+ private Project WorkspaceProject { get; }
+
+ private Workspace Workspace { get; }
+
+ [Fact]
+ public async Task GetTagHelpersAsync_WithNonInitializedProject_Noops()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject_For_2_0);
+
+ var project = ProjectManager.GetProjectWithFilePath("Test.csproj");
+
+ var resolver = new TestTagHelperResolver(EngineFactory, ErrorReporter, Workspace);
+
+ var result = await resolver.GetTagHelpersAsync(project);
+
+ // Assert
+ Assert.Same(TagHelperResolutionResult.Empty, result);
+ }
+
+ [Fact]
+ public async Task GetTagHelpersAsync_WithSerializableCustomFactory_GoesOutOfProcess()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject_For_2_0);
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProject);
+
+ var project = ProjectManager.GetProjectWithFilePath("Test.csproj");
+
+ var resolver = new TestTagHelperResolver(EngineFactory, ErrorReporter, Workspace)
+ {
+ OnResolveOutOfProcess = (f, p) =>
+ {
+ Assert.Same(CustomFactories[0].Value, f);
+ Assert.Same(project, p);
+
+ return Task.FromResult(TagHelperResolutionResult.Empty);
+ },
+ };
+
+ var result = await resolver.GetTagHelpersAsync(project);
+
+ // Assert
+ Assert.Same(TagHelperResolutionResult.Empty, result);
+ }
+
+ [Fact]
+ public async Task GetTagHelpersAsync_WithNonSerializableCustomFactory_StaysInProcess()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject_For_NonSerializableConfiguration);
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProject);
+
+ var project = ProjectManager.GetProjectWithFilePath("Test.csproj");
+
+ var resolver = new TestTagHelperResolver(EngineFactory, ErrorReporter, Workspace)
+ {
+ OnResolveInProcess = (p) =>
+ {
+ Assert.Same(project, p);
+
+ return Task.FromResult(TagHelperResolutionResult.Empty);
+ },
+ };
+
+ var result = await resolver.GetTagHelpersAsync(project);
+
+ // Assert
+ Assert.Same(TagHelperResolutionResult.Empty, result);
+
+ }
+
+ private class TestTagHelperResolver : OOPTagHelperResolver
+ {
+ public TestTagHelperResolver(RazorProjectEngineFactoryService engineFactory, ErrorReporter errorReporter, Workspace workspace)
+ : base(engineFactory, errorReporter, workspace)
+ {
+ }
+
+ public Func> OnResolveOutOfProcess { get; set; }
+
+ public Func> OnResolveInProcess { get; set; }
+
+ protected override Task ResolveTagHelpersOutOfProcessAsync(IProjectEngineFactory factory, ProjectSnapshot project)
+ {
+ Assert.NotNull(OnResolveOutOfProcess);
+ return OnResolveOutOfProcess(factory, project);
+ }
+
+ protected override Task ResolveTagHelpersInProcessAsync(ProjectSnapshot project)
+ {
+ Assert.NotNull(OnResolveInProcess);
+ return OnResolveInProcess(project);
+ }
+ }
+ private class TestProjectSnapshotManager : DefaultProjectSnapshotManager
+ {
+ public TestProjectSnapshotManager(Workspace workspace)
+ : base(
+ Mock.Of(),
+ Mock.Of(),
+ Mock.Of(),
+ Enumerable.Empty(),
+ workspace)
+ {
+ }
+
+ protected override void NotifyBackgroundWorker(ProjectSnapshotUpdateContext context)
+ {
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/DefaultProjectSnapshotManagerTest.cs b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/DefaultProjectSnapshotManagerTest.cs
new file mode 100644
index 0000000000..ef13312831
--- /dev/null
+++ b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/DefaultProjectSnapshotManagerTest.cs
@@ -0,0 +1,815 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using System.Linq;
+using Moq;
+using Xunit;
+
+namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
+{
+ public class DefaultProjectSnapshotManagerTest : ForegroundDispatcherTestBase
+ {
+ public DefaultProjectSnapshotManagerTest()
+ {
+ HostProject = new HostProject("Test.csproj", FallbackRazorConfiguration.MVC_2_0);
+
+ Workspace = TestWorkspace.Create();
+ ProjectManager = new TestProjectSnapshotManager(Dispatcher, Enumerable.Empty(), Workspace);
+
+ var projectId = ProjectId.CreateNewId("Test");
+ var solution = Workspace.CurrentSolution.AddProject(ProjectInfo.Create(
+ projectId,
+ VersionStamp.Default,
+ "Test",
+ "Test",
+ LanguageNames.CSharp,
+ "Test.csproj"));
+ WorkspaceProject = solution.GetProject(projectId);
+
+ var vbProjectId = ProjectId.CreateNewId("VB");
+ solution = solution.AddProject(ProjectInfo.Create(
+ vbProjectId,
+ VersionStamp.Default,
+ "VB",
+ "VB",
+ LanguageNames.VisualBasic,
+ "VB.vbproj"));
+ VBWorkspaceProject = solution.GetProject(vbProjectId);
+
+ var projectWithoutFilePathId = ProjectId.CreateNewId("NoFile");
+ solution = solution.AddProject(ProjectInfo.Create(
+ projectWithoutFilePathId,
+ VersionStamp.Default,
+ "NoFile",
+ "NoFile",
+ LanguageNames.CSharp));
+ WorkspaceProjectWithoutFilePath = solution.GetProject(projectWithoutFilePathId);
+
+ // Approximates a project with multi-targeting
+ var projectIdWithDifferentTfm = ProjectId.CreateNewId("TestWithDifferentTfm");
+ solution = Workspace.CurrentSolution.AddProject(ProjectInfo.Create(
+ projectIdWithDifferentTfm,
+ VersionStamp.Default,
+ "Test (Different TFM)",
+ "Test",
+ LanguageNames.CSharp,
+ "Test.csproj"));
+ WorkspaceProjectWithDifferentTfm = solution.GetProject(projectIdWithDifferentTfm);
+ }
+
+ private HostProject HostProject { get; }
+
+ private Project WorkspaceProject { get; }
+
+ private Project WorkspaceProjectWithDifferentTfm { get; }
+
+ private Project WorkspaceProjectWithoutFilePath { get; }
+
+ private Project VBWorkspaceProject { get; }
+
+ private TestProjectSnapshotManager ProjectManager { get; }
+
+ private Workspace Workspace { get; }
+
+ [ForegroundFact]
+ public void HostProjectAdded_WithoutWorkspaceProject_NotifiesListeners()
+ {
+ // Arrange
+
+ // Act
+ ProjectManager.HostProjectAdded(HostProject);
+
+ // Assert
+ var snapshot = ProjectManager.GetSnapshot(HostProject);
+ Assert.True(snapshot.IsDirty);
+ Assert.False(snapshot.IsInitialized);
+
+ Assert.True(ProjectManager.ListenersNotified);
+ Assert.False(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void HostProjectAdded_FindsWorkspaceProject_NotifiesListeners_AndStartsBackgroundWorker()
+ {
+ // Arrange
+ Assert.True(Workspace.TryApplyChanges(WorkspaceProject.Solution));
+
+ // Act
+ ProjectManager.HostProjectAdded(HostProject);
+
+ // Assert
+ var snapshot = ProjectManager.GetSnapshot(HostProject);
+ Assert.True(snapshot.IsDirty);
+ Assert.True(snapshot.IsInitialized);
+
+ Assert.True(ProjectManager.ListenersNotified);
+ Assert.True(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void HostProjectChanged_WithoutWorkspaceProject_NotifiesListeners_AndDoesNotStartBackgroundWorker()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.Reset();
+
+ var project = new HostProject(HostProject.FilePath, FallbackRazorConfiguration.MVC_1_0); // Simulate a project change
+
+ // Act
+ ProjectManager.HostProjectChanged(project);
+
+ // Assert
+ var snapshot = ProjectManager.GetSnapshot(HostProject);
+ Assert.True(snapshot.IsDirty);
+ Assert.False(snapshot.IsInitialized);
+
+ Assert.True(ProjectManager.ListenersNotified);
+ Assert.False(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void HostProjectChanged_WithWorkspaceProject_RetainsComputedState_NotifiesListeners_AndStartsBackgroundWorker()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProject);
+ ProjectManager.Reset();
+
+ // Adding some computed state
+ var snapshot = ProjectManager.GetSnapshot(HostProject);
+ var updateContext = snapshot.CreateUpdateContext();
+ ProjectManager.ProjectUpdated(updateContext);
+ ProjectManager.Reset();
+
+ var project = new HostProject(HostProject.FilePath, FallbackRazorConfiguration.MVC_1_0); // Simulate a project change
+
+ // Act
+ ProjectManager.HostProjectChanged(project);
+
+ // Assert
+ snapshot = ProjectManager.GetSnapshot(project);
+ Assert.True(snapshot.IsDirty);
+ Assert.True(snapshot.IsInitialized);
+
+ Assert.True(ProjectManager.ListenersNotified);
+ Assert.True(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void HostProjectChanged_IgnoresUnknownProject()
+ {
+ // Arrange
+
+ // Act
+ ProjectManager.HostProjectChanged(HostProject);
+
+ // Assert
+ Assert.Empty(ProjectManager.Projects);
+
+ Assert.False(ProjectManager.ListenersNotified);
+ Assert.False(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void HostProjectRemoved_RemovesProject_NotifiesListeners()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.Reset();
+
+ // Act
+ ProjectManager.HostProjectRemoved(HostProject);
+
+ // Assert
+ Assert.Empty(ProjectManager.Projects);
+
+ Assert.True(ProjectManager.ListenersNotified);
+ Assert.False(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void ProjectUpdated_WithComputedState_IgnoresUnknownProject()
+ {
+ // Arrange
+
+ // Act
+ ProjectManager.ProjectUpdated(new ProjectSnapshotUpdateContext("Test", HostProject, WorkspaceProject, VersionStamp.Default));
+
+ // Assert
+ Assert.Empty(ProjectManager.Projects);
+
+ Assert.False(ProjectManager.ListenersNotified);
+ Assert.False(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void ProjectUpdated_WhenHostProjectChanged_MadeClean_NotifiesListeners_AndDoesNotStartBackgroundWorker()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProject);
+ ProjectManager.Reset();
+
+ var project = new HostProject(HostProject.FilePath, FallbackRazorConfiguration.MVC_1_0); // Simulate a project change
+ ProjectManager.HostProjectChanged(project);
+ ProjectManager.Reset();
+
+ // Generate the update
+ var snapshot = ProjectManager.GetSnapshot(HostProject);
+ var updateContext = snapshot.CreateUpdateContext();
+
+ // Act
+ ProjectManager.ProjectUpdated(updateContext);
+
+ // Assert
+ snapshot = ProjectManager.GetSnapshot(project);
+ Assert.False(snapshot.IsDirty);
+
+ Assert.True(ProjectManager.ListenersNotified);
+ Assert.False(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void ProjectUpdated_WhenWorkspaceProjectChanged_MadeClean_NotifiesListeners_AndDoesNotStartBackgroundWorker()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProject);
+ ProjectManager.Reset();
+
+ var project = WorkspaceProject.WithAssemblyName("Test1"); // Simulate a project change
+ ProjectManager.WorkspaceProjectChanged(project);
+ ProjectManager.Reset();
+
+ // Generate the update
+ var snapshot = ProjectManager.GetSnapshot(WorkspaceProject);
+ var updateContext = snapshot.CreateUpdateContext();
+
+ // Act
+ ProjectManager.ProjectUpdated(updateContext);
+
+ // Assert
+ snapshot = ProjectManager.GetSnapshot(project);
+ Assert.False(snapshot.IsDirty);
+
+ Assert.True(ProjectManager.ListenersNotified);
+ Assert.False(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void ProjectUpdated_WhenHostProjectChanged_StillDirty_WithSignificantChanges_NotifiesListeners_AndStartsBackgroundWorker()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProject);
+ ProjectManager.Reset();
+
+ // Generate the update
+ var snapshot = ProjectManager.GetSnapshot(HostProject);
+ var updateContext = snapshot.CreateUpdateContext();
+
+ var project = new HostProject(HostProject.FilePath, FallbackRazorConfiguration.MVC_1_0); // Simulate a project change
+ ProjectManager.HostProjectChanged(project);
+ ProjectManager.Reset();
+
+ // Act
+ ProjectManager.ProjectUpdated(updateContext);
+
+ // Assert
+ snapshot = ProjectManager.GetSnapshot(project);
+ Assert.True(snapshot.IsDirty);
+
+ Assert.True(ProjectManager.ListenersNotified);
+ Assert.True(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void WorkspaceProjectChanged_BackgroundUpdate_StillDirty_WithSignificantChanges_NotifiesListeners_AndStartsBackgroundWorker()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProject);
+ ProjectManager.Reset();
+
+ // Generate the update
+ var snapshot = ProjectManager.GetSnapshot(WorkspaceProject);
+ var updateContext = snapshot.CreateUpdateContext();
+
+ var project = WorkspaceProject.WithAssemblyName("Test1"); // Simulate a project change
+ ProjectManager.WorkspaceProjectChanged(project);
+ ProjectManager.Reset();
+
+ // Act
+ ProjectManager.ProjectUpdated(updateContext);
+
+ // Assert
+ snapshot = ProjectManager.GetSnapshot(project);
+ Assert.True(snapshot.IsDirty);
+
+ Assert.True(ProjectManager.ListenersNotified);
+ Assert.True(ProjectManager.WorkerStarted);
+ }
+
+ [Fact(Skip = "We no longer have any background-computed state")]
+ public void ProjectUpdated_WhenHostProjectChanged_StillDirty_WithoutSignificantChanges_DoesNotNotifyListeners_AndStartsBackgroundWorker()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProject);
+ ProjectManager.Reset();
+
+ // Generate an update based on the original state
+ var snapshot = ProjectManager.GetSnapshot(HostProject);
+ var updateContext = snapshot.CreateUpdateContext();
+ ProjectManager.ProjectUpdated(updateContext);
+ ProjectManager.Reset();
+
+ var project = new HostProject(HostProject.FilePath, FallbackRazorConfiguration.MVC_1_0); // Simulate a project change
+ ProjectManager.HostProjectChanged(project);
+ ProjectManager.Reset();
+
+ // Now start computing another update
+ snapshot = ProjectManager.GetSnapshot(HostProject);
+ updateContext = snapshot.CreateUpdateContext();
+
+ project = new HostProject(HostProject.FilePath, FallbackRazorConfiguration.MVC_1_1); // Simulate a project change
+ ProjectManager.HostProjectChanged(project);
+ ProjectManager.Reset();
+
+ // Act
+ ProjectManager.ProjectUpdated(updateContext); // Still dirty because the project changed while computing the update
+
+ // Assert
+ snapshot = ProjectManager.GetSnapshot(project);
+ Assert.True(snapshot.IsDirty);
+
+ Assert.False(ProjectManager.ListenersNotified);
+ Assert.True(ProjectManager.WorkerStarted);
+ }
+
+ [Fact(Skip = "We no longer have any background-computed state")]
+ public void ProjectUpdated_WhenWorkspaceProjectChanged_StillDirty_WithoutSignificantChanges_DoesNotNotifyListeners_AndStartsBackgroundWorker()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProject);
+ ProjectManager.Reset();
+
+ // Generate an update based on the original state
+ var snapshot = ProjectManager.GetSnapshot(HostProject);
+ var updateContext = snapshot.CreateUpdateContext();
+ ProjectManager.ProjectUpdated(updateContext);
+ ProjectManager.Reset();
+
+ var project = WorkspaceProject.WithAssemblyName("Test1"); // Simulate a project change
+ ProjectManager.WorkspaceProjectChanged(project);
+ ProjectManager.Reset();
+
+ // Now start computing another update
+ snapshot = ProjectManager.GetSnapshot(HostProject);
+ updateContext = snapshot.CreateUpdateContext();
+
+ project = project.WithAssemblyName("Test2"); // Simulate a project change
+ ProjectManager.WorkspaceProjectChanged(project);
+ ProjectManager.Reset();
+
+ // Act
+ ProjectManager.ProjectUpdated(updateContext); // Still dirty because the project changed while computing the update
+
+ // Assert
+ snapshot = ProjectManager.GetSnapshot(project);
+ Assert.True(snapshot.IsDirty);
+
+ Assert.False(ProjectManager.ListenersNotified);
+ Assert.True(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void ProjectUpdated_WhenHostProjectRemoved_DiscardsUpdate()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProject);
+ ProjectManager.Reset();
+
+ // Generate the update
+ var snapshot = ProjectManager.GetSnapshot(HostProject);
+ var updateContext = snapshot.CreateUpdateContext();
+
+ ProjectManager.HostProjectRemoved(HostProject);
+ ProjectManager.Reset();
+
+ // Act
+ ProjectManager.ProjectUpdated(updateContext);
+
+ // Assert
+ snapshot = ProjectManager.GetSnapshot(HostProject);
+ Assert.Null(snapshot);
+ }
+
+ [ForegroundFact]
+ public void ProjectUpdated_WhenWorkspaceProjectRemoved_DiscardsUpdate()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProject);
+ ProjectManager.Reset();
+
+ // Generate the update
+ var snapshot = ProjectManager.GetSnapshot(WorkspaceProject);
+ var updateContext = snapshot.CreateUpdateContext();
+
+ ProjectManager.WorkspaceProjectRemoved(WorkspaceProject);
+ ProjectManager.Reset();
+
+ // Act
+ ProjectManager.ProjectUpdated(updateContext);
+
+ // Assert
+ snapshot = ProjectManager.GetSnapshot(WorkspaceProject);
+ Assert.True(snapshot.IsDirty);
+
+ Assert.False(ProjectManager.ListenersNotified);
+ Assert.False(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void ProjectUpdated_BackgroundUpdate_MadeClean_WithSignificantChanges_NotifiesListeners_AndDoesNotStartBackgroundWorker()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProject);
+ ProjectManager.Reset();
+
+ // Generate the update
+ var snapshot = ProjectManager.GetSnapshot(HostProject);
+ var updateContext = snapshot.CreateUpdateContext();
+
+ // Act
+ ProjectManager.ProjectUpdated(updateContext);
+
+ // Assert
+ snapshot = ProjectManager.GetSnapshot(WorkspaceProject);
+ Assert.False(snapshot.IsDirty);
+
+ Assert.True(ProjectManager.ListenersNotified);
+ Assert.False(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void WorkspaceProjectAdded_WithoutHostProject_IgnoresWorkspaceProject()
+ {
+ // Arrange
+
+ // Act
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProject);
+
+ // Assert
+ Assert.Empty(ProjectManager.Projects);
+
+ Assert.False(ProjectManager.ListenersNotified);
+ Assert.False(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void WorkspaceProjectAdded_IgnoresNonCSharpProject()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.Reset();
+
+ // Act
+ ProjectManager.WorkspaceProjectAdded(VBWorkspaceProject);
+
+ // Assert
+ var snapshot = ProjectManager.GetSnapshot(WorkspaceProject);
+ Assert.False(snapshot.IsInitialized);
+
+ Assert.False(ProjectManager.ListenersNotified);
+ Assert.False(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void WorkspaceProjectAdded_IgnoresSecondProjectWithSameFilePath()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProject);
+ ProjectManager.Reset();
+
+ // Act
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProjectWithDifferentTfm);
+
+ // Assert
+ var snapshot = ProjectManager.GetSnapshot(WorkspaceProject);
+ Assert.Same(WorkspaceProject, snapshot.WorkspaceProject);
+
+ Assert.False(ProjectManager.ListenersNotified);
+ Assert.False(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void WorkspaceProjectAdded_IgnoresProjectWithoutFilePath()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.Reset();
+
+ // Act
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProjectWithoutFilePath);
+
+ // Assert
+ var snapshot = ProjectManager.GetSnapshot(WorkspaceProject);
+ Assert.False(snapshot.IsInitialized);
+
+ Assert.False(ProjectManager.ListenersNotified);
+ Assert.False(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void WorkspaceProjectAdded_WithHostProject_NotifiesListenters_AndStartsBackgroundWorker()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.Reset();
+
+ // Act
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProject);
+
+ // Assert
+ var snapshot = ProjectManager.GetSnapshot(WorkspaceProject);
+ Assert.True(snapshot.IsDirty);
+ Assert.True(snapshot.IsInitialized);
+
+ Assert.True(ProjectManager.ListenersNotified);
+ Assert.True(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void WorkspaceProjectChanged_WithoutHostProject_IgnoresWorkspaceProject()
+ {
+ // Arrange
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProject);
+ ProjectManager.Reset();
+
+ var project = WorkspaceProject.WithAssemblyName("Test1"); // Simulate a project change
+
+ // Act
+ ProjectManager.WorkspaceProjectChanged(project);
+
+ // Assert
+ Assert.Empty(ProjectManager.Projects);
+
+ Assert.False(ProjectManager.ListenersNotified);
+ Assert.False(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void WorkspaceProjectChanged_IgnoresNonCSharpProject()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.WorkspaceProjectAdded(VBWorkspaceProject);
+ ProjectManager.Reset();
+
+ var project = VBWorkspaceProject.WithAssemblyName("Test1"); // Simulate a project change
+
+ // Act
+ ProjectManager.WorkspaceProjectChanged(project);
+
+ // Assert
+ var snapshot = ProjectManager.GetSnapshot(WorkspaceProject);
+ Assert.False(snapshot.IsInitialized);
+
+ Assert.False(ProjectManager.ListenersNotified);
+ Assert.False(ProjectManager.WorkerStarted);
+ }
+
+
+ [ForegroundFact]
+ public void WorkspaceProjectChanged_IgnoresProjectWithoutFilePath()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProjectWithoutFilePath);
+ ProjectManager.Reset();
+
+ var project = WorkspaceProjectWithoutFilePath.WithAssemblyName("Test1"); // Simulate a project change
+
+ // Act
+ ProjectManager.WorkspaceProjectChanged(project);
+
+ // Assert
+ var snapshot = ProjectManager.GetSnapshot(WorkspaceProject);
+ Assert.False(snapshot.IsInitialized);
+
+ Assert.False(ProjectManager.ListenersNotified);
+ Assert.False(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void WorkspaceProjectChanged_IgnoresSecondProjectWithSameFilePath()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProject);
+ ProjectManager.Reset();
+
+ // Act
+ ProjectManager.WorkspaceProjectChanged(WorkspaceProjectWithDifferentTfm);
+
+ // Assert
+ var snapshot = ProjectManager.GetSnapshot(WorkspaceProject);
+ Assert.Same(WorkspaceProject, snapshot.WorkspaceProject);
+
+ Assert.False(ProjectManager.ListenersNotified);
+ Assert.False(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void WorkspaceProjectChanged_MadeDirty_RetainsComputedState_NotifiesListeners_AndStartsBackgroundWorker()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProject);
+ ProjectManager.Reset();
+
+ // Generate the update
+ var snapshot = ProjectManager.GetSnapshot(HostProject);
+ var updateContext = snapshot.CreateUpdateContext();
+ ProjectManager.ProjectUpdated(updateContext);
+ ProjectManager.Reset();
+
+ var project = WorkspaceProject.WithAssemblyName("Test1"); // Simulate a project change
+
+ // Act
+ ProjectManager.WorkspaceProjectChanged(project);
+
+ // Assert
+ snapshot = ProjectManager.GetSnapshot(project);
+ Assert.True(snapshot.IsDirty);
+
+ Assert.False(ProjectManager.ListenersNotified);
+ Assert.True(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void WorkspaceProjectRemoved_WithHostProject_DoesNotRemoveProject()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProject);
+ ProjectManager.Reset();
+
+ // Act
+ ProjectManager.WorkspaceProjectRemoved(WorkspaceProject);
+
+ // Assert
+ var snapshot = ProjectManager.GetSnapshot(WorkspaceProject);
+ Assert.True(snapshot.IsDirty);
+ Assert.False(snapshot.IsInitialized);
+
+ Assert.True(ProjectManager.ListenersNotified);
+ Assert.False(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void WorkspaceProjectRemoved_WithHostProject_FallsBackToSecondProject()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProject);
+ ProjectManager.Reset();
+
+ // Sets up a solution where the which has WorkspaceProjectWithDifferentTfm but not WorkspaceProject
+ // This will enable us to fall back and find the WorkspaceProjectWithDifferentTfm
+ Assert.True(Workspace.TryApplyChanges(WorkspaceProjectWithDifferentTfm.Solution));
+
+ // Act
+ ProjectManager.WorkspaceProjectRemoved(WorkspaceProject);
+
+ // Assert
+ var snapshot = ProjectManager.GetSnapshot(WorkspaceProject);
+ Assert.True(snapshot.IsDirty);
+ Assert.True(snapshot.IsInitialized);
+ Assert.Equal(WorkspaceProjectWithDifferentTfm.Id, snapshot.WorkspaceProject.Id);
+
+ Assert.True(ProjectManager.ListenersNotified);
+ Assert.True(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void WorkspaceProjectRemoved_IgnoresSecondProjectWithSameFilePath()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProject);
+ ProjectManager.Reset();
+
+ // Act
+ ProjectManager.WorkspaceProjectRemoved(WorkspaceProjectWithDifferentTfm);
+
+ // Assert
+ var snapshot = ProjectManager.GetSnapshot(WorkspaceProject);
+ Assert.Same(WorkspaceProject, snapshot.WorkspaceProject);
+
+ Assert.False(ProjectManager.ListenersNotified);
+ Assert.False(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void WorkspaceProjectRemoved_IgnoresNonCSharpProject()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.WorkspaceProjectAdded(VBWorkspaceProject);
+ ProjectManager.Reset();
+
+ // Act
+ ProjectManager.WorkspaceProjectRemoved(VBWorkspaceProject);
+
+ // Assert
+ var snapshot = ProjectManager.GetSnapshot(WorkspaceProject);
+ Assert.False(snapshot.IsInitialized);
+
+ Assert.False(ProjectManager.ListenersNotified);
+ Assert.False(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void WorkspaceProjectRemoved_IgnoresProjectWithoutFilePath()
+ {
+ // Arrange
+ ProjectManager.HostProjectAdded(HostProject);
+ ProjectManager.WorkspaceProjectAdded(WorkspaceProjectWithoutFilePath);
+ ProjectManager.Reset();
+
+ // Act
+ ProjectManager.WorkspaceProjectRemoved(WorkspaceProjectWithoutFilePath);
+
+ // Assert
+ var snapshot = ProjectManager.GetSnapshot(WorkspaceProject);
+ Assert.False(snapshot.IsInitialized);
+
+ Assert.False(ProjectManager.ListenersNotified);
+ Assert.False(ProjectManager.WorkerStarted);
+ }
+
+ [ForegroundFact]
+ public void WorkspaceProjectRemoved_IgnoresUnknownProject()
+ {
+ // Arrange
+
+ // Act
+ ProjectManager.WorkspaceProjectRemoved(WorkspaceProject);
+
+ // Assert
+ Assert.Empty(ProjectManager.Projects);
+
+ Assert.False(ProjectManager.ListenersNotified);
+ Assert.False(ProjectManager.WorkerStarted);
+ }
+
+ private class TestProjectSnapshotManager : DefaultProjectSnapshotManager
+ {
+ public TestProjectSnapshotManager(ForegroundDispatcher dispatcher, IEnumerable triggers, Workspace workspace)
+ : base(dispatcher, Mock.Of(), Mock.Of(), triggers, workspace)
+ {
+ }
+
+ public bool ListenersNotified { get; private set; }
+
+ public bool WorkerStarted { get; private set; }
+
+ public DefaultProjectSnapshot GetSnapshot(HostProject hostProject)
+ {
+ return Projects.Cast().FirstOrDefault(s => s.FilePath == hostProject.FilePath);
+ }
+
+ public DefaultProjectSnapshot GetSnapshot(Project workspaceProject)
+ {
+ return Projects.Cast().FirstOrDefault(s => s.FilePath == workspaceProject.FilePath);
+ }
+
+ public void Reset()
+ {
+ ListenersNotified = false;
+ WorkerStarted = false;
+ }
+
+ protected override void NotifyListeners(ProjectChangeEventArgs e)
+ {
+ ListenersNotified = true;
+ }
+
+ protected override void NotifyBackgroundWorker(ProjectSnapshotUpdateContext context)
+ {
+ Assert.NotNull(context.HostProject);
+ Assert.NotNull(context.WorkspaceProject);
+
+ WorkerStarted = true;
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/DefaultRazorProjectHostTest.cs b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/DefaultRazorProjectHostTest.cs
new file mode 100644
index 0000000000..6234bfb557
--- /dev/null
+++ b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/DefaultRazorProjectHostTest.cs
@@ -0,0 +1,456 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Razor.Language;
+using Microsoft.VisualStudio.LanguageServices.Razor;
+using Microsoft.VisualStudio.ProjectSystem;
+using Moq;
+using Xunit;
+
+namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
+{
+ public class DefaultRazorProjectHostTest : ForegroundDispatcherTestBase
+ {
+ public DefaultRazorProjectHostTest()
+ {
+ Workspace = new AdhocWorkspace();
+ ProjectManager = new TestProjectSnapshotManager(Dispatcher, Workspace);
+ }
+
+ private TestProjectSnapshotManager ProjectManager { get; }
+
+ private Workspace Workspace { get; }
+
+ [ForegroundFact]
+ public async Task DefaultRazorProjectHost_ForegroundThread_CreateAndDispose_Succeeds()
+ {
+ // Arrange
+ var services = new TestProjectSystemServices("Test.csproj");
+ var host = new DefaultRazorProjectHost(services, Workspace, ProjectManager);
+
+ // Act & Assert
+ await host.LoadAsync();
+ Assert.Empty(ProjectManager.Projects);
+
+ await host.DisposeAsync();
+ Assert.Empty(ProjectManager.Projects);
+ }
+
+ [ForegroundFact]
+ public async Task DefaultRazorProjectHost_BackgroundThread_CreateAndDispose_Succeeds()
+ {
+ // Arrange
+ var services = new TestProjectSystemServices("Test.csproj");
+ var host = new DefaultRazorProjectHost(services, Workspace, ProjectManager);
+
+ // Act & Assert
+ await Task.Run(async () => await host.LoadAsync());
+ Assert.Empty(ProjectManager.Projects);
+
+ await Task.Run(async () => await host.DisposeAsync());
+ Assert.Empty(ProjectManager.Projects);
+ }
+
+ [ForegroundFact]
+ public async Task OnProjectChanged_ReadsProperties_InitializesProject()
+ {
+ // Arrange
+ var changes = new TestProjectChangeDescription[]
+ {
+ new TestProjectChangeDescription()
+ {
+ RuleName = Rules.RazorGeneral.SchemaName,
+ After = TestProjectRuleSnapshot.CreateProperties(Rules.RazorGeneral.SchemaName, new Dictionary()
+ {
+ { Rules.RazorGeneral.RazorLangVersionProperty, "2.1" },
+ { Rules.RazorGeneral.RazorDefaultConfigurationProperty, "MVC-2.1" },
+ }),
+ },
+ new TestProjectChangeDescription()
+ {
+ RuleName = Rules.RazorConfiguration.SchemaName,
+ After = TestProjectRuleSnapshot.CreateItems(Rules.RazorConfiguration.SchemaName, new Dictionary>()
+ {
+ { "MVC-2.1", new Dictionary() { { "Extensions", "MVC-2.1;Another-Thing" }, } },
+ })
+ },
+ new TestProjectChangeDescription()
+ {
+ RuleName = Rules.RazorExtension.SchemaName,
+ After = TestProjectRuleSnapshot.CreateItems(Rules.RazorExtension.SchemaName, new Dictionary>()
+ {
+ { "MVC-2.1", new Dictionary(){ } },
+ { "Another-Thing", new Dictionary(){ } },
+ })
+ }
+ };
+
+ var services = new TestProjectSystemServices("Test.csproj");
+
+ var host = new DefaultRazorProjectHost(services, Workspace, ProjectManager);
+
+ await Task.Run(async () => await host.LoadAsync());
+ Assert.Empty(ProjectManager.Projects);
+
+ // Act
+ await Task.Run(async () => await host.OnProjectChanged(services.CreateUpdate(changes)));
+
+ // Assert
+ var snapshot = Assert.Single(ProjectManager.Projects);
+ Assert.Equal("Test.csproj", snapshot.FilePath);
+
+ Assert.Equal(RazorLanguageVersion.Version_2_1, snapshot.Configuration.LanguageVersion);
+ Assert.Equal("MVC-2.1", snapshot.Configuration.ConfigurationName);
+ Assert.Collection(
+ snapshot.Configuration.Extensions,
+ e => Assert.Equal("MVC-2.1", e.ExtensionName),
+ e => Assert.Equal("Another-Thing", e.ExtensionName));
+
+ await Task.Run(async () => await host.DisposeAsync());
+ Assert.Empty(ProjectManager.Projects);
+ }
+
+ [ForegroundFact]
+ public async Task OnProjectChanged_NoVersionFound_DoesNotIniatializeProject()
+ {
+ // Arrange
+ var changes = new TestProjectChangeDescription[]
+ {
+ new TestProjectChangeDescription()
+ {
+ RuleName = Rules.RazorGeneral.SchemaName,
+ After = TestProjectRuleSnapshot.CreateProperties(Rules.RazorGeneral.SchemaName, new Dictionary()
+ {
+ { Rules.RazorGeneral.RazorLangVersionProperty, "" },
+ { Rules.RazorGeneral.RazorDefaultConfigurationProperty, "" },
+ }),
+ },
+ new TestProjectChangeDescription()
+ {
+ RuleName = Rules.RazorConfiguration.SchemaName,
+ After = TestProjectRuleSnapshot.CreateItems(Rules.RazorConfiguration.SchemaName, new Dictionary>()
+ {
+ })
+ },
+ new TestProjectChangeDescription()
+ {
+ RuleName = Rules.RazorExtension.SchemaName,
+ After = TestProjectRuleSnapshot.CreateItems(Rules.RazorExtension.SchemaName, new Dictionary>()
+ {
+ })
+ }
+ };
+
+ var services = new TestProjectSystemServices("Test.csproj");
+
+ var host = new DefaultRazorProjectHost(services, Workspace, ProjectManager);
+
+ await Task.Run(async () => await host.LoadAsync());
+ Assert.Empty(ProjectManager.Projects);
+
+ // Act
+ await Task.Run(async () => await host.OnProjectChanged(services.CreateUpdate(changes)));
+
+ // Assert
+ Assert.Empty(ProjectManager.Projects);
+
+ await Task.Run(async () => await host.DisposeAsync());
+ Assert.Empty(ProjectManager.Projects);
+ }
+
+ [ForegroundFact]
+ public async Task OnProjectChanged_UpdateProject_Succeeds()
+ {
+ // Arrange
+ var changes = new TestProjectChangeDescription[]
+ {
+ new TestProjectChangeDescription()
+ {
+ RuleName = Rules.RazorGeneral.SchemaName,
+ After = TestProjectRuleSnapshot.CreateProperties(Rules.RazorGeneral.SchemaName, new Dictionary()
+ {
+ { Rules.RazorGeneral.RazorLangVersionProperty, "2.1" },
+ { Rules.RazorGeneral.RazorDefaultConfigurationProperty, "MVC-2.1" },
+ }),
+ },
+ new TestProjectChangeDescription()
+ {
+ RuleName = Rules.RazorConfiguration.SchemaName,
+ After = TestProjectRuleSnapshot.CreateItems(Rules.RazorConfiguration.SchemaName, new Dictionary>()
+ {
+ { "MVC-2.1", new Dictionary() { { "Extensions", "MVC-2.1;Another-Thing" }, } },
+ })
+ },
+ new TestProjectChangeDescription()
+ {
+ RuleName = Rules.RazorExtension.SchemaName,
+ After = TestProjectRuleSnapshot.CreateItems(Rules.RazorExtension.SchemaName, new Dictionary>()
+ {
+ { "MVC-2.1", new Dictionary(){ } },
+ { "Another-Thing", new Dictionary(){ } },
+ })
+ }
+ };
+
+ var services = new TestProjectSystemServices("Test.csproj");
+
+ var host = new DefaultRazorProjectHost(services, Workspace, ProjectManager);
+
+ await Task.Run(async () => await host.LoadAsync());
+ Assert.Empty(ProjectManager.Projects);
+
+ // Act - 1
+ await Task.Run(async () => await host.OnProjectChanged(services.CreateUpdate(changes)));
+
+ // Assert - 1
+ var snapshot = Assert.Single(ProjectManager.Projects);
+ Assert.Equal("Test.csproj", snapshot.FilePath);
+
+ Assert.Equal(RazorLanguageVersion.Version_2_1, snapshot.Configuration.LanguageVersion);
+ Assert.Equal("MVC-2.1", snapshot.Configuration.ConfigurationName);
+ Assert.Collection(
+ snapshot.Configuration.Extensions,
+ e => Assert.Equal("MVC-2.1", e.ExtensionName),
+ e => Assert.Equal("Another-Thing", e.ExtensionName));
+
+ // Act - 2
+ changes[0].After.SetProperty(Rules.RazorGeneral.RazorLangVersionProperty, "2.0");
+ changes[0].After.SetProperty(Rules.RazorGeneral.RazorDefaultConfigurationProperty, "MVC-2.0");
+ changes[1].After.SetItem("MVC-2.0", new Dictionary() { { "Extensions", "MVC-2.0;Another-Thing" }, });
+ changes[2].After.SetItem("MVC-2.0", new Dictionary());
+
+ await Task.Run(async () => await host.OnProjectChanged(services.CreateUpdate(changes)));
+
+ // Assert - 2
+ snapshot = Assert.Single(ProjectManager.Projects);
+ Assert.Equal("Test.csproj", snapshot.FilePath);
+
+ Assert.Equal(RazorLanguageVersion.Version_2_0, snapshot.Configuration.LanguageVersion);
+ Assert.Equal("MVC-2.0", snapshot.Configuration.ConfigurationName);
+ Assert.Collection(
+ snapshot.Configuration.Extensions,
+ e => Assert.Equal("MVC-2.0", e.ExtensionName),
+ e => Assert.Equal("Another-Thing", e.ExtensionName));
+
+ await Task.Run(async () => await host.DisposeAsync());
+ Assert.Empty(ProjectManager.Projects);
+ }
+
+ [ForegroundFact]
+ public async Task OnProjectChanged_VersionRemoved_DeinitializesProject()
+ {
+ // Arrange
+ var changes = new TestProjectChangeDescription[]
+ {
+ new TestProjectChangeDescription()
+ {
+ RuleName = Rules.RazorGeneral.SchemaName,
+ After = TestProjectRuleSnapshot.CreateProperties(Rules.RazorGeneral.SchemaName, new Dictionary()
+ {
+ { Rules.RazorGeneral.RazorLangVersionProperty, "2.1" },
+ { Rules.RazorGeneral.RazorDefaultConfigurationProperty, "MVC-2.1" },
+ }),
+ },
+ new TestProjectChangeDescription()
+ {
+ RuleName = Rules.RazorConfiguration.SchemaName,
+ After = TestProjectRuleSnapshot.CreateItems(Rules.RazorConfiguration.SchemaName, new Dictionary>()
+ {
+ { "MVC-2.1", new Dictionary() { { "Extensions", "MVC-2.1;Another-Thing" }, } },
+ })
+ },
+ new TestProjectChangeDescription()
+ {
+ RuleName = Rules.RazorExtension.SchemaName,
+ After = TestProjectRuleSnapshot.CreateItems(Rules.RazorExtension.SchemaName, new Dictionary>()
+ {
+ { "MVC-2.1", new Dictionary(){ } },
+ { "Another-Thing", new Dictionary(){ } },
+ })
+ }
+ };
+
+ var services = new TestProjectSystemServices("Test.csproj");
+
+ var host = new DefaultRazorProjectHost(services, Workspace, ProjectManager);
+
+ await Task.Run(async () => await host.LoadAsync());
+ Assert.Empty(ProjectManager.Projects);
+
+ // Act - 1
+ await Task.Run(async () => await host.OnProjectChanged(services.CreateUpdate(changes)));
+
+ // Assert - 1
+ var snapshot = Assert.Single(ProjectManager.Projects);
+ Assert.Equal("Test.csproj", snapshot.FilePath);
+
+ Assert.Equal(RazorLanguageVersion.Version_2_1, snapshot.Configuration.LanguageVersion);
+ Assert.Equal("MVC-2.1", snapshot.Configuration.ConfigurationName);
+ Assert.Collection(
+ snapshot.Configuration.Extensions,
+ e => Assert.Equal("MVC-2.1", e.ExtensionName),
+ e => Assert.Equal("Another-Thing", e.ExtensionName));
+
+ // Act - 2
+ changes[0].After.SetProperty(Rules.RazorGeneral.RazorLangVersionProperty, "");
+ changes[0].After.SetProperty(Rules.RazorGeneral.RazorDefaultConfigurationProperty, "");
+
+ await Task.Run(async () => await host.OnProjectChanged(services.CreateUpdate(changes)));
+
+ // Assert - 2
+ Assert.Empty(ProjectManager.Projects);
+
+ await Task.Run(async () => await host.DisposeAsync());
+ Assert.Empty(ProjectManager.Projects);
+ }
+
+ [ForegroundFact]
+ public async Task OnProjectChanged_AfterDispose_IgnoresUpdate()
+ {
+ // Arrange
+ var changes = new TestProjectChangeDescription[]
+ {
+ new TestProjectChangeDescription()
+ {
+ RuleName = Rules.RazorGeneral.SchemaName,
+ After = TestProjectRuleSnapshot.CreateProperties(Rules.RazorGeneral.SchemaName, new Dictionary()
+ {
+ { Rules.RazorGeneral.RazorLangVersionProperty, "2.1" },
+ { Rules.RazorGeneral.RazorDefaultConfigurationProperty, "MVC-2.1" },
+ }),
+ },
+ new TestProjectChangeDescription()
+ {
+ RuleName = Rules.RazorConfiguration.SchemaName,
+ After = TestProjectRuleSnapshot.CreateItems(Rules.RazorConfiguration.SchemaName, new Dictionary>()
+ {
+ { "MVC-2.1", new Dictionary() { { "Extensions", "MVC-2.1;Another-Thing" }, } },
+ })
+ },
+ new TestProjectChangeDescription()
+ {
+ RuleName = Rules.RazorExtension.SchemaName,
+ After = TestProjectRuleSnapshot.CreateItems(Rules.RazorExtension.SchemaName, new Dictionary>()
+ {
+ { "MVC-2.1", new Dictionary(){ } },
+ { "Another-Thing", new Dictionary(){ } },
+ })
+ }
+ };
+
+ var services = new TestProjectSystemServices("Test.csproj");
+
+ var host = new DefaultRazorProjectHost(services, Workspace, ProjectManager);
+
+ await Task.Run(async () => await host.LoadAsync());
+ Assert.Empty(ProjectManager.Projects);
+
+ // Act - 1
+ await Task.Run(async () => await host.OnProjectChanged(services.CreateUpdate(changes)));
+
+ // Assert - 1
+ var snapshot = Assert.Single(ProjectManager.Projects);
+ Assert.Equal("Test.csproj", snapshot.FilePath);
+
+ Assert.Equal(RazorLanguageVersion.Version_2_1, snapshot.Configuration.LanguageVersion);
+ Assert.Equal("MVC-2.1", snapshot.Configuration.ConfigurationName);
+ Assert.Collection(
+ snapshot.Configuration.Extensions,
+ e => Assert.Equal("MVC-2.1", e.ExtensionName),
+ e => Assert.Equal("Another-Thing", e.ExtensionName));
+
+ // Act - 2
+ await Task.Run(async () => await host.DisposeAsync());
+
+ // Assert - 2
+ Assert.Empty(ProjectManager.Projects);
+
+ // Act - 3
+ changes[0].After.SetProperty(Rules.RazorGeneral.RazorLangVersionProperty, "2.0");
+ changes[0].After.SetProperty(Rules.RazorGeneral.RazorDefaultConfigurationProperty, "MVC-2.0");
+ changes[1].After.SetItem("MVC-2.0", new Dictionary() { { "Extensions", "MVC-2.0;Another-Thing" }, });
+
+ await Task.Run(async () => await host.OnProjectChanged(services.CreateUpdate(changes)));
+
+ // Assert - 3
+ Assert.Empty(ProjectManager.Projects);
+ }
+
+ [ForegroundFact]
+ public async Task OnProjectRenamed_RemovesHostProject_CopiesConfiguration()
+ {
+ // Arrange
+ var changes = new TestProjectChangeDescription[]
+ {
+ new TestProjectChangeDescription()
+ {
+ RuleName = Rules.RazorGeneral.SchemaName,
+ After = TestProjectRuleSnapshot.CreateProperties(Rules.RazorGeneral.SchemaName, new Dictionary()
+ {
+ { Rules.RazorGeneral.RazorLangVersionProperty, "2.1" },
+ { Rules.RazorGeneral.RazorDefaultConfigurationProperty, "MVC-2.1" },
+ }),
+ },
+ new TestProjectChangeDescription()
+ {
+ RuleName = Rules.RazorConfiguration.SchemaName,
+ After = TestProjectRuleSnapshot.CreateItems(Rules.RazorConfiguration.SchemaName, new Dictionary>()
+ {
+ { "MVC-2.1", new Dictionary() { { "Extensions", "MVC-2.1;Another-Thing" }, } },
+ })
+ },
+ new TestProjectChangeDescription()
+ {
+ RuleName = Rules.RazorExtension.SchemaName,
+ After = TestProjectRuleSnapshot.CreateItems(Rules.RazorExtension.SchemaName, new Dictionary>()
+ {
+ { "MVC-2.1", new Dictionary(){ } },
+ { "Another-Thing", new Dictionary(){ } },
+ })
+ }
+ };
+
+ var services = new TestProjectSystemServices("Test.csproj");
+
+ var host = new DefaultRazorProjectHost(services, Workspace, ProjectManager);
+
+ await Task.Run(async () => await host.LoadAsync());
+ Assert.Empty(ProjectManager.Projects);
+
+ // Act - 1
+ await Task.Run(async () => await host.OnProjectChanged(services.CreateUpdate(changes)));
+
+ // Assert - 1
+ var snapshot = Assert.Single(ProjectManager.Projects);
+ Assert.Equal("Test.csproj", snapshot.FilePath);
+ Assert.Same("MVC-2.1", snapshot.Configuration.ConfigurationName);
+
+ // Act - 2
+ services.UnconfiguredProject.FullPath = "Test2.csproj";
+ await Task.Run(async () => await host.OnProjectRenamingAsync());
+
+ // Assert - 1
+ snapshot = Assert.Single(ProjectManager.Projects);
+ Assert.Equal("Test2.csproj", snapshot.FilePath);
+ Assert.Same("MVC-2.1", snapshot.Configuration.ConfigurationName);
+
+ await Task.Run(async () => await host.DisposeAsync());
+ Assert.Empty(ProjectManager.Projects);
+ }
+
+ private class TestProjectSnapshotManager : DefaultProjectSnapshotManager
+ {
+ public TestProjectSnapshotManager(ForegroundDispatcher dispatcher, Workspace workspace)
+ : base(dispatcher, Mock.Of(), Mock.Of(), Array.Empty(), workspace)
+ {
+ }
+
+ protected override void NotifyBackgroundWorker(ProjectSnapshotUpdateContext context)
+ {
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/FallbackRazorProjectHostTest.cs b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/FallbackRazorProjectHostTest.cs
new file mode 100644
index 0000000000..04c910800b
--- /dev/null
+++ b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/FallbackRazorProjectHostTest.cs
@@ -0,0 +1,373 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.LanguageServices.Razor;
+using Microsoft.VisualStudio.ProjectSystem;
+using Moq;
+using Xunit;
+
+namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
+{
+ public class FallbackRazorProjectHostTest : ForegroundDispatcherTestBase
+ {
+ public FallbackRazorProjectHostTest()
+ {
+ Workspace = new AdhocWorkspace();
+ ProjectManager = new TestProjectSnapshotManager(Dispatcher, Workspace);
+ }
+
+ private TestProjectSnapshotManager ProjectManager { get; }
+
+ private Workspace Workspace { get; }
+
+ [ForegroundFact]
+ public async Task FallbackRazorProjectHost_ForegroundThread_CreateAndDispose_Succeeds()
+ {
+ // Arrange
+ var services = new TestProjectSystemServices("Test.csproj");
+ var host = new TestFallbackRazorProjectHost(services, Workspace, ProjectManager);
+
+ // Act & Assert
+ await host.LoadAsync();
+ Assert.Empty(ProjectManager.Projects);
+
+ await host.DisposeAsync();
+ Assert.Empty(ProjectManager.Projects);
+ }
+
+ [ForegroundFact]
+ public async Task FallbackRazorProjectHost_BackgroundThread_CreateAndDispose_Succeeds()
+ {
+ // Arrange
+ var services = new TestProjectSystemServices("Test.csproj");
+ var host = new TestFallbackRazorProjectHost(services, Workspace, ProjectManager);
+
+ // Act & Assert
+ await Task.Run(async () => await host.LoadAsync());
+ Assert.Empty(ProjectManager.Projects);
+
+ await Task.Run(async () => await host.DisposeAsync());
+ Assert.Empty(ProjectManager.Projects);
+ }
+
+ [ForegroundFact]
+ public async Task OnProjectChanged_ReadsProperties_InitializesProject()
+ {
+ // Arrange
+ var changes = new TestProjectChangeDescription[]
+ {
+ new TestProjectChangeDescription()
+ {
+ RuleName = ManageProjectSystemSchema.ResolvedCompilationReference.SchemaName,
+ After = TestProjectRuleSnapshot.CreateItems(ManageProjectSystemSchema.ResolvedCompilationReference.SchemaName, new Dictionary>()
+ {
+ { "c:\\nuget\\Microsoft.AspNetCore.Mvc.razor.dll", new Dictionary() },
+ }),
+ },
+ };
+
+ var services = new TestProjectSystemServices("Test.csproj");
+
+ var host = new TestFallbackRazorProjectHost(services, Workspace, ProjectManager)
+ {
+ AssemblyVersion = new Version(2, 0), // Mock for reading the assembly's version
+ };
+
+ await Task.Run(async () => await host.LoadAsync());
+ Assert.Empty(ProjectManager.Projects);
+
+ // Act
+ await Task.Run(async () => await host.OnProjectChanged(services.CreateUpdate(changes)));
+
+ // Assert
+ var snapshot = Assert.Single(ProjectManager.Projects);
+ Assert.Equal("Test.csproj", snapshot.FilePath);
+ Assert.Same(FallbackRazorConfiguration.MVC_2_0, snapshot.Configuration);
+
+ await Task.Run(async () => await host.DisposeAsync());
+ Assert.Empty(ProjectManager.Projects);
+ }
+
+ [ForegroundFact]
+ public async Task OnProjectChanged_NoAssemblyFound_DoesNotIniatializeProject()
+ {
+ // Arrange
+ var changes = new TestProjectChangeDescription[]
+ {
+ new TestProjectChangeDescription()
+ {
+ RuleName = ManageProjectSystemSchema.ResolvedCompilationReference.SchemaName,
+ After = TestProjectRuleSnapshot.CreateItems(ManageProjectSystemSchema.ResolvedCompilationReference.SchemaName, new Dictionary>()
+ {
+ }),
+ },
+
+ };
+ var services = new TestProjectSystemServices("Test.csproj");
+
+ var host = new TestFallbackRazorProjectHost(services, Workspace, ProjectManager);
+
+ await Task.Run(async () => await host.LoadAsync());
+ Assert.Empty(ProjectManager.Projects);
+
+ // Act
+ await Task.Run(async () => await host.OnProjectChanged(services.CreateUpdate(changes)));
+
+ // Assert
+ Assert.Empty(ProjectManager.Projects);
+
+ await Task.Run(async () => await host.DisposeAsync());
+ Assert.Empty(ProjectManager.Projects);
+ }
+
+ [ForegroundFact]
+ public async Task OnProjectChanged_AssemblyFoundButCannotReadVersion_DoesNotIniatializeProject()
+ {
+ // Arrange
+ var changes = new TestProjectChangeDescription[]
+ {
+ new TestProjectChangeDescription()
+ {
+ RuleName = ManageProjectSystemSchema.ResolvedCompilationReference.SchemaName,
+ After = TestProjectRuleSnapshot.CreateItems(ManageProjectSystemSchema.ResolvedCompilationReference.SchemaName, new Dictionary>()
+ {
+ { "c:\\nuget\\Microsoft.AspNetCore.Mvc.razor.dll", new Dictionary() },
+ }),
+ },
+ };
+
+ var services = new TestProjectSystemServices("Test.csproj");
+
+ var host = new TestFallbackRazorProjectHost(services, Workspace, ProjectManager);
+
+ await Task.Run(async () => await host.LoadAsync());
+ Assert.Empty(ProjectManager.Projects);
+
+ // Act
+ await Task.Run(async () => await host.OnProjectChanged(services.CreateUpdate(changes)));
+
+ // Assert
+ Assert.Empty(ProjectManager.Projects);
+
+ await Task.Run(async () => await host.DisposeAsync());
+ Assert.Empty(ProjectManager.Projects);
+ }
+
+ [ForegroundFact]
+ public async Task OnProjectChanged_UpdateProject_Succeeds()
+ {
+ // Arrange
+ var changes = new TestProjectChangeDescription[]
+ {
+ new TestProjectChangeDescription()
+ {
+ RuleName = ManageProjectSystemSchema.ResolvedCompilationReference.SchemaName,
+ After = TestProjectRuleSnapshot.CreateItems(ManageProjectSystemSchema.ResolvedCompilationReference.SchemaName, new Dictionary>()
+ {
+ { "c:\\nuget\\Microsoft.AspNetCore.Mvc.razor.dll", new Dictionary() },
+ }),
+ },
+ };
+
+ var services = new TestProjectSystemServices("Test.csproj");
+
+ var host = new TestFallbackRazorProjectHost(services, Workspace, ProjectManager)
+ {
+ AssemblyVersion = new Version(2, 0),
+ };
+
+ await Task.Run(async () => await host.LoadAsync());
+ Assert.Empty(ProjectManager.Projects);
+
+ // Act - 1
+ await Task.Run(async () => await host.OnProjectChanged(services.CreateUpdate(changes)));
+
+ // Assert - 1
+ var snapshot = Assert.Single(ProjectManager.Projects);
+ Assert.Equal("Test.csproj", snapshot.FilePath);
+ Assert.Same(FallbackRazorConfiguration.MVC_2_0, snapshot.Configuration);
+
+ // Act - 2
+ host.AssemblyVersion = new Version(1, 0);
+ await Task.Run(async () => await host.OnProjectChanged(services.CreateUpdate(changes)));
+
+ // Assert - 2
+ snapshot = Assert.Single(ProjectManager.Projects);
+ Assert.Equal("Test.csproj", snapshot.FilePath);
+ Assert.Same(FallbackRazorConfiguration.MVC_1_0, snapshot.Configuration);
+
+ await Task.Run(async () => await host.DisposeAsync());
+ Assert.Empty(ProjectManager.Projects);
+ }
+
+ [ForegroundFact]
+ public async Task OnProjectChanged_VersionRemoved_DeinitializesProject()
+ {
+ // Arrange
+ var changes = new TestProjectChangeDescription[]
+ {
+ new TestProjectChangeDescription()
+ {
+ RuleName = ManageProjectSystemSchema.ResolvedCompilationReference.SchemaName,
+ After = TestProjectRuleSnapshot.CreateItems(ManageProjectSystemSchema.ResolvedCompilationReference.SchemaName, new Dictionary>()
+ {
+ { "c:\\nuget\\Microsoft.AspNetCore.Mvc.razor.dll", new Dictionary() },
+ }),
+ },
+ };
+
+ var services = new TestProjectSystemServices("Test.csproj");
+
+ var host = new TestFallbackRazorProjectHost(services, Workspace, ProjectManager)
+ {
+ AssemblyVersion = new Version(2, 0),
+ };
+
+ await Task.Run(async () => await host.LoadAsync());
+ Assert.Empty(ProjectManager.Projects);
+
+ // Act - 1
+ await Task.Run(async () => await host.OnProjectChanged(services.CreateUpdate(changes)));
+
+ // Assert - 1
+ var snapshot = Assert.Single(ProjectManager.Projects);
+ Assert.Equal("Test.csproj", snapshot.FilePath);
+ Assert.Same(FallbackRazorConfiguration.MVC_2_0, snapshot.Configuration);
+
+ // Act - 2
+ host.AssemblyVersion= null;
+ await Task.Run(async () => await host.OnProjectChanged(services.CreateUpdate(changes)));
+
+ // Assert - 2
+ Assert.Empty(ProjectManager.Projects);
+
+ await Task.Run(async () => await host.DisposeAsync());
+ Assert.Empty(ProjectManager.Projects);
+ }
+
+ [ForegroundFact]
+ public async Task OnProjectChanged_AfterDispose_IgnoresUpdate()
+ {
+ // Arrange
+ var changes = new TestProjectChangeDescription[]
+ {
+ new TestProjectChangeDescription()
+ {
+ RuleName = ManageProjectSystemSchema.ResolvedCompilationReference.SchemaName,
+ After = TestProjectRuleSnapshot.CreateItems(ManageProjectSystemSchema.ResolvedCompilationReference.SchemaName, new Dictionary>()
+ {
+ { "c:\\nuget\\Microsoft.AspNetCore.Mvc.razor.dll", new Dictionary() },
+ }),
+ },
+ };
+
+ var services = new TestProjectSystemServices("Test.csproj");
+
+ var host = new TestFallbackRazorProjectHost(services, Workspace, ProjectManager)
+ {
+ AssemblyVersion = new Version(2, 0),
+ };
+
+ await Task.Run(async () => await host.LoadAsync());
+ Assert.Empty(ProjectManager.Projects);
+
+ // Act - 1
+ await Task.Run(async () => await host.OnProjectChanged(services.CreateUpdate(changes)));
+
+ // Assert - 1
+ var snapshot = Assert.Single(ProjectManager.Projects);
+ Assert.Equal("Test.csproj", snapshot.FilePath);
+ Assert.Same(FallbackRazorConfiguration.MVC_2_0, snapshot.Configuration);
+
+ // Act - 2
+ await Task.Run(async () => await host.DisposeAsync());
+
+ // Assert - 2
+ Assert.Empty(ProjectManager.Projects);
+
+ // Act - 3
+ host.AssemblyVersion = new Version(1, 1);
+ await Task.Run(async () => await host.OnProjectChanged(services.CreateUpdate(changes)));
+
+ // Assert - 3
+ Assert.Empty(ProjectManager.Projects);
+ }
+
+ [ForegroundFact]
+ public async Task OnProjectRenamed_RemovesHostProject_CopiesConfiguration()
+ {
+ // Arrange
+ var changes = new TestProjectChangeDescription[]
+ {
+ new TestProjectChangeDescription()
+ {
+ RuleName = ManageProjectSystemSchema.ResolvedCompilationReference.SchemaName,
+ After = TestProjectRuleSnapshot.CreateItems(ManageProjectSystemSchema.ResolvedCompilationReference.SchemaName, new Dictionary>()
+ {
+ { "c:\\nuget\\Microsoft.AspNetCore.Mvc.razor.dll", new Dictionary() },
+ }),
+ },
+ };
+
+ var services = new TestProjectSystemServices("Test.csproj");
+
+ var host = new TestFallbackRazorProjectHost(services, Workspace, ProjectManager)
+ {
+ AssemblyVersion = new Version(2, 0), // Mock for reading the assembly's version
+ };
+
+ await Task.Run(async () => await host.LoadAsync());
+ Assert.Empty(ProjectManager.Projects);
+
+ // Act - 1
+ await Task.Run(async () => await host.OnProjectChanged(services.CreateUpdate(changes)));
+
+ // Assert - 1
+ var snapshot = Assert.Single(ProjectManager.Projects);
+ Assert.Equal("Test.csproj", snapshot.FilePath);
+ Assert.Same(FallbackRazorConfiguration.MVC_2_0, snapshot.Configuration);
+
+ // Act - 2
+ services.UnconfiguredProject.FullPath = "Test2.csproj";
+ await Task.Run(async () => await host.OnProjectRenamingAsync());
+
+ // Assert - 1
+ snapshot = Assert.Single(ProjectManager.Projects);
+ Assert.Equal("Test2.csproj", snapshot.FilePath);
+ Assert.Same(FallbackRazorConfiguration.MVC_2_0, snapshot.Configuration);
+
+ await Task.Run(async () => await host.DisposeAsync());
+ Assert.Empty(ProjectManager.Projects);
+ }
+
+ private class TestFallbackRazorProjectHost : FallbackRazorProjectHost
+ {
+ internal TestFallbackRazorProjectHost(IUnconfiguredProjectCommonServices commonServices, Workspace workspace, ProjectSnapshotManagerBase projectManager)
+ : base(commonServices, workspace, projectManager)
+ {
+ }
+
+ public Version AssemblyVersion { get; set; }
+
+ protected override Version GetAssemblyVersion(string filePath)
+ {
+ return AssemblyVersion;
+ }
+ }
+
+ private class TestProjectSnapshotManager : DefaultProjectSnapshotManager
+ {
+ public TestProjectSnapshotManager(ForegroundDispatcher dispatcher, Workspace workspace)
+ : base(dispatcher, Mock.Of(), Mock.Of(), Array.Empty(), workspace)
+ {
+ }
+
+ protected override void NotifyBackgroundWorker(ProjectSnapshotUpdateContext context)
+ {
+ }
+ }
+ }
+}
diff --git a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/ProjectSnapshotWorkerQueueTest.cs b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/ProjectSnapshotWorkerQueueTest.cs
index f4129b8bd6..70ff58db57 100644
--- a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/ProjectSnapshotWorkerQueueTest.cs
+++ b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/ProjectSnapshotWorkerQueueTest.cs
@@ -16,30 +16,54 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
public ProjectSnapshotWorkerQueueTest()
{
- Project project1 = null;
- Project project2 = null;
+ HostProject1 = new HostProject("Test1.csproj", FallbackRazorConfiguration.MVC_1_0);
+ HostProject2 = new HostProject("Test2.csproj", FallbackRazorConfiguration.MVC_1_0);
- Workspace = TestWorkspace.Create(workspace =>
- {
- project1 = workspace.CurrentSolution.AddProject("Test1", "Test1", LanguageNames.CSharp);
- project2 = workspace.CurrentSolution.AddProject("Test2", "Test2", LanguageNames.CSharp);
- });
+ Workspace = TestWorkspace.Create();
- Project1 = project1;
- Project2 = project2;
+ var projectId1 = ProjectId.CreateNewId("Test1");
+ var projectId2 = ProjectId.CreateNewId("Test2");
+
+ var solution = Workspace.CurrentSolution
+ .AddProject(ProjectInfo.Create(
+ projectId1,
+ VersionStamp.Default,
+ "Test1",
+ "Test1",
+ LanguageNames.CSharp,
+ "Test1.csproj"))
+ .AddProject(ProjectInfo.Create(
+ projectId2,
+ VersionStamp.Default,
+ "Test2",
+ "Test2",
+ LanguageNames.CSharp,
+ "Test2.csproj")); ;
+
+ WorkspaceProject1 = solution.GetProject(projectId1);
+ WorkspaceProject2 = solution.GetProject(projectId2);
}
- public Project Project1 { get; }
+ private HostProject HostProject1 { get; }
- public Project Project2 { get; }
+ private HostProject HostProject2 { get; }
- public Workspace Workspace { get; }
+ private Project WorkspaceProject1 { get; }
+
+ private Project WorkspaceProject2 { get; }
+
+ private Workspace Workspace { get; }
[ForegroundFact]
public async Task Queue_ProcessesNotifications_AndGoesBackToSleep()
{
// Arrange
var projectManager = new TestProjectSnapshotManager(Dispatcher, Workspace);
+ projectManager.HostProjectAdded(HostProject1);
+ projectManager.HostProjectAdded(HostProject2);
+ projectManager.WorkspaceProjectAdded(WorkspaceProject1);
+ projectManager.WorkspaceProjectAdded(WorkspaceProject2);
+
var projectWorker = new TestProjectSnapshotWorker();
var queue = new ProjectSnapshotWorkerQueue(Dispatcher, projectManager, projectWorker)
@@ -51,10 +75,10 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
};
// Act & Assert
- queue.Enqueue(Project1);
+ queue.Enqueue(projectManager.GetSnapshot(HostProject1).CreateUpdateContext());
- Assert.True(queue.IsScheduledOrRunning);
- Assert.True(queue.HasPendingNotifications);
+ Assert.True(queue.IsScheduledOrRunning, "Queue should be scheduled during Enqueue");
+ Assert.True(queue.HasPendingNotifications, "Queue should have a notification created during Enqueue");
// Allow the background work to proceed.
queue.BlockBackgroundWorkStart.Set();
@@ -62,8 +86,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Get off the foreground thread and allow the updates to flow through.
await Task.Run(() => queue.NotifyForegroundWorkFinish.Wait(TimeSpan.FromSeconds(1)));
- Assert.False(queue.IsScheduledOrRunning);
- Assert.False(queue.HasPendingNotifications);
+ Assert.False(queue.IsScheduledOrRunning, "Queue should not have restarted");
+ Assert.False(queue.HasPendingNotifications, "Queue should have processed all notifications");
}
[ForegroundFact]
@@ -71,6 +95,11 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
// Arrange
var projectManager = new TestProjectSnapshotManager(Dispatcher, Workspace);
+ projectManager.HostProjectAdded(HostProject1);
+ projectManager.HostProjectAdded(HostProject2);
+ projectManager.WorkspaceProjectAdded(WorkspaceProject1);
+ projectManager.WorkspaceProjectAdded(WorkspaceProject2);
+
var projectWorker = new TestProjectSnapshotWorker();
var queue = new ProjectSnapshotWorkerQueue(Dispatcher, projectManager, projectWorker)
@@ -82,20 +111,20 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
};
// Act & Assert
- queue.Enqueue(Project1);
+ queue.Enqueue(projectManager.GetSnapshot(HostProject1).CreateUpdateContext());
- Assert.True(queue.IsScheduledOrRunning);
- Assert.True(queue.HasPendingNotifications);
+ Assert.True(queue.IsScheduledOrRunning, "Queue should be scheduled during Enqueue");
+ Assert.True(queue.HasPendingNotifications, "Queue should have a notification created during Enqueue");
// Allow the background work to proceed.
queue.BlockBackgroundWorkStart.Set();
queue.NotifyBackgroundWorkFinish.Wait(); // Block the foreground thread so we can queue another notification.
- Assert.True(queue.IsScheduledOrRunning);
- Assert.False(queue.HasPendingNotifications);
+ Assert.True(queue.IsScheduledOrRunning, "Worker should be processing now");
+ Assert.False(queue.HasPendingNotifications, "Worker should have taken all notifications");
- queue.Enqueue(Project2);
+ queue.Enqueue(projectManager.GetSnapshot(HostProject2).CreateUpdateContext());
Assert.True(queue.HasPendingNotifications); // Now we should see the worker restart when it finishes.
@@ -106,17 +135,17 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
queue.NotifyForegroundWorkFinish.Reset();
// It should start running again right away.
- Assert.True(queue.IsScheduledOrRunning);
- Assert.True(queue.HasPendingNotifications);
+ Assert.True(queue.IsScheduledOrRunning, "Queue should be scheduled during Enqueue");
+ Assert.True(queue.HasPendingNotifications, "Queue should have a notification created during Enqueue");
// Allow the background work to proceed.
queue.BlockBackgroundWorkStart.Set();
// Get off the foreground thread and allow the updates to flow through.
await Task.Run(() => queue.NotifyForegroundWorkFinish.Wait(TimeSpan.FromSeconds(1)));
-
- Assert.False(queue.IsScheduledOrRunning);
- Assert.False(queue.HasPendingNotifications);
+
+ Assert.False(queue.IsScheduledOrRunning, "Queue should not have restarted");
+ Assert.False(queue.HasPendingNotifications, "Queue should have processed all notifications");
}
private class TestProjectSnapshotManager : DefaultProjectSnapshotManager
@@ -126,17 +155,24 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
}
- public DefaultProjectSnapshot GetSnapshot(ProjectId id)
+ public DefaultProjectSnapshot GetSnapshot(HostProject hostProject)
{
- return Projects.Cast().FirstOrDefault(s => s.UnderlyingProject.Id == id);
+ return Projects.Cast().FirstOrDefault(s => s.FilePath == hostProject.FilePath);
+ }
+
+ public DefaultProjectSnapshot GetSnapshot(Project workspaceProject)
+ {
+ return Projects.Cast().FirstOrDefault(s => s.FilePath == workspaceProject.FilePath);
}
protected override void NotifyListeners(ProjectChangeEventArgs e)
{
}
- protected override void NotifyBackgroundWorker(Project project)
+ protected override void NotifyBackgroundWorker(ProjectSnapshotUpdateContext context)
{
+ Assert.NotNull(context.HostProject);
+ Assert.NotNull(context.WorkspaceProject);
}
}
diff --git a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/TestAssemblyReference.cs b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/TestAssemblyReference.cs
new file mode 100644
index 0000000000..b80f5e85ba
--- /dev/null
+++ b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/TestAssemblyReference.cs
@@ -0,0 +1,68 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Reflection;
+using System.Threading.Tasks;
+using Microsoft.VisualStudio.ProjectSystem.Properties;
+
+namespace Microsoft.VisualStudio.ProjectSystem.References
+{
+ internal class TestAssemblyReference : IAssemblyReference
+ {
+ public AssemblyName AssemblyName { get; set; }
+
+ public string FullPath { get; set; }
+
+ public IProjectProperties Metadata => throw new System.NotImplementedException();
+
+ public Task GetAssemblyNameAsync()
+ {
+ return Task.FromResult(AssemblyName);
+ }
+
+ public Task GetCopyLocalAsync()
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public Task GetCopyLocalSatelliteAssembliesAsync()
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public Task GetDescriptionAsync()
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public Task GetFullPathAsync()
+ {
+ return Task.FromResult(FullPath);
+ }
+
+ public Task GetNameAsync()
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public Task GetReferenceOutputAssemblyAsync()
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public Task GetRequiredTargetFrameworkAsync()
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public Task GetSpecificVersionAsync()
+ {
+ throw new System.NotImplementedException();
+ }
+
+ public Task IsWinMDFileAsync()
+ {
+ throw new System.NotImplementedException();
+ }
+ }
+}
diff --git a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/TestProjectChangeDescription.cs b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/TestProjectChangeDescription.cs
new file mode 100644
index 0000000000..a6aa3b21d5
--- /dev/null
+++ b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/TestProjectChangeDescription.cs
@@ -0,0 +1,24 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information
+
+using Microsoft.VisualStudio.ProjectSystem.Properties;
+
+namespace Microsoft.VisualStudio.ProjectSystem
+{
+ internal class TestProjectChangeDescription : IProjectChangeDescription
+ {
+ public string RuleName { get; set; }
+
+ public TestProjectRuleSnapshot Before { get; set; }
+
+ public IProjectChangeDiff Difference { get; set; }
+
+ public TestProjectRuleSnapshot After { get; set; }
+
+ IProjectRuleSnapshot IProjectChangeDescription.Before => Before;
+
+ IProjectChangeDiff IProjectChangeDescription.Difference => Difference;
+
+ IProjectRuleSnapshot IProjectChangeDescription.After => After;
+ }
+}
\ No newline at end of file
diff --git a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/TestProjectRuleSnapshot.cs b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/TestProjectRuleSnapshot.cs
new file mode 100644
index 0000000000..4239470863
--- /dev/null
+++ b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/TestProjectRuleSnapshot.cs
@@ -0,0 +1,61 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using Microsoft.VisualStudio.ProjectSystem.Properties;
+
+namespace Microsoft.VisualStudio.ProjectSystem
+{
+ internal class TestProjectRuleSnapshot : IProjectRuleSnapshot
+ {
+ public static TestProjectRuleSnapshot CreateProperties(string ruleName, Dictionary properties)
+ {
+ return new TestProjectRuleSnapshot(
+ ruleName,
+ items: ImmutableDictionary>.Empty,
+ properties: properties.ToImmutableDictionary(),
+ dataSourceVersions: ImmutableDictionary.Empty);
+ }
+
+ public static TestProjectRuleSnapshot CreateItems(string ruleName, Dictionary> items)
+ {
+ return new TestProjectRuleSnapshot(
+ ruleName,
+ items: items.ToImmutableDictionary(kvp => kvp.Key, kvp => (IImmutableDictionary)kvp.Value.ToImmutableDictionary()),
+ properties: ImmutableDictionary.Empty,
+ dataSourceVersions: ImmutableDictionary.Empty);
+ }
+
+ public TestProjectRuleSnapshot(
+ string ruleName,
+ IImmutableDictionary> items,
+ IImmutableDictionary properties,
+ IImmutableDictionary dataSourceVersions)
+ {
+ RuleName = ruleName;
+ Items = items;
+ Properties = properties;
+ DataSourceVersions = dataSourceVersions;
+ }
+
+ public void SetProperty(string key, string value)
+ {
+ Properties = Properties.SetItem(key, value);
+ }
+
+ public void SetItem(string key, Dictionary values)
+ {
+ Items = Items.SetItem(key, values.ToImmutableDictionary());
+ }
+
+ public string RuleName { get; }
+
+ public IImmutableDictionary> Items { get; set; }
+
+ public IImmutableDictionary Properties { get; set; }
+
+ public IImmutableDictionary DataSourceVersions { get; }
+ }
+}
diff --git a/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/TestProjectSystemServices.cs b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/TestProjectSystemServices.cs
new file mode 100644
index 0000000000..7333883233
--- /dev/null
+++ b/test/Microsoft.VisualStudio.LanguageServices.Razor.Test/ProjectSystem/TestProjectSystemServices.cs
@@ -0,0 +1,862 @@
+// 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();
+
+ TasksService = new TestProjectAsynchronousTasksService(ProjectService, UnconfiguredProject, ActiveConfiguredProject);
+ }
+
+ 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 TestProjectAsynchronousTasksService TasksService { 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;
+
+ IProjectAsynchronousTasksService IUnconfiguredProjectCommonServices.TasksService => TasksService;
+
+ IProjectThreadingService IUnconfiguredProjectCommonServices.ThreadingService => ThreadingService;
+
+ UnconfiguredProject IUnconfiguredProjectCommonServices.UnconfiguredProject => UnconfiguredProject;
+
+ public IProjectVersionedValue CreateUpdate(params TestProjectChangeDescription[] descriptions)
+ {
+ return new ProjectVersionedValue(
+ value: new ProjectSubscriptionUpdate(
+ projectChanges: descriptions.ToImmutableDictionary(d => d.RuleName, d => (IProjectChangeDescription)d),
+ projectConfiguration: ActiveConfiguredProject.ProjectConfiguration),
+ dataSourceVersions: ImmutableDictionary.Empty);
+ }
+
+ public class TestProjectServices : IProjectServices
+ {
+ public TestProjectServices(TestProjectService projectService)
+ {
+ ProjectService = projectService;
+ ThreadingPolicy = new TestThreadingService();
+ }
+
+ public TestProjectService ProjectService { get; }
+
+ public TestThreadingService ThreadingPolicy { get; }
+
+ IProjectLockService IProjectServices.ProjectLockService => throw new NotImplementedException();
+
+ IProjectThreadingService IProjectServices.ThreadingPolicy => ThreadingPolicy;
+
+ IProjectFaultHandlerService IProjectServices.FaultHandler => throw new NotImplementedException();
+
+ IProjectReloader IProjectServices.ProjectReloader => throw new NotImplementedException();
+
+ ExportProvider IProjectCommonServices.ExportProvider => throw new NotImplementedException();
+
+ IProjectDataSourceRegistry IProjectCommonServices.DataSourceRegistry => throw new NotImplementedException();
+
+ IProjectService IProjectCommonServices.ProjectService => ProjectService;
+
+ IProjectCapabilitiesScope IProjectCommonServices.Capabilities => throw new NotImplementedException();
+ }
+
+ public class TestProjectService : IProjectService
+ {
+ public TestProjectService()
+ {
+ LoadedUnconfiguredProjects = new List();
+ Services = new TestProjectServices(this);
+ }
+
+ public List LoadedUnconfiguredProjects { get; }
+
+ public TestProjectServices Services { get; }
+
+ IEnumerable IProjectService.LoadedUnconfiguredProjects => throw new NotImplementedException();
+
+ IProjectServices IProjectService.Services => Services;
+
+ IProjectCapabilitiesScope IProjectService.Capabilities => throw new NotImplementedException();
+
+ Task IProjectService.LoadProjectAsync(string projectLocation, IImmutableSet projectCapabilities)
+ {
+ throw new NotImplementedException();
+ }
+
+ Task IProjectService.LoadProjectAsync(XmlReader reader, IImmutableSet projectCapabilities)
+ {
+ throw new NotImplementedException();
+ }
+
+ Task IProjectService.LoadProjectAsync(string projectLocation, bool delayAutoLoad, IImmutableSet projectCapabilities)
+ {
+ throw new NotImplementedException();
+ }
+
+ Task IProjectService.UnloadProjectAsync(UnconfiguredProject project)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ public class TestUnconfiguredProject : UnconfiguredProject
+ {
+ public TestUnconfiguredProject(TestProjectService projectService, string fullPath)
+ {
+ ProjectService = projectService;
+ FullPath = fullPath;
+
+ LoadedConfiguredProjects = new List();
+ }
+
+ public TestProjectService ProjectService { get; }
+
+ public string FullPath { get; set; }
+
+ public List LoadedConfiguredProjects { get; }
+
+ string UnconfiguredProject.FullPath => FullPath;
+ bool UnconfiguredProject.RequiresReloadForExternalFileChange => throw new NotImplementedException();
+
+ IProjectCapabilitiesScope UnconfiguredProject.Capabilities => throw new NotImplementedException();
+
+ IProjectService UnconfiguredProject.ProjectService => ProjectService;
+
+ IUnconfiguredProjectServices UnconfiguredProject.Services => throw new NotImplementedException();
+
+ IEnumerable UnconfiguredProject.LoadedConfiguredProjects => LoadedConfiguredProjects;
+
+ bool UnconfiguredProject.IsLoading => throw new NotImplementedException();
+
+ event AsyncEventHandler UnconfiguredProject.ProjectUnloading
+ {
+ add
+ {
+ throw new NotImplementedException();
+ }
+
+ remove
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ event AsyncEventHandler UnconfiguredProject.ProjectRenaming
+ {
+ add
+ {
+ }
+
+ remove
+ {
+ }
+ }
+
+ event AsyncEventHandler UnconfiguredProject.ProjectRenamedOnWriter
+ {
+ add
+ {
+ throw new NotImplementedException();
+ }
+
+ remove
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ event AsyncEventHandler UnconfiguredProject.ProjectRenamed
+ {
+ add
+ {
+ throw new NotImplementedException();
+ }
+
+ remove
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ Task UnconfiguredProject.CanRenameAsync(string newFilePath)
+ {
+ throw new NotImplementedException();
+ }
+
+ Task UnconfiguredProject.GetFileEncodingAsync()
+ {
+ throw new NotImplementedException();
+ }
+
+ Task UnconfiguredProject.GetIsDirtyAsync()
+ {
+ throw new NotImplementedException();
+ }
+
+ Task UnconfiguredProject.GetSuggestedConfiguredProjectAsync()
+ {
+ throw new NotImplementedException();
+ }
+
+ Task UnconfiguredProject.LoadConfiguredProjectAsync(string name, IImmutableDictionary configurationProperties)
+ {
+ throw new NotImplementedException();
+ }
+
+ Task UnconfiguredProject.LoadConfiguredProjectAsync(ProjectConfiguration projectConfiguration)
+ {
+ throw new NotImplementedException();
+ }
+
+ Task UnconfiguredProject.ReloadAsync(bool immediately)
+ {
+ throw new NotImplementedException();
+ }
+
+ Task UnconfiguredProject.RenameAsync(string newFilePath)
+ {
+ throw new NotImplementedException();
+ }
+
+ Task UnconfiguredProject.SaveAsync(string filePath)
+ {
+ throw new NotImplementedException();
+ }
+
+ Task UnconfiguredProject.SaveCopyAsync(string filePath, Encoding fileEncoding)
+ {
+ throw new NotImplementedException();
+ }
+
+ Task UnconfiguredProject.SaveUserFileAsync()
+ {
+ throw new NotImplementedException();
+ }
+
+ Task UnconfiguredProject.SetFileEncodingAsync(Encoding value)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ public class TestConfiguredProject : ConfiguredProject
+ {
+ public TestConfiguredProject(TestUnconfiguredProject unconfiguredProject, TestPropertyData[] data)
+ {
+ UnconfiguredProject = unconfiguredProject;
+ Services = new TestConfiguredProjectServices(this, data);
+
+ ProjectConfiguration = new StandardProjectConfiguration(
+ "Debug|AnyCPU",
+ ImmutableDictionary.Empty.Add("Configuration", "Debug").Add("Platform", "AnyCPU"));
+ }
+
+ public TestUnconfiguredProject UnconfiguredProject { get; }
+
+ public ProjectConfiguration ProjectConfiguration { get; }
+
+ public TestConfiguredProjectServices Services { get; }
+
+ IComparable ConfiguredProject.ProjectVersion => throw new NotImplementedException();
+
+ IReceivableSourceBlock ConfiguredProject.ProjectVersionBlock => throw new NotImplementedException();
+
+ ProjectConfiguration ConfiguredProject.ProjectConfiguration => ProjectConfiguration;
+
+ IProjectCapabilitiesScope ConfiguredProject.Capabilities => throw new NotImplementedException();
+
+ UnconfiguredProject ConfiguredProject.UnconfiguredProject => UnconfiguredProject;
+
+ IConfiguredProjectServices ConfiguredProject.Services => Services;
+
+ event AsyncEventHandler ConfiguredProject.ProjectUnloading
+ {
+ add
+ {
+ throw new NotImplementedException();
+ }
+
+ remove
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ event EventHandler ConfiguredProject.ProjectChanged
+ {
+ add
+ {
+ throw new NotImplementedException();
+ }
+
+ remove
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ event EventHandler ConfiguredProject.ProjectChangedSynchronous
+ {
+ add
+ {
+ throw new NotImplementedException();
+ }
+
+ remove
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ void ConfiguredProject.NotifyProjectChange()
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ public class TestConfiguredProjectServices : IConfiguredProjectServices
+ {
+ public TestConfiguredProjectServices(TestConfiguredProject configuredProject, TestPropertyData[] data)
+ {
+ ConfiguredProject = configuredProject;
+
+ AdditionalRuleDefinitions = new TestAdditionalRuleDefinitionsService();
+ PropertyPagesCatalog = new TestPropertyPagesCatalogProvider(new TestPropertyPagesCatalog(data));
+ }
+
+ public TestConfiguredProject ConfiguredProject { get; }
+
+ public TestAdditionalRuleDefinitionsService AdditionalRuleDefinitions { get; }
+
+ public TestPropertyPagesCatalogProvider PropertyPagesCatalog { get; }
+
+ IOutputGroupsService IConfiguredProjectServices.OutputGroups => throw new NotImplementedException();
+
+ IBuildProject IConfiguredProjectServices.Build => throw new NotImplementedException();
+
+ IBuildSupport IConfiguredProjectServices.BuildSupport => throw new NotImplementedException();
+
+ IAssemblyReferencesService IConfiguredProjectServices.AssemblyReferences => throw new NotImplementedException();
+
+ IComReferencesService IConfiguredProjectServices.ComReferences => throw new NotImplementedException();
+
+ ISdkReferencesService IConfiguredProjectServices.SdkReferences => throw new NotImplementedException();
+
+ IPackageReferencesService IConfiguredProjectServices.PackageReferences => throw new NotImplementedException();
+
+ IWinRTReferencesService IConfiguredProjectServices.WinRTReferences => throw new NotImplementedException();
+
+ IBuildDependencyProjectReferencesService IConfiguredProjectServices.ProjectReferences => throw new NotImplementedException();
+
+ IProjectItemProvider IConfiguredProjectServices.SourceItems => throw new NotImplementedException();
+
+ IProjectPropertiesProvider IConfiguredProjectServices.ProjectPropertiesProvider => throw new NotImplementedException();
+
+ IProjectPropertiesProvider IConfiguredProjectServices.UserPropertiesProvider => throw new NotImplementedException();
+
+ IProjectAsynchronousTasksService IConfiguredProjectServices.ProjectAsynchronousTasks => throw new NotImplementedException();
+
+ IAdditionalRuleDefinitionsService IConfiguredProjectServices.AdditionalRuleDefinitions => AdditionalRuleDefinitions;
+
+ IPropertyPagesCatalogProvider IConfiguredProjectServices.PropertyPagesCatalog => PropertyPagesCatalog;
+
+ IProjectSubscriptionService IConfiguredProjectServices.ProjectSubscription => throw new NotImplementedException();
+
+ IProjectSnapshotService IConfiguredProjectServices.ProjectSnapshotService => throw new NotImplementedException();
+
+ object IConfiguredProjectServices.HostObject => throw new NotImplementedException();
+
+ ExportProvider IProjectCommonServices.ExportProvider => throw new NotImplementedException();
+
+ IProjectDataSourceRegistry IProjectCommonServices.DataSourceRegistry => throw new NotImplementedException();
+
+ IProjectService IProjectCommonServices.ProjectService => ConfiguredProject.UnconfiguredProject.ProjectService;
+
+ IProjectCapabilitiesScope IProjectCommonServices.Capabilities => throw new NotImplementedException();
+ }
+
+ public class TestAdditionalRuleDefinitionsService : IAdditionalRuleDefinitionsService
+ {
+ IProjectVersionedValue IAdditionalRuleDefinitionsService.AdditionalRuleDefinitions => throw new NotImplementedException();
+
+ IReceivableSourceBlock> IProjectValueDataSource.SourceBlock => throw new NotImplementedException();
+
+ ISourceBlock> IProjectValueDataSource.SourceBlock => throw new NotImplementedException();
+
+ NamedIdentity IProjectValueDataSource.DataSourceKey => throw new NotImplementedException();
+
+ IComparable IProjectValueDataSource.DataSourceVersion => throw new NotImplementedException();
+
+ bool IAdditionalRuleDefinitionsService.AddRuleDefinition(string path, string context)
+ {
+ return false;
+ }
+
+ bool IAdditionalRuleDefinitionsService.AddRuleDefinition(Rule rule, string context)
+ {
+ return false;
+ }
+
+ IDisposable IJoinableProjectValueDataSource.Join()
+ {
+ throw new NotImplementedException();
+ }
+
+ bool IAdditionalRuleDefinitionsService.RemoveRuleDefinition(string path)
+ {
+ return false;
+ }
+
+ bool IAdditionalRuleDefinitionsService.RemoveRuleDefinition(Rule rule)
+ {
+ return false;
+ }
+ }
+
+ public class TestPropertyPagesCatalogProvider : IPropertyPagesCatalogProvider
+ {
+ public TestPropertyPagesCatalogProvider(TestPropertyPagesCatalog catalog)
+ {
+ Catalog = catalog;
+ CatalogsByContext = new Dictionary()
+ {
+ { "Project", catalog },
+ };
+ }
+
+ public TestPropertyPagesCatalog Catalog { get; }
+
+ public Dictionary CatalogsByContext { get; }
+
+ public IReceivableSourceBlock> SourceBlock => throw new NotImplementedException();
+
+ public NamedIdentity DataSourceKey => throw new NotImplementedException();
+
+ public IComparable DataSourceVersion => throw new NotImplementedException();
+
+ ISourceBlock> IProjectValueDataSource.SourceBlock => throw new NotImplementedException();
+
+ public Task