Merge branch 'rel/vs15.7' into dev

# Conflicts:
#	build/dependencies.props
#	src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshot.cs
#	src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotManager.cs
#	src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotWorker.cs
#	src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/DefaultProjectSnapshotWorkerFactory.cs
#	src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshot.cs
#	src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectSnapshotUpdateContext.cs
#	src/Microsoft.VisualStudio.Editor.Razor/DefaultVisualStudioDocumentTracker.cs
#	src/Microsoft.VisualStudio.LanguageServices.Razor/Legacy/LegacyTagHelperResolver.cs
#	src/Microsoft.VisualStudio.LanguageServices.Razor/Microsoft.VisualStudio.LanguageServices.Razor.csproj
#	src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/Rules/RazorConfiguration.cs
#	src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/Rules/RazorExtension.cs
#	src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/Rules/RazorGeneral.cs
#	src/Microsoft.VisualStudio.LanguageServices.Razor/ProjectSystem/Rules/RazorProjectProperties.cs
#	test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/ProjectSystem/DefaultProjectSnapshotTest.cs
#	test/Microsoft.VisualStudio.Editor.Razor.Test/Microsoft.VisualStudio.Editor.Razor.Test.csproj
This commit is contained in:
N. Taylor Mullen 2018-03-02 16:25:10 -08:00
commit 5c456fbd04
125 changed files with 6425 additions and 1888 deletions

View File

@ -10,14 +10,12 @@
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Mvc.Razor.Extensions\Microsoft.AspNetCore.Mvc.Razor.Extensions.csproj" />
<ProjectReference Include="..\..\src\Microsoft.CodeAnalysis.Razor.Workspaces\Microsoft.CodeAnalysis.Razor.Workspaces.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\..\src\Microsoft.VisualStudio.LanguageServices.Razor\RazorDiagnosticJsonConverter.cs">
<Link>Shared\RazorDiagnosticJsonConverter.cs</Link>
</Compile>
<Compile Include="..\..\src\Microsoft.VisualStudio.LanguageServices.Razor\TagHelperDescriptorJsonConverter.cs">
<Link>Shared\TagHelperDescriptorJsonConverter.cs</Link>
<Compile Include="..\..\src\Microsoft.VisualStudio.LanguageServices.Razor\Serialization\*.cs">
<Link>Serialization\%(FileName)%(Extension)</Link>
</Compile>
</ItemGroup>

View File

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

View File

@ -70,7 +70,10 @@
</ItemGroup>
<ItemGroup>
<LanguageServiceAssembly Include="$(LanguageServiceOutputPath)%(LanguageServiceAssemblyNames.Identity)" />
<LanguageServiceExtensionAssembly Include="$(RepositoryRoot)src\Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X\bin\$(Configuration)\net46\Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X.dll" />
<LanguageServiceExtensionAssembly Include="$(RepositoryRoot)src\Microsoft.AspNetCore.Mvc.Razor.Extensions\bin\$(Configuration)\net46\Microsoft.AspNetCore.Mvc.Razor.Extensions.dll" />
<LanguageServiceAssembly Include="$(LanguageServiceOutputPath)%(LanguageServiceAssemblyNames.Identity)" Condition="Exists('$(LanguageServiceOutputPath)%(LanguageServiceAssemblyNames.Identity)')" />
<LanguageServiceAssembly Include="%(LanguageServiceExtensionAssembly.Identity)" />
<LanguageServicePDB Include="%(LanguageServiceAssembly.RootDir)%(Directory)%(FileName).pdb" Condition="Exists('%(LanguageServiceAssembly.RootDir)%(Directory)%(FileName).pdb')" />
</ItemGroup>

View File

@ -52,6 +52,7 @@
<VSIX_MicrosoftCodeAnalysisEditorFeaturesTextPackageVersion>2.7.0-beta3-62512-06</VSIX_MicrosoftCodeAnalysisEditorFeaturesTextPackageVersion>
<VSIX_MicrosoftCodeAnalysisRemoteRazorServiceHubPackageVersion>2.7.0-beta3-62512-06</VSIX_MicrosoftCodeAnalysisRemoteRazorServiceHubPackageVersion>
<VSIX_MicrosoftCodeAnalysisWorkspacesCommonPackageVersion>2.7.0-beta3-62512-06</VSIX_MicrosoftCodeAnalysisWorkspacesCommonPackageVersion>
<VSIX_MicrosoftCodeAnalysisVisualBasicWorkspacesPackageVersion>2.7.0-beta3-62512-06</VSIX_MicrosoftCodeAnalysisVisualBasicWorkspacesPackageVersion>
<VSIX_MicrosoftVisualStudioLanguageServicesPackageVersion>2.7.0-beta3-62512-06</VSIX_MicrosoftVisualStudioLanguageServicesPackageVersion>
<VSIX_MicrosoftVisualStudioLanguageServicesRazorRemoteClientPackageVersion>2.7.0-beta3-62512-06</VSIX_MicrosoftVisualStudioLanguageServicesRazorRemoteClientPackageVersion>
<XunitAnalyzersPackageVersion>0.8.0</XunitAnalyzersPackageVersion>

View File

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

View File

@ -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")]

View File

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

View File

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

View File

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

View File

@ -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<INamedTypeSymbol> _results;
public static ViewComponentTypeVisitor Create(Compilation compilation, List<INamedTypeSymbol> 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);
}

View File

@ -7,15 +7,15 @@ using System.Linq;
namespace Microsoft.AspNetCore.Razor.Language
{
public sealed class RazorConfiguration
public abstract class RazorConfiguration
{
public static readonly RazorConfiguration Default = new RazorConfiguration(
public static readonly RazorConfiguration Default = new DefaultRazorConfiguration(
RazorLanguageVersion.Latest,
"unnamed",
Array.Empty<RazorExtension>());
public RazorConfiguration(
RazorLanguageVersion languageVersion,
public static RazorConfiguration Create(
RazorLanguageVersion languageVersion,
string configurationName,
IEnumerable<RazorExtension> extensions)
{
@ -34,15 +34,32 @@ namespace Microsoft.AspNetCore.Razor.Language
throw new ArgumentNullException(nameof(extensions));
}
LanguageVersion = languageVersion;
ConfigurationName = configurationName;
Extensions = extensions.ToArray();
return new DefaultRazorConfiguration(languageVersion, configurationName, extensions.ToArray());
}
public string ConfigurationName { get; }
public abstract string ConfigurationName { get; }
public IReadOnlyList<RazorExtension> Extensions { get; }
public abstract IReadOnlyList<RazorExtension> Extensions { get; }
public RazorLanguageVersion LanguageVersion { get; }
public abstract RazorLanguageVersion LanguageVersion { get; }
private class DefaultRazorConfiguration : RazorConfiguration
{
public DefaultRazorConfiguration(
RazorLanguageVersion languageVersion,
string configurationName,
RazorExtension[] extensions)
{
LanguageVersion = languageVersion;
ConfigurationName = configurationName;
Extensions = extensions;
}
public override string ConfigurationName { get; }
public override IReadOnlyList<RazorExtension> Extensions { get; }
public override RazorLanguageVersion LanguageVersion { get; }
}
}
}

View File

@ -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,

View File

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

View File

@ -13,12 +13,12 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Microsoft.VisualStudio.LanguageServices.Razor\RazorDiagnosticJsonConverter.cs">
<Link>Shared\RazorDiagnosticJsonConverter.cs</Link>
</Compile>
<Compile Include="..\Microsoft.VisualStudio.LanguageServices.Razor\TagHelperDescriptorJsonConverter.cs">
<Compile Include="..\Microsoft.VisualStudio.LanguageServices.Razor\Serialization\TagHelperDescriptorJsonConverter.cs">
<Link>Shared\TagHelperDescriptorJsonConverter.cs</Link>
</Compile>
<Compile Include="..\Microsoft.VisualStudio.LanguageServices.Razor\Serialization\RazorDiagnosticJsonConverter.cs">
<Link>Shared\RazorDiagnosticJsonConverter.cs</Link>
</Compile>
</ItemGroup>
<ItemGroup>

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
namespace Microsoft.CodeAnalysis.Razor
{
@ -9,11 +10,31 @@ namespace Microsoft.CodeAnalysis.Razor
{
public override void ReportError(Exception exception)
{
if (exception == null)
{
throw new ArgumentNullException(nameof(exception));
}
// Do nothing.
}
public override void ReportError(Exception exception, Project project)
public override void ReportError(Exception exception, ProjectSnapshot project)
{
if (exception == null)
{
throw new ArgumentNullException(nameof(exception));
}
// Do nothing.
}
public override void ReportError(Exception exception, Project workspaceProject)
{
if (exception == null)
{
throw new ArgumentNullException(nameof(exception));
}
// Do nothing.
}
}

View File

@ -3,13 +3,16 @@
using System;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
namespace Microsoft.CodeAnalysis.Razor
{
internal abstract class ErrorReporter : IWorkspaceService
{
public abstract void ReportError(Exception exception);
public abstract void ReportError(Exception exception, ProjectSnapshot project);
public abstract void ReportError(Exception exception, Project project);
public abstract void ReportError(Exception exception, Project workspaceProject);
}
}

View File

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

View File

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

View File

@ -1,142 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
// This is hardcoded for now. A more complete design would fan out to a list of providers.
internal class DefaultProjectExtensibilityConfigurationFactory : ProjectExtensibilityConfigurationFactory
{
private const string MvcAssemblyName = "Microsoft.AspNetCore.Mvc.Razor";
private const string RazorV1AssemblyName = "Microsoft.AspNetCore.Razor";
private const string RazorV2AssemblyName = "Microsoft.AspNetCore.Razor.Language";
// Using MaxValue here so that we ignore patch and build numbers. We only want to compare major/minor.
private static readonly Version MaxSupportedRazorVersion = new Version(2, 0, Int32.MaxValue, Int32.MaxValue);
private static readonly Version MaxSupportedMvcVersion = new Version(2, 0, Int32.MaxValue, Int32.MaxValue);
private static readonly Version DefaultRazorVersion = new Version(2, 0, 0, 0);
private static readonly Version DefaultMvcVersion = new Version(2, 0, 0, 0);
public async override Task<ProjectExtensibilityConfiguration> GetConfigurationAsync(Project project, CancellationToken cancellationToken = default(CancellationToken))
{
if (project == null)
{
throw new ArgumentNullException(nameof(project));
}
var compilation = await project.GetCompilationAsync(cancellationToken);
return GetConfiguration(compilation.ReferencedAssemblyNames);
}
// internal/separate for testing.
internal ProjectExtensibilityConfiguration GetConfiguration(IEnumerable<AssemblyIdentity> references)
{
// Avoiding ToDictionary here because we don't want a crash if there is a duplicate name.
var assemblies = new Dictionary<string, AssemblyIdentity>();
foreach (var assembly in references)
{
assemblies[assembly.Name] = assembly;
}
// First we look for the V2+ Razor Assembly. If we find this then its version is the correct Razor version.
AssemblyIdentity razorAssembly;
if (assemblies.TryGetValue(RazorV2AssemblyName, out razorAssembly))
{
if (razorAssembly.Version == null || razorAssembly.Version > MaxSupportedRazorVersion)
{
// This is a newer Razor version than we know, treat it as a fallback case.
razorAssembly = null;
}
}
else if (assemblies.TryGetValue(RazorV1AssemblyName, out razorAssembly))
{
// This assembly only counts as the 'Razor' assembly if it's a version lower than 2.0.0.
if (razorAssembly.Version == null || razorAssembly.Version >= new Version(2, 0, 0, 0))
{
razorAssembly = null;
}
}
AssemblyIdentity mvcAssembly;
if (assemblies.TryGetValue(MvcAssemblyName, out mvcAssembly))
{
if (mvcAssembly.Version == null || mvcAssembly.Version > MaxSupportedMvcVersion)
{
// This is a newer MVC version than we know, treat it as a fallback case.
mvcAssembly = null;
}
}
RazorLanguageVersion languageVersion = null;
if (razorAssembly != null && mvcAssembly != null)
{
languageVersion = GetLanguageVersion(razorAssembly);
// This means we've definitely found a supported Razor version and an MVC version.
return new MvcExtensibilityConfiguration(
languageVersion,
ProjectExtensibilityConfigurationKind.ApproximateMatch,
new ProjectExtensibilityAssembly(razorAssembly),
new ProjectExtensibilityAssembly(mvcAssembly));
}
// If we get here it means we didn't find everything, so we have to guess.
if (razorAssembly == null || razorAssembly.Version == null)
{
razorAssembly = new AssemblyIdentity(RazorV2AssemblyName, DefaultRazorVersion);
}
if (mvcAssembly == null || mvcAssembly.Version == null)
{
mvcAssembly = new AssemblyIdentity(MvcAssemblyName, DefaultMvcVersion);
}
if (languageVersion == null)
{
languageVersion = GetLanguageVersion(razorAssembly);
}
return new MvcExtensibilityConfiguration(
languageVersion,
ProjectExtensibilityConfigurationKind.Fallback,
new ProjectExtensibilityAssembly(razorAssembly),
new ProjectExtensibilityAssembly(mvcAssembly));
}
// Internal for testing
internal static RazorLanguageVersion GetLanguageVersion(AssemblyIdentity razorAssembly)
{
// This is inferred from the assembly for now, the Razor language version will eventually flow from MSBuild.
var razorAssemblyVersion = razorAssembly.Version;
if (razorAssemblyVersion.Major == 1)
{
if (razorAssemblyVersion.Minor >= 1)
{
return RazorLanguageVersion.Version_1_1;
}
return RazorLanguageVersion.Version_1_0;
}
if (razorAssemblyVersion.Major == 2)
{
if (razorAssemblyVersion.Minor >= 1)
{
return RazorLanguageVersion.Version_2_1;
}
return RazorLanguageVersion.Version_2_0;
}
// Couldn't determine version based off of assembly, fallback to latest.
return RazorLanguageVersion.Latest;
}
}
}

View File

@ -1,25 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Composition;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
[Shared]
[ExportLanguageServiceFactory(typeof(ProjectExtensibilityConfigurationFactory), RazorLanguage.Name)]
internal class DefaultProjectExtensibilityConfigurationFactoryFactory : ILanguageServiceFactory
{
public ILanguageService CreateLanguageService(HostLanguageServices languageServices)
{
if (languageServices == null)
{
throw new ArgumentNullException(nameof(languageServices));
}
return new DefaultProjectExtensibilityConfigurationFactory();
}
}
}

View File

@ -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<TagHelperDescriptor>();
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<TagHelperDescriptor> TagHelpers { get; } = Array.Empty<TagHelperDescriptor>();
@ -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);
}
}
}
}

View File

@ -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<ProjectChangeEventArgs> Changed;
@ -17,8 +33,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
private readonly ProjectSnapshotWorkerQueue _workerQueue;
private readonly ProjectSnapshotWorker _worker;
private readonly Dictionary<ProjectId, DefaultProjectSnapshot> _projects;
private readonly Dictionary<string, DefaultProjectSnapshot> _projects;
public DefaultProjectSnapshotManager(
ForegroundDispatcher foregroundDispatcher,
ErrorReporter errorReporter,
@ -57,7 +73,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
_triggers = triggers.ToArray();
Workspace = workspace;
_projects = new Dictionary<ProjectId, DefaultProjectSnapshot>();
_projects = new Dictionary<string, DefaultProjectSnapshot>(FilePathComparer.Instance);
_workerQueue = new ProjectSnapshotWorkerQueue(_foregroundDispatcher, this, worker);
for (var i = 0; i < _triggers.Length; i++)
@ -70,63 +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);
}
}
}
}

View File

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

View File

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

View File

@ -0,0 +1,78 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Razor.Language;
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
internal class FallbackRazorConfiguration : RazorConfiguration
{
public static readonly RazorConfiguration MVC_1_0 = new FallbackRazorConfiguration(
RazorLanguageVersion.Version_1_0,
"MVC-1.0",
new[] { new FallbackRazorExtension("MVC-1.0"), });
public static readonly RazorConfiguration MVC_1_1 = new FallbackRazorConfiguration(
RazorLanguageVersion.Version_1_1,
"MVC-1.1",
new[] { new FallbackRazorExtension("MVC-1.1"), });
public static readonly RazorConfiguration MVC_2_0 = new FallbackRazorConfiguration(
RazorLanguageVersion.Version_2_0,
"MVC-2.0",
new[] { new FallbackRazorExtension("MVC-2.0"), });
public static RazorConfiguration SelectConfiguration(Version version)
{
if (version.Major == 1 && version.Minor == 0)
{
return MVC_1_0;
}
else if (version.Major == 1 && version.Minor == 1)
{
return MVC_1_1;
}
else if (version.Major == 2 && version.Minor == 0)
{
return MVC_2_0;
}
else
{
return MVC_2_0;
}
}
public FallbackRazorConfiguration(
RazorLanguageVersion languageVersion,
string configurationName,
RazorExtension[] extensions)
{
if (languageVersion == null)
{
throw new ArgumentNullException(nameof(languageVersion));
}
if (configurationName == null)
{
throw new ArgumentNullException(nameof(configurationName));
}
if (extensions == null)
{
throw new ArgumentNullException(nameof(extensions));
}
LanguageVersion = languageVersion;
ConfigurationName = configurationName;
Extensions = extensions;
}
public override string ConfigurationName { get; }
public override IReadOnlyList<RazorExtension> Extensions { get; }
public override RazorLanguageVersion LanguageVersion { get; }
}
}

View File

@ -0,0 +1,23 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Razor.Language;
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
internal class FallbackRazorExtension : RazorExtension
{
public FallbackRazorExtension(string extensionName)
{
if (extensionName == null)
{
throw new ArgumentNullException(nameof(extensionName));
}
ExtensionName = extensionName;
}
public override string ExtensionName { get; }
}
}

View File

@ -0,0 +1,31 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Razor.Language;
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
internal class HostProject
{
public HostProject(string projectFilePath, RazorConfiguration razorConfiguration)
{
if (projectFilePath == null)
{
throw new ArgumentNullException(nameof(projectFilePath));
}
if (razorConfiguration == null)
{
throw new ArgumentNullException(nameof(razorConfiguration));
}
FilePath = projectFilePath;
Configuration = razorConfiguration;
}
public RazorConfiguration Configuration { get; }
public string FilePath { get; }
}
}

View File

@ -1,86 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.Extensions.Internal;
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
internal class MvcExtensibilityConfiguration : ProjectExtensibilityConfiguration
{
public MvcExtensibilityConfiguration(
RazorLanguageVersion languageVersion,
ProjectExtensibilityConfigurationKind kind,
ProjectExtensibilityAssembly razorAssembly,
ProjectExtensibilityAssembly mvcAssembly)
{
if (razorAssembly == null)
{
throw new ArgumentNullException(nameof(razorAssembly));
}
if (mvcAssembly == null)
{
throw new ArgumentNullException(nameof(mvcAssembly));
}
Kind = kind;
RazorAssembly = razorAssembly;
MvcAssembly = mvcAssembly;
LanguageVersion = languageVersion;
Assemblies = new[] { RazorAssembly, MvcAssembly, };
}
public override IReadOnlyList<ProjectExtensibilityAssembly> Assemblies { get; }
// MVC: '2.0.0' (fallback) | Razor Language '2.0.0'
// or
// MVC: '2.1.3' | Razor Language '2.1.3'
public override string DisplayName => $"MVC: {MvcAssembly.Identity.Version.ToString(3)}" + (Kind == ProjectExtensibilityConfigurationKind.Fallback? " (fallback)" : string.Empty) + " | " + LanguageVersion;
public override ProjectExtensibilityConfigurationKind Kind { get; }
public override ProjectExtensibilityAssembly RazorAssembly { get; }
public override RazorLanguageVersion LanguageVersion { get; }
public ProjectExtensibilityAssembly MvcAssembly { get; }
public override bool Equals(ProjectExtensibilityConfiguration other)
{
if (other == null)
{
return false;
}
// We're intentionally ignoring the 'Kind' here. That's mostly for diagnostics and doesn't influence any behavior.
return LanguageVersion == other.LanguageVersion &&
Enumerable.SequenceEqual(
Assemblies.OrderBy(a => a.Identity.Name).Select(a => a.Identity),
other.Assemblies.OrderBy(a => a.Identity.Name).Select(a => a.Identity),
AssemblyIdentityEqualityComparer.NameAndVersion);
}
public override int GetHashCode()
{
var hash = new HashCodeCombiner();
foreach (var assembly in Assemblies.OrderBy(a => a.Identity.Name))
{
hash.Add(assembly);
}
hash.Add(LanguageVersion);
return hash;
}
public override string ToString()
{
return DisplayName;
}
}
}

View File

@ -1,31 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Razor.Language;
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
internal abstract class ProjectExtensibilityConfiguration : IEquatable<ProjectExtensibilityConfiguration>
{
public abstract IReadOnlyList<ProjectExtensibilityAssembly> Assemblies { get; }
public abstract string DisplayName { get; }
public abstract ProjectExtensibilityConfigurationKind Kind { get; }
public abstract ProjectExtensibilityAssembly RazorAssembly { get; }
public abstract RazorLanguageVersion LanguageVersion { get; }
public abstract bool Equals(ProjectExtensibilityConfiguration other);
public abstract override int GetHashCode();
public override bool Equals(object obj)
{
return Equals(obj as ProjectExtensibilityConfiguration);
}
}
}

View File

@ -1,14 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host;
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
internal abstract class ProjectExtensibilityConfigurationFactory : ILanguageService
{
public abstract Task<ProjectExtensibilityConfiguration> GetConfigurationAsync(Project project, CancellationToken cancellationToken = default(CancellationToken));
}
}

View File

@ -1,14 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
/// <summary>
/// Describes how closely the configuration of Razor tooling matches the actual project dependencies.
/// </summary>
internal enum ProjectExtensibilityConfigurationKind
{
ApproximateMatch,
Fallback,
}
}

View File

@ -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<TagHelperDescriptor> TagHelpers { get; }
public abstract VersionStamp Version { get; }
public abstract Project WorkspaceProject { get; }
public abstract HostProject HostProject { get; }
}
}
}

View File

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

View File

@ -13,7 +13,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
for (var i = 0; i< projects.Count; i++)
{
var project = projects[i];
if (string.Equals(filePath, project.UnderlyingProject.FilePath, StringComparison.OrdinalIgnoreCase))
if (FilePathComparer.Instance.Equals(filePath, project.FilePath))
{
return project;
}

View File

@ -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<TagHelperDescriptor> TagHelpers { get; set; }
public VersionStamp Version { get; }
}
}

View File

@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@ -16,7 +15,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
private readonly DefaultProjectSnapshotManager _projectManager;
private readonly ProjectSnapshotWorker _projectWorker;
private readonly Dictionary<ProjectId, Project> _projects;
private readonly Dictionary<string, ProjectSnapshotUpdateContext> _projects;
private Timer _timer;
public ProjectSnapshotWorkerQueue(ForegroundDispatcher foregroundDispatcher, DefaultProjectSnapshotManager projectManager, ProjectSnapshotWorker projectWorker)
@ -40,7 +39,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
_projectManager = projectManager;
_projectWorker = projectWorker;
_projects = new Dictionary<ProjectId, Project>();
_projects = new Dictionary<string, ProjectSnapshotUpdateContext>(FilePathComparer.Instance);
}
public bool HasPendingNotifications
@ -93,11 +92,11 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
}
}
public void Enqueue(Project project)
public void Enqueue(ProjectSnapshotUpdateContext context)
{
if (project == null)
if (context == null)
{
throw new ArgumentNullException();
throw new ArgumentNullException(nameof(context));
}
_foregroundDispatcher.AssertForegroundThread();
@ -106,7 +105,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
// We only want to store the last 'seen' version of any given project. That way when we pick one to process
// it's always the best version to use.
_projects[project.Id] = project;
_projects[context.FilePath] = context;
StartWorker();
}
@ -133,7 +132,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
OnStartingBackgroundWork();
Project[] work;
ProjectSnapshotUpdateContext[] work;
lock (_projects)
{
work = _projects.Values.ToArray();
@ -145,7 +144,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
try
{
updates[i] = (new ProjectSnapshotUpdateContext(work[i]), null);
updates[i] = (work[i], null);
await _projectWorker.ProcessUpdateAsync(updates[i].context);
}
catch (Exception projectException)
@ -196,7 +195,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
}
else
{
_projectManager.ReportError(update.exception, update.context?.UnderlyingProject);
_projectManager.ReportError(update.exception, update.context?.WorkspaceProject);
}
}
}

View File

@ -0,0 +1,43 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using Microsoft.AspNetCore.Razor.Language;
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
internal class ProjectSystemRazorConfiguration : RazorConfiguration
{
public ProjectSystemRazorConfiguration(
RazorLanguageVersion languageVersion,
string configurationName,
RazorExtension[] extensions)
{
if (languageVersion == null)
{
throw new ArgumentNullException(nameof(languageVersion));
}
if (configurationName == null)
{
throw new ArgumentNullException(nameof(configurationName));
}
if (extensions == null)
{
throw new ArgumentNullException(nameof(extensions));
}
LanguageVersion = languageVersion;
ConfigurationName = configurationName;
Extensions = extensions;
}
public override string ConfigurationName { get; }
public override IReadOnlyList<RazorExtension> Extensions { get; }
public override RazorLanguageVersion LanguageVersion { get; }
}
}

View File

@ -0,0 +1,23 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Razor.Language;
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
internal class ProjectSystemRazorExtension : RazorExtension
{
public ProjectSystemRazorExtension(string extensionName)
{
if (extensionName == null)
{
throw new ArgumentNullException(nameof(extensionName));
}
ExtensionName = extensionName;
}
public override string ExtensionName { get; }
}
}

View File

@ -23,51 +23,43 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
Debug.Assert(solution != null);
_projectManager.ProjectsCleared();
foreach (var project in solution.Projects)
{
if (project.Language == LanguageNames.CSharp)
{
_projectManager.ProjectAdded(project);
}
_projectManager.WorkspaceProjectAdded(project);
}
}
// Internal for testing
internal void Workspace_WorkspaceChanged(object sender, WorkspaceChangeEventArgs e)
{
Project underlyingProject;
Project project;
switch (e.Kind)
{
case WorkspaceChangeKind.ProjectAdded:
{
underlyingProject = e.NewSolution.GetProject(e.ProjectId);
Debug.Assert(underlyingProject != null);
project = e.NewSolution.GetProject(e.ProjectId);
Debug.Assert(project != null);
if (underlyingProject.Language == LanguageNames.CSharp)
{
_projectManager.ProjectAdded(underlyingProject);
}
_projectManager.WorkspaceProjectAdded(project);
break;
}
case WorkspaceChangeKind.ProjectChanged:
case WorkspaceChangeKind.ProjectReloaded:
{
underlyingProject = e.NewSolution.GetProject(e.ProjectId);
Debug.Assert(underlyingProject != null);
project = e.NewSolution.GetProject(e.ProjectId);
Debug.Assert(project != null);
_projectManager.ProjectChanged(underlyingProject);
_projectManager.WorkspaceProjectChanged(project);
break;
}
case WorkspaceChangeKind.ProjectRemoved:
{
underlyingProject = e.OldSolution.GetProject(e.ProjectId);
Debug.Assert(underlyingProject != null);
project = e.OldSolution.GetProject(e.ProjectId);
Debug.Assert(project != null);
_projectManager.ProjectRemoved(underlyingProject);
_projectManager.WorkspaceProjectRemoved(project);
break;
}
@ -76,6 +68,15 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
case WorkspaceChangeKind.SolutionCleared:
case WorkspaceChangeKind.SolutionReloaded:
case WorkspaceChangeKind.SolutionRemoved:
if (e.OldSolution != null)
{
foreach (var p in e.OldSolution.Projects)
{
_projectManager.WorkspaceProjectRemoved(p);
}
}
InitializeSolution(e.NewSolution);
break;
}

View File

@ -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")]

View File

@ -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<RazorProjectEngineBuilder> configure);
public abstract IProjectEngineFactory FindFactory(ProjectSnapshot project);
public abstract IProjectEngineFactory FindSerializableFactory(ProjectSnapshot project);
public abstract RazorProjectEngine Create(ProjectSnapshot project, Action<RazorProjectEngineBuilder> configure);
public abstract RazorProjectEngine Create(ProjectSnapshot project, RazorProjectFileSystem fileSystem, Action<RazorProjectEngineBuilder> configure);
public abstract RazorProjectEngine Create(string directoryPath, Action<RazorProjectEngineBuilder> configure);
}
}

View File

@ -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<TagHelperDescriptor>(), Array.Empty<RazorDiagnostic>());
public TagHelperResolutionResult(IReadOnlyList<TagHelperDescriptor> descriptors, IReadOnlyList<RazorDiagnostic> diagnostics)
{
Descriptors = descriptors;

View File

@ -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<TagHelperResolutionResult> GetTagHelpersAsync(Project project, CancellationToken cancellationToken);
public abstract Task<TagHelperResolutionResult> GetTagHelpersAsync(ProjectSnapshot project, CancellationToken cancellationToken = default);
protected virtual async Task<TagHelperResolutionResult> 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<ITagHelperDescriptorProvider>().ToArray();
if (providers.Length == 0)
{
return TagHelperResolutionResult.Empty;
}
var results = new List<TagHelperDescriptor>();
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<RazorDiagnostic>());
}
}
}

View File

@ -0,0 +1,30 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Runtime.InteropServices;
namespace Microsoft.CodeAnalysis.Razor
{
internal static class FilePathComparer
{
private static StringComparer _instance;
public static StringComparer Instance
{
get
{
if (_instance == null && RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
_instance = StringComparer.Ordinal;
}
else if (_instance == null)
{
_instance = StringComparer.OrdinalIgnoreCase;
}
return _instance;
}
}
}
}

View File

@ -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<TagHelperDescriptor>();
var providers = new ITagHelperDescriptorProvider[]
{
new DefaultTagHelperDescriptorProvider() { DesignTime = DesignTime, },
new ViewComponentTagHelperDescriptorProvider(),
};
var results = new List<TagHelperDescriptor>();
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<RazorDiagnostic>();
var resolutionResult = new TagHelperResolutionResult(results, diagnostics);
return resolutionResult;
}
public override async Task<TagHelperResolutionResult> GetTagHelpersAsync(Project project, CancellationToken cancellationToken)
{
var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
return GetTagHelpers(compilation);
}
}
}

View File

@ -7,12 +7,13 @@
</PropertyGroup>
<ItemGroup>
<Compile Include="..\Microsoft.VisualStudio.LanguageServices.Razor\RazorDiagnosticJsonConverter.cs" />
<Compile Include="..\Microsoft.VisualStudio.LanguageServices.Razor\Serialization\*.cs">
<Link>Serialization\%(FileName)%(Extension)</Link>
</Compile>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Microsoft.CodeAnalysis.Razor.Workspaces\Microsoft.CodeAnalysis.Razor.Workspaces.csproj" />
<ProjectReference Include="..\Microsoft.AspNetCore.Mvc.Razor.Extensions\Microsoft.AspNetCore.Mvc.Razor.Extensions.csproj" />
<ProjectReference Include="..\Microsoft.AspNetCore.Razor.Language\Microsoft.AspNetCore.Razor.Language.csproj" />
</ItemGroup>

View File

@ -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<TagHelperResolutionResult> GetTagHelpersAsync(Guid projectIdBytes, string projectDebugName, CancellationToken cancellationToken = default(CancellationToken))
public async Task<TagHelperResolutionResult> 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<IEnumerable<DirectiveDescriptor>> GetDirectivesAsync(Guid projectIdBytes, string projectDebugName, CancellationToken cancellationToken = default(CancellationToken))

View File

@ -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<ProjectSnapshot> 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<TagHelperDescriptor>();
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<TagHelperDescriptor> TagHelpers { get; }
}
}
}

View File

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

View File

@ -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<TagHelperResolutionResult> GetTagHelpersAsync(ProjectSnapshot project, CancellationToken cancellationToken = default)
{
throw new NotImplementedException();
}
public Task<TagHelperResolutionResult> 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<RazorProjectEngineBuilder> 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));
}
}
}

View File

@ -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<IProjectEngineFactory, ICustomProjectEngineFactoryMetadata>[] _customFactories;
private ProjectSnapshotManager _projectManager;
public DefaultProjectEngineFactoryService(ProjectSnapshotManager projectManager)
public DefaultProjectEngineFactoryService(
Workspace workspace,
IFallbackProjectEngineFactory defaultFactory,
Lazy<IProjectEngineFactory, ICustomProjectEngineFactoryMetadata>[] 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<IProjectEngineFactory, ICustomProjectEngineFactoryMetadata>[] 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<RazorProjectEngineBuilder> 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<RazorExtension>());
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<RazorProjectEngineBuilder> 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<RazorProjectEngineBuilder> 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<RazorProjectEngineBuilder> 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<RazorProjectEngineBuilder> 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<ProjectSnapshotManager>();
}
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;
}

View File

@ -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<IProjectEngineFactory, ICustomProjectEngineFactoryMetadata>[] _customFactories;
private readonly IFallbackProjectEngineFactory _fallbackFactory;
[ImportingConstructor]
public DefaultProjectEngineFactoryServiceFactory(
IFallbackProjectEngineFactory fallbackFactory,
[ImportMany] IEnumerable<Lazy<IProjectEngineFactory, ICustomProjectEngineFactoryMetadata>> 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<ProjectSnapshotManager>());
return new DefaultProjectEngineFactoryService(
languageServices.WorkspaceServices.Workspace,
_fallbackFactory,
_customFactories);
}
}
}

View File

@ -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<TagHelperResolutionResult> GetTagHelpersAsync(Project project, CancellationToken cancellationToken)
public DefaultTagHelperResolver(RazorProjectEngineFactoryService engineFactory)
{
if (engineFactory == null)
{
throw new ArgumentNullException(nameof(engineFactory));
}
_engineFactory = engineFactory;
}
public override Task<TagHelperResolutionResult> 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<TagHelperDescriptor>();
var providers = new ITagHelperDescriptorProvider[]
if (project.Configuration == null || project.WorkspaceProject == null)
{
new DefaultTagHelperDescriptorProvider() { DesignTime = true, },
new ViewComponentTagHelperDescriptorProvider() { ForceEnabled = ForceEnableViewComponentDiscovery },
};
var results = new List<TagHelperDescriptor>();
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<RazorDiagnostic>();
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);
}
}
}

View File

@ -14,7 +14,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
{
public ILanguageService CreateLanguageService(HostLanguageServices languageServices)
{
return new DefaultTagHelperResolver();
return new DefaultTagHelperResolver(languageServices.GetRequiredService<RazorProjectEngineFactoryService>());
}
}
}

View File

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

View File

@ -0,0 +1,32 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.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<RazorProjectEngineBuilder> 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);
});
}
}
}

View File

@ -0,0 +1,32 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.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<RazorProjectEngineBuilder> 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);
});
}
}
}

View File

@ -0,0 +1,31 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using 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<RazorProjectEngineBuilder> 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);
});
}
}
}

View File

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

View File

@ -14,8 +14,6 @@
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.CodeAnalysis.Razor.Workspaces\Microsoft.CodeAnalysis.Razor.Workspaces.csproj" />
<ProjectReference Include="..\Microsoft.AspNetCore.Mvc.Razor.Extensions\Microsoft.AspNetCore.Mvc.Razor.Extensions.csproj" />
<ProjectReference Include="..\Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X\Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X.csproj" />
</ItemGroup>
</Project>

View File

@ -6,7 +6,6 @@ using System.Collections.Generic;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Razor.Editor;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
@ -16,7 +15,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
{
public abstract event EventHandler<ContextChangeEventArgs> ContextChanged;
internal abstract ProjectExtensibilityConfiguration Configuration { get; }
public abstract RazorConfiguration Configuration { get; }
public abstract EditorSettings EditorSettings { get; }

View File

@ -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<TagHelperResolutionResult> GetTagHelpersAsync(Project project, CancellationToken cancellationToken)
public override async Task<TagHelperResolutionResult> 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<JObject>(
"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<TagHelperResolutionResult> 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<JObject>("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<TagHelperResolutionResult> 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())
{

View File

@ -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<RazorProjectEngineFactoryService>(),
languageServices.WorkspaceServices.GetRequiredService<ErrorReporter>(),
languageServices.WorkspaceServices.Workspace);
}
}
}

View File

@ -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<IProjectVersionedValue<IProjectSubscriptionUpdate>>(OnProjectChanged);
_subscription = CommonServices.ActiveConfiguredProjectSubscription.JointRuleSource.SourceBlock.LinkTo(
receiver,
initialDataAsNew: true,
suppressVersionOnlyUpdates: true,
ruleNames: new string[] { Rules.RazorGeneral.SchemaName, Rules.RazorConfiguration.SchemaName, Rules.RazorExtension.SchemaName });
}
protected override async Task DisposeCoreAsync(bool initialized)
{
await base.DisposeCoreAsync(initialized).ConfigureAwait(false);
if (initialized)
{
_subscription.Dispose();
}
}
// Internal for testing
internal async Task OnProjectChanged(IProjectVersionedValue<IProjectSubscriptionUpdate> update)
{
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);
}
}
}

View File

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

View File

@ -0,0 +1,32 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using 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; }
}
}

View File

@ -0,0 +1,16 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
// Well-Known Schema and property names defined by the ManagedProjectSystem
internal static class ManageProjectSystemSchema
{
public static class ResolvedCompilationReference
{
public static readonly string SchemaName = "ResolvedCompilationReference";
public static readonly string ItemName = "ResolvedCompilationReference";
}
}
}

View File

@ -0,0 +1,190 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.ComponentModel.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.LanguageServices;
using Microsoft.VisualStudio.ProjectSystem;
using Microsoft.VisualStudio.Threading;
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
internal abstract class RazorProjectHostBase : OnceInitializedOnceDisposedAsync, IProjectDynamicLoadComponent
{
private readonly Workspace _workspace;
private readonly AsyncSemaphore _lock;
private ProjectSnapshotManagerBase _projectManager;
private HostProject _current;
public RazorProjectHostBase(
IUnconfiguredProjectCommonServices commonServices,
[Import(typeof(VisualStudioWorkspace))] Workspace workspace)
: base(commonServices.ThreadingService.JoinableTaskContext)
{
if (commonServices == null)
{
throw new ArgumentNullException(nameof(commonServices));
}
if (workspace == null)
{
throw new ArgumentNullException(nameof(workspace));
}
CommonServices = commonServices;
_workspace = workspace;
_lock = new AsyncSemaphore(initialCount: 1);
}
// Internal for testing
protected RazorProjectHostBase(
IUnconfiguredProjectCommonServices commonServices,
Workspace workspace,
ProjectSnapshotManagerBase projectManager)
: base(commonServices.ThreadingService.JoinableTaskContext)
{
if (commonServices == null)
{
throw new ArgumentNullException(nameof(commonServices));
}
if (workspace == null)
{
throw new ArgumentNullException(nameof(workspace));
}
if (projectManager == null)
{
throw new ArgumentNullException(nameof(projectManager));
}
CommonServices = commonServices;
_workspace = workspace;
_projectManager = projectManager;
_lock = new AsyncSemaphore(initialCount: 1);
}
protected IUnconfiguredProjectCommonServices CommonServices { get; }
// internal for tests. The product will call through the IProjectDynamicLoadComponent interface.
internal Task LoadAsync()
{
return InitializeAsync();
}
protected override Task InitializeCoreAsync(CancellationToken cancellationToken)
{
CommonServices.UnconfiguredProject.ProjectRenaming += UnconfiguredProject_ProjectRenaming;
return Task.CompletedTask;
}
protected override async Task DisposeCoreAsync(bool initialized)
{
if (initialized)
{
CommonServices.UnconfiguredProject.ProjectRenaming -= UnconfiguredProject_ProjectRenaming;
await ExecuteWithLock(async () =>
{
if (_current != null)
{
await UpdateProjectUnsafeAsync(null).ConfigureAwait(false);
}
});
}
}
// Internal for tests
internal async Task OnProjectRenamingAsync()
{
// When a project gets renamed we expect any rules watched by the derived class to fire.
//
// However, the project snapshot manager uses the project Fullpath as the key. We want to just
// reinitialize the HostProject with the same configuration and settings here, but the updated
// FilePath.
await ExecuteWithLock(async () =>
{
if (_current != null)
{
var old = _current;
await UpdateProjectUnsafeAsync(null).ConfigureAwait(false);
var filePath = CommonServices.UnconfiguredProject.FullPath;
await UpdateProjectUnsafeAsync(new HostProject(filePath, old.Configuration)).ConfigureAwait(false);
}
});
}
// Should only be called from the UI thread.
private ProjectSnapshotManagerBase GetProjectManager()
{
CommonServices.ThreadingService.VerifyOnUIThread();
if (_projectManager == null)
{
_projectManager = (ProjectSnapshotManagerBase)_workspace.Services.GetLanguageServices(RazorLanguage.Name).GetRequiredService<ProjectSnapshotManager>();
}
return _projectManager;
}
// Must be called inside the lock.
protected async Task UpdateProjectUnsafeAsync(HostProject project)
{
await CommonServices.ThreadingService.SwitchToUIThread();
var projectManager = GetProjectManager();
if (_current == null && project == null)
{
// This is a no-op. This project isn't using Razor.
}
else if (_current == null && project != null)
{
projectManager.HostProjectAdded(project);
}
else if (_current != null && project == null)
{
projectManager.HostProjectRemoved(_current);
}
else
{
projectManager.HostProjectChanged(project);
}
_current = project;
}
protected async Task ExecuteWithLock(Func<Task> func)
{
using (JoinableCollection.Join())
{
using (await _lock.EnterAsync().ConfigureAwait(false))
{
var task = JoinableFactory.RunAsync(func);
await task.Task.ConfigureAwait(false);
}
}
}
Task IProjectDynamicLoadComponent.LoadAsync()
{
return InitializeAsync();
}
Task IProjectDynamicLoadComponent.UnloadAsync()
{
return DisposeAsync();
}
private async Task UnconfiguredProject_ProjectRenaming(object sender, ProjectRenamedEventArgs args)
{
await OnProjectRenamingAsync().ConfigureAwait(false);
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<string>();
var configuration = obj[nameof(ProjectSnapshotHandle.Configuration)].ToObject<RazorConfiguration>(serializer);
var id = obj[nameof(ProjectSnapshotHandle.WorkspaceProjectId)].Value<string>();
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();
}
}
}

View File

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

View File

@ -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<string>();
var languageVersion = obj[nameof(RazorConfiguration.LanguageVersion)].Value<string>();
var extensions = obj[nameof(RazorConfiguration.Extensions)].ToObject<RazorExtension[]>(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();
}
}
}

View File

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

View File

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

View File

@ -0,0 +1,23 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Razor.Language;
namespace Microsoft.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; }
}
}

View File

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

View File

@ -4,6 +4,7 @@
using System;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Razor;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using Microsoft.VisualStudio.Shell.Interop;
namespace Microsoft.VisualStudio.LanguageServices.Razor
@ -40,7 +41,7 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
}
}
public override void ReportError(Exception exception, Project project)
public override void ReportError(Exception exception, ProjectSnapshot project)
{
if (exception == null)
{
@ -53,7 +54,25 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
var hr = activityLog.LogEntry(
(uint)__ACTIVITYLOG_ENTRYTYPE.ALE_ERROR,
"Razor Language Services",
$"Error encountered from project '{project?.Name}':{Environment.NewLine}{exception}");
$"Error encountered from project '{project?.FilePath}':{Environment.NewLine}{exception}");
ErrorHandler.ThrowOnFailure(hr);
}
}
public override void ReportError(Exception exception, Project workspaceProject)
{
if (exception == null)
{
return;
}
var activityLog = GetActivityLog();
if (activityLog != null)
{
var hr = activityLog.LogEntry(
(uint)__ACTIVITYLOG_ENTRYTYPE.ALE_ERROR,
"Razor Language Services",
$"Error encountered from project '{workspaceProject?.Name}' '{workspaceProject?.FilePath}':{Environment.NewLine}{exception}");
ErrorHandler.ThrowOnFailure(hr);
}
}

View File

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

View File

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

View File

@ -52,6 +52,22 @@ namespace Microsoft.VisualStudio.Mac.LanguageServices.Razor
internal static string FormatRazorLanguageServiceProjectError(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("RazorLanguageServiceProjectError"), p0);
/// <summary>
/// Error encountered from project '{0}':
/// {1}
/// </summary>
internal static string RazorLanguageServiceProjectSnapshotError
{
get => GetString("RazorLanguageServiceProjectSnapshotError");
}
/// <summary>
/// Error encountered from project '{0}':
/// {1}
/// </summary>
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);

View File

@ -126,4 +126,8 @@
<data name="RazorLanguageServiceProjectError" xml:space="preserve">
<value>Razor Language Service error encountered from project '{0}'.</value>
</data>
<data name="RazorLanguageServiceProjectSnapshotError" xml:space="preserve">
<value>Error encountered from project '{0}':
{1}</value>
</data>
</root>

View File

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

View File

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

View File

@ -1,245 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNetCore.Razor.Language;
using Xunit;
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
public class DefaultProjectExtensibilityConfigurationFactoryTest
{
public static TheoryData LanguageVersionMappingData
{
get
{
return new TheoryData<AssemblyIdentity, RazorLanguageVersion>
{
{ new AssemblyIdentity("Microsoft.AspNetCore.Razor", new Version("1.0.0.0")), RazorLanguageVersion.Version_1_0 },
{ new AssemblyIdentity("Microsoft.AspNetCore.Razor", new Version("1.1.0.0")), RazorLanguageVersion.Version_1_1 },
{ new AssemblyIdentity("Microsoft.AspNetCore.Razor", new Version("2.0.0.0")), RazorLanguageVersion.Version_2_0 },
{ new AssemblyIdentity("Microsoft.AspNetCore.Razor", new Version("2.1.0.0")), RazorLanguageVersion.Version_2_1 },
};
}
}
[Theory]
[MemberData(nameof(LanguageVersionMappingData))]
public void GetLanguageVersion_MapsExactVersionsCorrectly(AssemblyIdentity assemblyIdentity, RazorLanguageVersion expectedVersion)
{
// Act
var languageVersion = DefaultProjectExtensibilityConfigurationFactory.GetLanguageVersion(assemblyIdentity);
// Assert
Assert.Same(expectedVersion, languageVersion);
}
[Fact]
public void GetLanguageVersion_MapsFuture_1_0_VersionsCorrectly()
{
// Arrange
var assemblyIdentity = new AssemblyIdentity("Microsoft.AspNetCore.Razor", new Version("1.3.0.0"));
// Act
var languageVersion = DefaultProjectExtensibilityConfigurationFactory.GetLanguageVersion(assemblyIdentity);
// Assert
Assert.Same(RazorLanguageVersion.Version_1_1, languageVersion);
}
[Fact]
public void GetLanguageVersion_MapsFuture_2_0_VersionsCorrectly()
{
// Arrange
var assemblyIdentity = new AssemblyIdentity("Microsoft.AspNetCore.Razor", new Version("2.3.0.0"));
// Act
var languageVersion = DefaultProjectExtensibilityConfigurationFactory.GetLanguageVersion(assemblyIdentity);
// Assert
Assert.Same(RazorLanguageVersion.Latest, languageVersion);
}
[Theory]
[InlineData("1.0.0.0", "1.0.0.0")]
[InlineData("1.1.0.0", "1.1.0.0")]
[InlineData("2.0.0.0", "2.0.0.0")]
[InlineData("2.0.2.0", "2.0.2.0")]
public void GetConfiguration_FindsSupportedConfiguration_ForNewRazor(string razorVersion, string mvcVersion)
{
// Arrange
var references = new AssemblyIdentity[]
{
new AssemblyIdentity("Microsoft.AspNetCore.Razor.Language", new Version(razorVersion)),
new AssemblyIdentity("Microsoft.AspNetCore.Mvc.Razor", new Version(mvcVersion)),
};
var factory = new DefaultProjectExtensibilityConfigurationFactory();
// Act
var result = factory.GetConfiguration(references);
// Assert
var configuration = Assert.IsType<MvcExtensibilityConfiguration>(result);
Assert.Equal(ProjectExtensibilityConfigurationKind.ApproximateMatch, configuration.Kind);
Assert.Equal(razorVersion, configuration.RazorAssembly.Identity.Version.ToString());
Assert.Equal(mvcVersion, configuration.MvcAssembly.Identity.Version.ToString());
}
[Theory]
[InlineData("1.0.0.0", "1.0.0.0")]
[InlineData("1.1.0.0", "1.1.0.0")]
[InlineData("1.9.9.9", "2.0.0.0")] // MVC version is ignored
public void GetConfiguration_FindsSupportedConfiguration_ForOldRazor(string razorVersion, string mvcVersion)
{
// Arrange
var references = new AssemblyIdentity[]
{
new AssemblyIdentity("Microsoft.AspNetCore.Razor", new Version(razorVersion)),
new AssemblyIdentity("Microsoft.AspNetCore.Mvc.Razor", new Version(mvcVersion)),
};
var factory = new DefaultProjectExtensibilityConfigurationFactory();
// Act
var result = factory.GetConfiguration(references);
// Assert
var configuration = Assert.IsType<MvcExtensibilityConfiguration>(result);
Assert.Equal(ProjectExtensibilityConfigurationKind.ApproximateMatch, configuration.Kind);
Assert.Equal(razorVersion, configuration.RazorAssembly.Identity.Version.ToString());
Assert.Equal(mvcVersion, configuration.MvcAssembly.Identity.Version.ToString());
}
[Fact]
public void GetConfiguration_RazorVersion_NewAssemblyWinsOverOld()
{
// Arrange
var references = new AssemblyIdentity[]
{
new AssemblyIdentity("Microsoft.AspNetCore.Razor", new Version("1.0.0.0")),
new AssemblyIdentity("Microsoft.AspNetCore.Razor.Language", new Version("2.0.0.0")),
new AssemblyIdentity("Microsoft.AspNetCore.Mvc.Razor", new Version("2.0.0.0")),
};
var factory = new DefaultProjectExtensibilityConfigurationFactory();
// Act
var result = factory.GetConfiguration(references);
// Assert
var configuration = Assert.IsType<MvcExtensibilityConfiguration>(result);
Assert.Equal(ProjectExtensibilityConfigurationKind.ApproximateMatch, configuration.Kind);
Assert.Equal("2.0.0.0", configuration.RazorAssembly.Identity.Version.ToString());
Assert.Equal("2.0.0.0", configuration.MvcAssembly.Identity.Version.ToString());
}
[Fact]
public void GetConfiguration_RazorVersion_OldAssemblyIgnoredPastV1()
{
// Arrange
var references = new AssemblyIdentity[]
{
new AssemblyIdentity("Microsoft.AspNetCore.Razor", new Version("2.0.0.0")),
new AssemblyIdentity("Microsoft.AspNetCore.Mvc.Razor", new Version("2.0.0.0")),
};
var factory = new DefaultProjectExtensibilityConfigurationFactory();
// Act
var result = factory.GetConfiguration(references);
// Assert
var configuration = Assert.IsType<MvcExtensibilityConfiguration>(result);
Assert.Equal(ProjectExtensibilityConfigurationKind.Fallback, configuration.Kind);
Assert.Equal("2.0.0.0", configuration.RazorAssembly.Identity.Version.ToString());
Assert.Equal("2.0.0.0", configuration.MvcAssembly.Identity.Version.ToString());
}
[Fact]
public void GetConfiguration_NoRazorVersion_ChoosesDefault()
{
// Arrange
var references = new AssemblyIdentity[]
{
new AssemblyIdentity("Microsoft.AspNetCore.Mvc.Razor", new Version("2.0.0.0")),
};
var factory = new DefaultProjectExtensibilityConfigurationFactory();
// Act
var result = factory.GetConfiguration(references);
// Assert
var configuration = Assert.IsType<MvcExtensibilityConfiguration>(result);
Assert.Equal(ProjectExtensibilityConfigurationKind.Fallback, configuration.Kind);
Assert.Equal("2.0.0.0", configuration.RazorAssembly.Identity.Version.ToString());
Assert.Equal("2.0.0.0", configuration.MvcAssembly.Identity.Version.ToString());
}
[Fact]
public void GetConfiguration_UnsupportedRazorVersion_ChoosesDefault()
{
// Arrange
var references = new AssemblyIdentity[]
{
new AssemblyIdentity("Microsoft.AspNetCore.Razor.Language", new Version("3.0.0.0")),
new AssemblyIdentity("Microsoft.AspNetCore.Mvc.Razor", new Version("2.0.0.0")),
};
var factory = new DefaultProjectExtensibilityConfigurationFactory();
// Act
var result = factory.GetConfiguration(references);
// Assert
var configuration = Assert.IsType<MvcExtensibilityConfiguration>(result);
Assert.Equal(ProjectExtensibilityConfigurationKind.Fallback, configuration.Kind);
Assert.Equal("2.0.0.0", configuration.RazorAssembly.Identity.Version.ToString());
Assert.Equal("2.0.0.0", configuration.MvcAssembly.Identity.Version.ToString());
}
[Fact]
public void GetConfiguration_NoMvcVersion_ChoosesDefault()
{
// Arrange
var references = new AssemblyIdentity[]
{
new AssemblyIdentity("Microsoft.AspNetCore.Razor.Language", new Version("2.0.0.0")),
};
var factory = new DefaultProjectExtensibilityConfigurationFactory();
// Act
var result = factory.GetConfiguration(references);
// Assert
var configuration = Assert.IsType<MvcExtensibilityConfiguration>(result);
Assert.Equal(ProjectExtensibilityConfigurationKind.Fallback, configuration.Kind);
Assert.Equal("2.0.0.0", configuration.RazorAssembly.Identity.Version.ToString());
Assert.Equal("2.0.0.0", configuration.MvcAssembly.Identity.Version.ToString());
}
[Fact]
public void GetConfiguration_UnsupportedMvcVersion_ChoosesDefault()
{
// Arrange
var references = new AssemblyIdentity[]
{
new AssemblyIdentity("Microsoft.AspNetCore.Razor.Language", new Version("2.0.0.0")),
new AssemblyIdentity("Microsoft.AspNetCore.Mvc.Razor", new Version("3.0.0.0")),
};
var factory = new DefaultProjectExtensibilityConfigurationFactory();
// Act
var result = factory.GetConfiguration(references);
// Assert
var configuration = Assert.IsType<MvcExtensibilityConfiguration>(result);
Assert.Equal(ProjectExtensibilityConfigurationKind.Fallback, configuration.Kind);
Assert.Equal("2.0.0.0", configuration.RazorAssembly.Identity.Version.ToString());
Assert.Equal("2.0.0.0", configuration.MvcAssembly.Identity.Version.ToString());
}
}
}

View File

@ -1,331 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Linq;
using Moq;
using Xunit;
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
public class DefaultProjectSnapshotManagerTest
{
public DefaultProjectSnapshotManagerTest()
{
Workspace = TestWorkspace.Create();
ProjectManager = new TestProjectSnapshotManager(Enumerable.Empty<ProjectSnapshotChangeTrigger>(), Workspace);
}
private TestProjectSnapshotManager ProjectManager { get; }
private Workspace Workspace { get; }
[Fact]
public void ProjectAdded_AddsProject_NotifiesListeners_AndStartsBackgroundWorker()
{
// Arrange
var project = Workspace.CurrentSolution.AddProject("Test", "Test", LanguageNames.CSharp);
// Act
ProjectManager.ProjectAdded(project);
// Assert
var snapshot = ProjectManager.GetSnapshot(project.Id);
Assert.True(snapshot.IsDirty);
Assert.True(ProjectManager.ListenersNotified);
Assert.True(ProjectManager.WorkerStarted);
}
[Fact]
public void ProjectChanged_MadeDirty_RetainsComputedState_NotifiesListeners_AndStartsBackgroundWorker()
{
// Arrange
var project = Workspace.CurrentSolution.AddProject("Test", "Test", LanguageNames.CSharp);
ProjectManager.ProjectAdded(project);
ProjectManager.Reset();
// Adding some computed state
var configuration = Mock.Of<ProjectExtensibilityConfiguration>();
ProjectManager.ProjectUpdated(new ProjectSnapshotUpdateContext(project) { Configuration = configuration });
ProjectManager.Reset();
project = project.WithAssemblyName("Test1"); // Simulate a project change
// Act
ProjectManager.ProjectChanged(project);
// Assert
var snapshot = ProjectManager.GetSnapshot(project.Id);
Assert.True(snapshot.IsDirty);
Assert.Same(configuration, snapshot.Configuration);
Assert.False(ProjectManager.ListenersNotified);
Assert.True(ProjectManager.WorkerStarted);
}
[Fact]
public void ProjectChanged_BackgroundUpdate_MadeClean_WithSignificantChanges_NotifiesListeners_AndDoesNotStartBackgroundWorker()
{
// Arrange
var project = Workspace.CurrentSolution.AddProject("Test", "Test", LanguageNames.CSharp);
ProjectManager.ProjectAdded(project);
ProjectManager.Reset();
var configuration = Mock.Of<ProjectExtensibilityConfiguration>();
// Act
ProjectManager.ProjectUpdated(new ProjectSnapshotUpdateContext(project) { Configuration = configuration });
// Assert
var snapshot = ProjectManager.GetSnapshot(project.Id);
Assert.False(snapshot.IsDirty);
Assert.Same(configuration, snapshot.Configuration);
Assert.True(ProjectManager.ListenersNotified);
Assert.False(ProjectManager.WorkerStarted);
}
[Fact]
public void ProjectChanged_BackgroundUpdate_MadeClean_WithoutSignificantChanges_NotifiesListeners_AndDoesNotStartBackgroundWorker()
{
// Arrange
var project = Workspace.CurrentSolution.AddProject("Test", "Test", LanguageNames.CSharp);
ProjectManager.ProjectAdded(project);
ProjectManager.Reset();
var configuration = Mock.Of<ProjectExtensibilityConfiguration>();
ProjectManager.ProjectUpdated(new ProjectSnapshotUpdateContext(project) { Configuration = configuration });
ProjectManager.Reset();
project = project.WithAssemblyName("Test1"); // Simulate a project change
ProjectManager.ProjectChanged(project);
ProjectManager.Reset();
// Act
ProjectManager.ProjectUpdated(new ProjectSnapshotUpdateContext(project) { Configuration = configuration });
// Assert
var snapshot = ProjectManager.GetSnapshot(project.Id);
Assert.False(snapshot.IsDirty);
Assert.Same(configuration, snapshot.Configuration);
Assert.False(ProjectManager.ListenersNotified);
Assert.False(ProjectManager.WorkerStarted);
}
[Fact]
public void ProjectChanged_BackgroundUpdate_StillDirty_WithSignificantChanges_NotifiesListeners_AndStartsBackgroundWorker()
{
// Arrange
var project = Workspace.CurrentSolution.AddProject("Test", "Test", LanguageNames.CSharp);
ProjectManager.ProjectAdded(project);
ProjectManager.Reset();
var configuration = Mock.Of<ProjectExtensibilityConfiguration>();
// Compute an update for "Test"
var update = new ProjectSnapshotUpdateContext(project) { Configuration = configuration };
project = project.WithAssemblyName("Test1"); // Simulate a project change
ProjectManager.ProjectChanged(project);
ProjectManager.Reset();
// Act
ProjectManager.ProjectUpdated(update);
// Assert
var snapshot = ProjectManager.GetSnapshot(project.Id);
Assert.True(snapshot.IsDirty);
Assert.Same(configuration, snapshot.Configuration);
Assert.True(ProjectManager.ListenersNotified);
Assert.True(ProjectManager.WorkerStarted);
}
[Fact]
public void ProjectChanged_BackgroundUpdate_StillDirty_WithoutSignificantChanges_NotifiesListeners_AndStartsBackgroundWorker()
{
// Arrange
var project = Workspace.CurrentSolution.AddProject("Test", "Test", LanguageNames.CSharp);
ProjectManager.ProjectAdded(project);
ProjectManager.Reset();
var configuration = Mock.Of<ProjectExtensibilityConfiguration>();
ProjectManager.ProjectUpdated(new ProjectSnapshotUpdateContext(project) { Configuration = configuration });
project = project.WithAssemblyName("Test1"); // Simulate a project change
ProjectManager.ProjectChanged(project);
ProjectManager.Reset();
// Compute an update for "Test1"
var update = new ProjectSnapshotUpdateContext(project) { Configuration = configuration };
project = project.WithAssemblyName("Test2"); // Simulate a project change
ProjectManager.ProjectChanged(project);
ProjectManager.Reset();
// Act
ProjectManager.ProjectUpdated(update); // Still dirty because the project changed while computing the update
// Assert
var snapshot = ProjectManager.GetSnapshot(project.Id);
Assert.True(snapshot.IsDirty);
Assert.Same(configuration, snapshot.Configuration);
Assert.False(ProjectManager.ListenersNotified);
Assert.True(ProjectManager.WorkerStarted);
}
[Fact]
public void ProjectChanged_IgnoresUnknownProject()
{
// Arrange
var project = Workspace.CurrentSolution.AddProject("Test", "Test", LanguageNames.CSharp);
// Act
ProjectManager.ProjectChanged(project);
// Assert
Assert.Empty(ProjectManager.Projects);
Assert.False(ProjectManager.ListenersNotified);
Assert.False(ProjectManager.WorkerStarted);
}
[Fact]
public void ProjectChanged_WithComputedState_IgnoresUnknownProject()
{
// Arrange
var project = Workspace.CurrentSolution.AddProject("Test", "Test", LanguageNames.CSharp);
// Act
ProjectManager.ProjectUpdated(new ProjectSnapshotUpdateContext(project));
// Assert
Assert.Empty(ProjectManager.Projects);
Assert.False(ProjectManager.ListenersNotified);
Assert.False(ProjectManager.WorkerStarted);
}
[Fact]
public void ProjectBuildComplete_KnownProject_NotifiesBackgroundWorker()
{
// Arrange
var project = Workspace.CurrentSolution.AddProject("Test", "Test", LanguageNames.CSharp);
ProjectManager.ProjectAdded(project);
ProjectManager.Reset();
// Act
ProjectManager.ProjectBuildComplete(project);
// Assert
Assert.False(ProjectManager.ListenersNotified);
Assert.True(ProjectManager.WorkerStarted);
}
[Fact]
public void ProjectBuildComplete_IgnoresUnknownProject()
{
// Arrange
var project = Workspace.CurrentSolution.AddProject("Test", "Test", LanguageNames.CSharp);
// Act
ProjectManager.ProjectBuildComplete(project);
// Assert
Assert.Empty(ProjectManager.Projects);
Assert.False(ProjectManager.ListenersNotified);
Assert.False(ProjectManager.WorkerStarted);
}
[Fact]
public void ProjectRemoved_RemovesProject_NotifiesListeners_DoesNotStartBackgroundWorker()
{
// Arrange
var project = Workspace.CurrentSolution.AddProject("Test", "Test", LanguageNames.CSharp);
ProjectManager.ProjectAdded(project);
ProjectManager.Reset();
// Act
ProjectManager.ProjectRemoved(project);
// Assert
Assert.Empty(ProjectManager.Projects);
Assert.True(ProjectManager.ListenersNotified);
Assert.False(ProjectManager.WorkerStarted);
}
[Fact]
public void ProjectRemoved_IgnoresUnknownProject()
{
// Arrange
var project = Workspace.CurrentSolution.AddProject("Test", "Test", LanguageNames.CSharp);
// Act
ProjectManager.ProjectRemoved(project);
// Assert
Assert.Empty(ProjectManager.Projects);
Assert.False(ProjectManager.ListenersNotified);
Assert.False(ProjectManager.WorkerStarted);
}
[Fact]
public void ProjectsCleared_RemovesProject_NotifiesListeners_DoesNotStartBackgroundWorker()
{
// Arrange
var project = Workspace.CurrentSolution.AddProject("Test", "Test", LanguageNames.CSharp);
ProjectManager.ProjectAdded(project);
ProjectManager.Reset();
// Act
ProjectManager.ProjectsCleared();
// Assert
Assert.Empty(ProjectManager.Projects);
Assert.True(ProjectManager.ListenersNotified);
Assert.False(ProjectManager.WorkerStarted);
}
private class TestProjectSnapshotManager : DefaultProjectSnapshotManager
{
public TestProjectSnapshotManager(IEnumerable<ProjectSnapshotChangeTrigger> triggers, Workspace workspace)
: base(Mock.Of<ForegroundDispatcher>(), Mock.Of<ErrorReporter>(), Mock.Of<ProjectSnapshotWorker>(), triggers, workspace)
{
}
public bool ListenersNotified { get; private set; }
public bool WorkerStarted { get; private set; }
public DefaultProjectSnapshot GetSnapshot(ProjectId id)
{
return Projects.Cast<DefaultProjectSnapshot>().FirstOrDefault(s => s.UnderlyingProject.Id == id);
}
public void Reset()
{
ListenersNotified = false;
WorkerStarted = false;
}
protected override void NotifyListeners(ProjectChangeEventArgs e)
{
ListenersNotified = true;
}
protected override void NotifyBackgroundWorker(Project project)
{
WorkerStarted = true;
}
}
}
}

View File

@ -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<ProjectExtensibilityConfiguration>(),
TagHelpers = Array.Empty<TagHelperDescriptor>(),
};
// 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 =>

View File

@ -14,33 +14,53 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{
public WorkspaceProjectSnapshotChangeTriggerTest()
{
Solution emptySolution = null;
Project project1 = null;
Project project2 = null;
Project project3 = null;
Solution solutionWithTwoProjects = null;
Solution solutionWithOneProject = null;
Workspace = TestWorkspace.Create();
EmptySolution = Workspace.CurrentSolution.GetIsolatedSolution();
Workspace = TestWorkspace.Create(ws =>
{
emptySolution = ws.CurrentSolution.GetIsolatedSolution();
project1 = ws.CurrentSolution.AddProject("One", "One", LanguageNames.CSharp);
project2 = project1.Solution.AddProject("Two", "Two", LanguageNames.CSharp);
solutionWithTwoProjects = project2.Solution;
var projectId1 = ProjectId.CreateNewId("One");
var projectId2 = ProjectId.CreateNewId("Two");
var projectId3 = ProjectId.CreateNewId("Three");
project3 = emptySolution.GetIsolatedSolution().AddProject("Three", "Three", LanguageNames.CSharp);
solutionWithOneProject = project3.Solution;
});
SolutionWithTwoProjects = Workspace.CurrentSolution
.AddProject(ProjectInfo.Create(
projectId1,
VersionStamp.Default,
"One",
"One",
LanguageNames.CSharp,
filePath: "One.csproj"))
.AddProject(ProjectInfo.Create(
projectId2,
VersionStamp.Default,
"Two",
"Two",
LanguageNames.CSharp,
filePath: "Two.csproj"));
EmptySolution = emptySolution;
ProjectNumberOne = project1;
ProjectNumberTwo = project2;
ProjectNumberThree = project3;
SolutionWithTwoProjects = solutionWithTwoProjects;
SolutionWithOneProject = solutionWithOneProject;
SolutionWithOneProject = EmptySolution.GetIsolatedSolution()
.AddProject(ProjectInfo.Create(
projectId3,
VersionStamp.Default,
"Three",
"Three",
LanguageNames.CSharp,
filePath: "Three.csproj"));
ProjectNumberOne = SolutionWithTwoProjects.GetProject(projectId1);
ProjectNumberTwo = SolutionWithTwoProjects.GetProject(projectId2);
ProjectNumberThree = SolutionWithOneProject.GetProject(projectId3);
HostProjectOne = new HostProject("One.csproj", FallbackRazorConfiguration.MVC_1_1);
HostProjectTwo = new HostProject("Two.csproj", FallbackRazorConfiguration.MVC_1_1);
HostProjectThree = new HostProject("Three.csproj", FallbackRazorConfiguration.MVC_1_1);
}
private HostProject HostProjectOne { get; }
private HostProject HostProjectTwo { get; }
private HostProject HostProjectThree { get; }
private Solution EmptySolution { get; }
private Solution SolutionWithOneProject { get; }
@ -66,7 +86,9 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Arrange
var trigger = new WorkspaceProjectSnapshotChangeTrigger();
var projectManager = new TestProjectSnapshotManager(new[] { trigger }, Workspace);
projectManager.HostProjectAdded(HostProjectOne);
projectManager.HostProjectAdded(HostProjectTwo);
var e = new WorkspaceChangeEventArgs(kind, oldSolution: EmptySolution, newSolution: SolutionWithTwoProjects);
// Act
@ -74,9 +96,9 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Assert
Assert.Collection(
projectManager.Projects.OrderBy(p => p.UnderlyingProject.Name),
p => Assert.Equal(ProjectNumberOne.Id, p.UnderlyingProject.Id),
p => Assert.Equal(ProjectNumberTwo.Id, p.UnderlyingProject.Id));
projectManager.Projects.OrderBy(p => p.WorkspaceProject.Name),
p => Assert.Equal(ProjectNumberOne.Id, p.WorkspaceProject.Id),
p => Assert.Equal(ProjectNumberTwo.Id, p.WorkspaceProject.Id));
}
[Theory]
@ -90,21 +112,25 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Arrange
var trigger = new WorkspaceProjectSnapshotChangeTrigger();
var projectManager = new TestProjectSnapshotManager(new[] { trigger }, Workspace);
projectManager.HostProjectAdded(HostProjectOne);
projectManager.HostProjectAdded(HostProjectTwo);
projectManager.HostProjectAdded(HostProjectThree);
// Initialize with a project. This will get removed.
var e = new WorkspaceChangeEventArgs(WorkspaceChangeKind.SolutionAdded, oldSolution: EmptySolution, newSolution: SolutionWithOneProject);
trigger.Workspace_WorkspaceChanged(Workspace, e);
e = new WorkspaceChangeEventArgs(kind, oldSolution: EmptySolution, newSolution: SolutionWithTwoProjects);
e = new WorkspaceChangeEventArgs(kind, oldSolution: SolutionWithOneProject, newSolution: SolutionWithTwoProjects);
// Act
trigger.Workspace_WorkspaceChanged(Workspace, e);
// Assert
Assert.Collection(
projectManager.Projects.OrderBy(p => p.UnderlyingProject.Name),
p => Assert.Equal(ProjectNumberOne.Id, p.UnderlyingProject.Id),
p => Assert.Equal(ProjectNumberTwo.Id, p.UnderlyingProject.Id));
projectManager.Projects.OrderBy(p => p.WorkspaceProject?.Name),
p => Assert.Null(p.WorkspaceProject),
p => Assert.Equal(ProjectNumberOne.Id, p.WorkspaceProject.Id),
p => Assert.Equal(ProjectNumberTwo.Id, p.WorkspaceProject.Id));
}
[Theory]
@ -115,6 +141,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Arrange
var trigger = new WorkspaceProjectSnapshotChangeTrigger();
var projectManager = new TestProjectSnapshotManager(new[] { trigger }, Workspace);
projectManager.HostProjectAdded(HostProjectOne);
projectManager.HostProjectAdded(HostProjectTwo);
// Initialize with some projects.
var e = new WorkspaceChangeEventArgs(WorkspaceChangeKind.SolutionAdded, oldSolution: EmptySolution, newSolution: SolutionWithTwoProjects);
@ -128,13 +156,13 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Assert
Assert.Collection(
projectManager.Projects.OrderBy(p => p.UnderlyingProject.Name),
projectManager.Projects.OrderBy(p => p.WorkspaceProject.Name),
p =>
{
Assert.Equal(ProjectNumberOne.Id, p.UnderlyingProject.Id);
Assert.Equal("Changed", p.UnderlyingProject.AssemblyName);
Assert.Equal(ProjectNumberOne.Id, p.WorkspaceProject.Id);
Assert.Equal("Changed", p.WorkspaceProject.AssemblyName);
},
p => Assert.Equal(ProjectNumberTwo.Id, p.UnderlyingProject.Id));
p => Assert.Equal(ProjectNumberTwo.Id, p.WorkspaceProject.Id));
}
[Fact]
@ -143,6 +171,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Arrange
var trigger = new WorkspaceProjectSnapshotChangeTrigger();
var projectManager = new TestProjectSnapshotManager(new[] { trigger }, Workspace);
projectManager.HostProjectAdded(HostProjectOne);
projectManager.HostProjectAdded(HostProjectTwo);
// Initialize with some projects project.
var e = new WorkspaceChangeEventArgs(WorkspaceChangeKind.SolutionAdded, oldSolution: EmptySolution, newSolution: SolutionWithTwoProjects);
@ -156,8 +186,9 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Assert
Assert.Collection(
projectManager.Projects.OrderBy(p => p.UnderlyingProject.Name),
p => Assert.Equal(ProjectNumberTwo.Id, p.UnderlyingProject.Id));
projectManager.Projects.OrderBy(p => p.WorkspaceProject?.Name),
p => Assert.Null(p.WorkspaceProject),
p => Assert.Equal(ProjectNumberTwo.Id, p.WorkspaceProject.Id));
}
[Fact]
@ -166,6 +197,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Arrange
var trigger = new WorkspaceProjectSnapshotChangeTrigger();
var projectManager = new TestProjectSnapshotManager(new[] { trigger }, Workspace);
projectManager.HostProjectAdded(HostProjectThree);
var solution = SolutionWithOneProject;
var e = new WorkspaceChangeEventArgs(WorkspaceChangeKind.ProjectAdded, oldSolution: EmptySolution, newSolution: solution, projectId: ProjectNumberThree.Id);
@ -175,19 +207,21 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Assert
Assert.Collection(
projectManager.Projects.OrderBy(p => p.UnderlyingProject.Name),
p => Assert.Equal(ProjectNumberThree.Id, p.UnderlyingProject.Id));
projectManager.Projects.OrderBy(p => p.WorkspaceProject.Name),
p => Assert.Equal(ProjectNumberThree.Id, p.WorkspaceProject.Id));
}
private class TestProjectSnapshotManager : DefaultProjectSnapshotManager
{
public TestProjectSnapshotManager(IEnumerable<ProjectSnapshotChangeTrigger> triggers, Workspace workspace)
public TestProjectSnapshotManager(IEnumerable<ProjectSnapshotChangeTrigger> triggers, Workspace workspace)
: base(Mock.Of<ForegroundDispatcher>(), Mock.Of<ErrorReporter>(), new TestProjectSnapshotWorker(), triggers, workspace)
{
}
protected override void NotifyBackgroundWorker(Project project)
protected override void NotifyBackgroundWorker(ProjectSnapshotUpdateContext context)
{
Assert.NotNull(context.HostProject);
Assert.NotNull(context.WorkspaceProject);
}
}

View File

@ -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<VisualStudioDocumentTracker>(t => t.FilePath == filePath && t.ProjectPath == projectPath);
var anotherTracker = Mock.Of<VisualStudioDocumentTracker>(t => t.FilePath == anotherFilePath && t.ProjectPath == projectPath);
var templateEngineFactoryService = GetProjectEngineFactoryService();
var projectEngineFactoryService = GetProjectEngineFactoryService();
var fileChangeTracker = new Mock<FileChangeTracker>();
fileChangeTracker.Setup(f => f.FilePath).Returns(testImportsPath);
var fileChangeTrackerFactory = new Mock<FileChangeTrackerFactory>();
@ -36,7 +38,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
.Returns(Mock.Of<FileChangeTracker>());
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<ProjectSnapshotManager>();
projectManager.Setup(p => p.Projects).Returns(Array.Empty<ProjectSnapshot>());
var service = new DefaultProjectEngineFactoryService(projectManager.Object);
var projectEngineFactory = new Mock<IFallbackProjectEngineFactory>();
projectEngineFactory.Setup(s => s.Create(It.IsAny<RazorConfiguration>(), It.IsAny<RazorProjectFileSystem>(), It.IsAny<Action<RazorProjectEngineBuilder>>()))
.Returns<RazorConfiguration, RazorProjectFileSystem, Action<RazorProjectEngineBuilder>>(
(c, fs, b) => RazorProjectEngine.Create(
RazorConfiguration.Default,
fs,
builder => RazorExtensions.Register(builder)));
var service = new DefaultProjectEngineFactoryService(
projectManager.Object,
projectEngineFactory.Object,
new Lazy<IProjectEngineFactory, ICustomProjectEngineFactoryMetadata>[0]);
return service;
}
}

View File

@ -2,6 +2,8 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using 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<VisualStudioDocumentTracker>(t => t.FilePath == filePath && t.ProjectPath == projectPath);
var templateEngineFactoryService = GetTemplateEngineFactoryService();
var projectEngineService = GetProjectEngineFactoryService();
var fileChangeTracker1 = new Mock<FileChangeTracker>();
fileChangeTracker1.Setup(f => f.StartListening()).Verifiable();
var fileChangeTrackerFactory = new Mock<FileChangeTrackerFactory>();
@ -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<VisualStudioDocumentTracker>(t => t.FilePath == filePath && t.ProjectPath == projectPath);
var templateEngineFactoryService = GetTemplateEngineFactoryService();
var projectEngineService = GetProjectEngineFactoryService();
var callCount = 0;
var fileChangeTrackerFactory = new Mock<FileChangeTrackerFactory>();
@ -67,7 +69,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
.Returns(Mock.Of<FileChangeTracker>())
.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<VisualStudioDocumentTracker>(t => t.FilePath == filePath && t.ProjectPath == projectPath);
var templateEngineFactoryService = GetTemplateEngineFactoryService();
var projectEngineService = GetProjectEngineFactoryService();
var fileChangeTracker = new Mock<FileChangeTracker>();
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<VisualStudioDocumentTracker>(t => t.FilePath == filePath && t.ProjectPath == projectPath);
var templateEngineFactoryService = GetTemplateEngineFactoryService();
var projectEngineService = GetProjectEngineFactoryService();
var fileChangeTracker = new Mock<FileChangeTracker>();
fileChangeTracker
@ -126,7 +128,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
.Setup(f => f.Create(It.IsAny<string>()))
.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<ProjectSnapshotManager>();
projectManager.Setup(p => p.Projects).Returns(Array.Empty<ProjectSnapshot>());
var service = new DefaultProjectEngineFactoryService(projectManager.Object);
var projectEngineFactory = new Mock<IFallbackProjectEngineFactory>();
projectEngineFactory.Setup(s => s.Create(It.IsAny<RazorConfiguration>(), It.IsAny<RazorProjectFileSystem>(), It.IsAny<Action<RazorProjectEngineBuilder>>()))
.Returns<RazorConfiguration, RazorProjectFileSystem, Action<RazorProjectEngineBuilder>>(
(c, fs, b) => RazorProjectEngine.Create(
RazorConfiguration.Default,
fs,
builder => RazorExtensions.Register(builder)));
var service = new DefaultProjectEngineFactoryService(
projectManager.Object,
projectEngineFactory.Object,
new Lazy<IProjectEngineFactory, ICustomProjectEngineFactoryMetadata>[0]);
return service;
}
}

View File

@ -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<RazorExtension>()));
HostProject_For_UnknownConfiguration = new HostProject(
"/TestPath/SomePath/Test.csproj",
new ProjectSystemRazorConfiguration(RazorLanguageVersion.Version_2_1, "Blazor-0.1", Array.Empty<RazorExtension>()));
CustomFactories = new Lazy<IProjectEngineFactory, ICustomProjectEngineFactoryMetadata>[]
{
new Lazy<IProjectEngineFactory, ICustomProjectEngineFactoryMetadata>(
() => new LegacyProjectEngineFactory_1_0(),
typeof(LegacyProjectEngineFactory_1_0).GetCustomAttribute<ExportCustomProjectEngineFactoryAttribute>()),
new Lazy<IProjectEngineFactory, ICustomProjectEngineFactoryMetadata>(
() => new LegacyProjectEngineFactory_1_1(),
typeof(LegacyProjectEngineFactory_1_1).GetCustomAttribute<ExportCustomProjectEngineFactoryAttribute>()),
new Lazy<IProjectEngineFactory, ICustomProjectEngineFactoryMetadata>(
() => new LegacyProjectEngineFactory_2_0(),
typeof(LegacyProjectEngineFactory_2_0).GetCustomAttribute<ExportCustomProjectEngineFactoryAttribute>()),
new Lazy<IProjectEngineFactory, ICustomProjectEngineFactoryMetadata>(
() => new LegacyProjectEngineFactory_2_1(),
typeof(LegacyProjectEngineFactory_2_1).GetCustomAttribute<ExportCustomProjectEngineFactoryAttribute>()),
};
FallbackFactory = new FallbackProjectEngineFactory();
}
// We don't actually look at the project, we rely on the ProjectStateManager
public Project Project { get; }
private Lazy<IProjectEngineFactory, ICustomProjectEngineFactoryMetadata>[] 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<MyCoolNewFeature>());
Assert.Single(engine.Engine.Features.OfType<MvcLatest.ViewComponentTagHelperDescriptorProvider>());
Assert.Single(engine.Engine.Features.OfType<MvcLatest.MvcViewDocumentClassifierPass>());
Assert.Single(engine.Engine.Features.OfType<MvcLatest.ViewComponentTagHelperPass>());
}
[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<MyCoolNewFeature>());
Assert.Single(engine.Engine.Features.OfType<MvcLatest.ViewComponentTagHelperDescriptorProvider>());
Assert.Single(engine.Engine.Features.OfType<MvcLatest.MvcViewDocumentClassifierPass>());
Assert.Single(engine.Engine.Features.OfType<MvcLatest.ViewComponentTagHelperPass>());
}
@ -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<MyCoolNewFeature>());
Assert.Single(engine.Engine.Features.OfType<Mvc1_X.ViewComponentTagHelperDescriptorProvider>());
Assert.Single(engine.Engine.Features.OfType<Mvc1_X.MvcViewDocumentClassifierPass>());
Assert.Single(engine.Engine.Features.OfType<Mvc1_X.ViewComponentTagHelperPass>());
}
@ -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<MyCoolNewFeature>());
Assert.Single(engine.Engine.Features.OfType<Mvc1_X.MvcViewDocumentClassifierPass>());
Assert.Empty(engine.Engine.Features.OfType<Mvc1_X.ViewComponentTagHelperPass>());
Assert.Empty(engine.Engine.Features.OfType<MvcLatest.ViewComponentTagHelperDescriptorProvider>());
Assert.Empty(engine.Engine.Features.OfType<MvcLatest.MvcViewDocumentClassifierPass>());
Assert.Empty(engine.Engine.Features.OfType<MvcLatest.ViewComponentTagHelperPass>());
}
[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<MyCoolNewFeature>());
Assert.Single(engine.Engine.Features.OfType<MvcLatest.ViewComponentTagHelperDescriptorProvider>());
Assert.Single(engine.Engine.Features.OfType<MvcLatest.MvcViewDocumentClassifierPass>());
Assert.Single(engine.Engine.Features.OfType<MvcLatest.ViewComponentTagHelperPass>());
}
[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<MyCoolNewFeature>());
Assert.Single(engine.Engine.Features.OfType<MvcLatest.MvcViewDocumentClassifierPass>());
Assert.Single(engine.Engine.Features.OfType<MvcLatest.ViewComponentTagHelperPass>());
}
[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<MyCoolNewFeature>());
Assert.Single(engine.Engine.Features.OfType<MvcLatest.MvcViewDocumentClassifierPass>());
Assert.Single(engine.Engine.Features.OfType<MvcLatest.ViewComponentTagHelperPass>());
}
[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<MyCoolNewFeature>());
Assert.Single(engine.Engine.Features.OfType<MvcLatest.MvcViewDocumentClassifierPass>());
Assert.Single(engine.Engine.Features.OfType<MvcLatest.ViewComponentTagHelperPass>());
Assert.Empty(engine.Engine.Features.OfType<DefaultTagHelperDescriptorProvider>());
Assert.Empty(engine.Engine.Features.OfType<MvcLatest.ViewComponentTagHelperDescriptorProvider>());
Assert.Empty(engine.Engine.Features.OfType<MvcLatest.MvcViewDocumentClassifierPass>());
Assert.Empty(engine.Engine.Features.OfType<MvcLatest.ViewComponentTagHelperPass>());
}
private class MyCoolNewFeature : IRazorEngineFeature

View File

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

View File

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

View File

@ -15,6 +15,8 @@
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Razor.Runtime\Microsoft.AspNetCore.Razor.Runtime.csproj" />
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Mvc.Razor.Extensions\Microsoft.AspNetCore.Mvc.Razor.Extensions.csproj" />
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X\Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X.csproj" />
<ProjectReference Include="..\Microsoft.VisualStudio.Editor.Razor.Test.Common\Microsoft.VisualStudio.Editor.Razor.Test.Common.csproj" />
</ItemGroup>

View File

@ -18,6 +18,8 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Mvc.Razor.Extensions\Microsoft.AspNetCore.Mvc.Razor.Extensions.csproj" />
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X\Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X.csproj" />
<ProjectReference Include="..\..\src\Microsoft.VisualStudio.LanguageServices.Razor\Microsoft.VisualStudio.LanguageServices.Razor.csproj" />
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Razor.Runtime\Microsoft.AspNetCore.Razor.Runtime.csproj" />
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Razor\Microsoft.AspNetCore.Razor.csproj" />

Some files were not shown because too many files have changed in this diff Show More