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> <ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Mvc.Razor.Extensions\Microsoft.AspNetCore.Mvc.Razor.Extensions.csproj" /> <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>
<ItemGroup> <ItemGroup>
<Compile Include="..\..\src\Microsoft.VisualStudio.LanguageServices.Razor\RazorDiagnosticJsonConverter.cs"> <Compile Include="..\..\src\Microsoft.VisualStudio.LanguageServices.Razor\Serialization\*.cs">
<Link>Shared\RazorDiagnosticJsonConverter.cs</Link> <Link>Serialization\%(FileName)%(Extension)</Link>
</Compile>
<Compile Include="..\..\src\Microsoft.VisualStudio.LanguageServices.Razor\TagHelperDescriptorJsonConverter.cs">
<Link>Shared\TagHelperDescriptorJsonConverter.cs</Link>
</Compile> </Compile>
</ItemGroup> </ItemGroup>

View File

@ -7,7 +7,7 @@ using System.IO;
using System.Text; using System.Text;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language;
using Microsoft.VisualStudio.LanguageServices.Razor; using Microsoft.VisualStudio.LanguageServices.Razor.Serialization;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace Microsoft.AspNetCore.Razor.Performance namespace Microsoft.AspNetCore.Razor.Performance

View File

@ -70,7 +70,10 @@
</ItemGroup> </ItemGroup>
<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')" /> <LanguageServicePDB Include="%(LanguageServiceAssembly.RootDir)%(Directory)%(FileName).pdb" Condition="Exists('%(LanguageServiceAssembly.RootDir)%(Directory)%(FileName).pdb')" />
</ItemGroup> </ItemGroup>

View File

@ -52,6 +52,7 @@
<VSIX_MicrosoftCodeAnalysisEditorFeaturesTextPackageVersion>2.7.0-beta3-62512-06</VSIX_MicrosoftCodeAnalysisEditorFeaturesTextPackageVersion> <VSIX_MicrosoftCodeAnalysisEditorFeaturesTextPackageVersion>2.7.0-beta3-62512-06</VSIX_MicrosoftCodeAnalysisEditorFeaturesTextPackageVersion>
<VSIX_MicrosoftCodeAnalysisRemoteRazorServiceHubPackageVersion>2.7.0-beta3-62512-06</VSIX_MicrosoftCodeAnalysisRemoteRazorServiceHubPackageVersion> <VSIX_MicrosoftCodeAnalysisRemoteRazorServiceHubPackageVersion>2.7.0-beta3-62512-06</VSIX_MicrosoftCodeAnalysisRemoteRazorServiceHubPackageVersion>
<VSIX_MicrosoftCodeAnalysisWorkspacesCommonPackageVersion>2.7.0-beta3-62512-06</VSIX_MicrosoftCodeAnalysisWorkspacesCommonPackageVersion> <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_MicrosoftVisualStudioLanguageServicesPackageVersion>2.7.0-beta3-62512-06</VSIX_MicrosoftVisualStudioLanguageServicesPackageVersion>
<VSIX_MicrosoftVisualStudioLanguageServicesRazorRemoteClientPackageVersion>2.7.0-beta3-62512-06</VSIX_MicrosoftVisualStudioLanguageServicesRazorRemoteClientPackageVersion> <VSIX_MicrosoftVisualStudioLanguageServicesRazorRemoteClientPackageVersion>2.7.0-beta3-62512-06</VSIX_MicrosoftVisualStudioLanguageServicesRazorRemoteClientPackageVersion>
<XunitAnalyzersPackageVersion>0.8.0</XunitAnalyzersPackageVersion> <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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Runtime.CompilerServices; 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")] [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)); throw new ArgumentNullException(nameof(builder));
} }
builder.Features.Add(new ViewComponentTagHelperDescriptorProvider());
builder.Features.Add(new ViewComponentTagHelperPass()); builder.Features.Add(new ViewComponentTagHelperPass());
builder.AddTargetExtension(new ViewComponentTagHelperTargetExtension()); builder.AddTargetExtension(new ViewComponentTagHelperTargetExtension());
} }
@ -88,6 +90,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions.Version1_X
EnsureDesignTime(builder); EnsureDesignTime(builder);
builder.Features.Add(new ViewComponentTagHelperDescriptorProvider());
builder.Features.Add(new ViewComponentTagHelperPass()); builder.Features.Add(new ViewComponentTagHelperPass());
builder.AddTargetExtension(new ViewComponentTagHelperTargetExtension()); 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; var symbol = compilation.GetAssemblyOrModuleSymbol(reference) as IAssemblySymbol;
if (symbol != null) if (symbol != null)
{ {
if (string.Equals(symbol.Identity.Name, ViewComponentTypes.Assembly, StringComparison.Ordinal) && if (string.Equals(symbol.Identity.Name, ViewComponentTypes.Assembly, StringComparison.Ordinal))
symbol.Identity.Version > ViewComponentTypes.AssemblyVersion)
{ {
enabled = true; enabled = symbol.Identity.Version >= ViewComponentTypes.AssemblyVersion;
break; break;
} }
} }

View File

@ -61,6 +61,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
InheritsDirective.Register(builder); InheritsDirective.Register(builder);
SectionDirective.Register(builder); SectionDirective.Register(builder);
builder.Features.Add(new ViewComponentTagHelperDescriptorProvider());
builder.AddTargetExtension(new ViewComponentTagHelperTargetExtension()); builder.AddTargetExtension(new ViewComponentTagHelperTargetExtension());
builder.AddTargetExtension(new TemplateTargetExtension() builder.AddTargetExtension(new TemplateTargetExtension()
{ {

View File

@ -10,31 +10,14 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Extensions
{ {
internal class ViewComponentTypeVisitor : SymbolVisitor internal class ViewComponentTypeVisitor : SymbolVisitor
{ {
private static readonly Version SupportedVCTHMvcVersion = new Version(1, 1);
private readonly INamedTypeSymbol _viewComponentAttribute; private readonly INamedTypeSymbol _viewComponentAttribute;
private readonly INamedTypeSymbol _nonViewComponentAttribute; private readonly INamedTypeSymbol _nonViewComponentAttribute;
private readonly List<INamedTypeSymbol> _results; private readonly List<INamedTypeSymbol> _results;
public static ViewComponentTypeVisitor Create(Compilation compilation, List<INamedTypeSymbol> results) public static ViewComponentTypeVisitor Create(Compilation compilation, List<INamedTypeSymbol> results)
{ {
var enabled = false; var vcAttribute = compilation.GetTypeByMetadataName(ViewComponentTypes.ViewComponentAttribute);
foreach (var reference in compilation.References) var nonVCAttribute = compilation.GetTypeByMetadataName(ViewComponentTypes.NonViewComponentAttribute);
{
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;
return new ViewComponentTypeVisitor(vcAttribute, nonVCAttribute, results); return new ViewComponentTypeVisitor(vcAttribute, nonVCAttribute, results);
} }

View File

@ -7,14 +7,14 @@ using System.Linq;
namespace Microsoft.AspNetCore.Razor.Language 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, RazorLanguageVersion.Latest,
"unnamed", "unnamed",
Array.Empty<RazorExtension>()); Array.Empty<RazorExtension>());
public RazorConfiguration( public static RazorConfiguration Create(
RazorLanguageVersion languageVersion, RazorLanguageVersion languageVersion,
string configurationName, string configurationName,
IEnumerable<RazorExtension> extensions) IEnumerable<RazorExtension> extensions)
@ -34,15 +34,32 @@ namespace Microsoft.AspNetCore.Razor.Language
throw new ArgumentNullException(nameof(extensions)); throw new ArgumentNullException(nameof(extensions));
} }
LanguageVersion = languageVersion; return new DefaultRazorConfiguration(languageVersion, configurationName, extensions.ToArray());
ConfigurationName = configurationName;
Extensions = 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;
using Microsoft.CodeAnalysis.Razor; using Microsoft.CodeAnalysis.Razor;
using Microsoft.Extensions.CommandLineUtils; using Microsoft.Extensions.CommandLineUtils;
using Microsoft.VisualStudio.LanguageServices.Razor; using Microsoft.VisualStudio.LanguageServices.Razor.Serialization;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace Microsoft.AspNetCore.Razor.Tools namespace Microsoft.AspNetCore.Razor.Tools
@ -115,7 +115,7 @@ namespace Microsoft.AspNetCore.Razor.Tools
} }
var version = RazorLanguageVersion.Parse(Version.Value()); 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( var result = ExecuteCore(
configuration: configuration, configuration: configuration,

View File

@ -8,7 +8,7 @@ using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language;
using Microsoft.Extensions.CommandLineUtils; using Microsoft.Extensions.CommandLineUtils;
using Microsoft.VisualStudio.LanguageServices.Razor; using Microsoft.VisualStudio.LanguageServices.Razor.Serialization;
using Newtonsoft.Json; using Newtonsoft.Json;
namespace Microsoft.AspNetCore.Razor.Tools namespace Microsoft.AspNetCore.Razor.Tools
@ -58,7 +58,7 @@ namespace Microsoft.AspNetCore.Razor.Tools
} }
var version = RazorLanguageVersion.Parse(Version.Value()); 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( var result = ExecuteCore(
configuration: configuration, configuration: configuration,

View File

@ -13,12 +13,12 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Compile Include="..\Microsoft.VisualStudio.LanguageServices.Razor\RazorDiagnosticJsonConverter.cs"> <Compile Include="..\Microsoft.VisualStudio.LanguageServices.Razor\Serialization\TagHelperDescriptorJsonConverter.cs">
<Link>Shared\RazorDiagnosticJsonConverter.cs</Link>
</Compile>
<Compile Include="..\Microsoft.VisualStudio.LanguageServices.Razor\TagHelperDescriptorJsonConverter.cs">
<Link>Shared\TagHelperDescriptorJsonConverter.cs</Link> <Link>Shared\TagHelperDescriptorJsonConverter.cs</Link>
</Compile> </Compile>
<Compile Include="..\Microsoft.VisualStudio.LanguageServices.Razor\Serialization\RazorDiagnosticJsonConverter.cs">
<Link>Shared\RazorDiagnosticJsonConverter.cs</Link>
</Compile>
</ItemGroup> </ItemGroup>
<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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
namespace Microsoft.CodeAnalysis.Razor namespace Microsoft.CodeAnalysis.Razor
{ {
@ -9,11 +10,31 @@ namespace Microsoft.CodeAnalysis.Razor
{ {
public override void ReportError(Exception exception) public override void ReportError(Exception exception)
{ {
if (exception == null)
{
throw new ArgumentNullException(nameof(exception));
}
// Do nothing. // 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. // Do nothing.
} }
} }

View File

@ -3,6 +3,7 @@
using System; using System;
using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
namespace Microsoft.CodeAnalysis.Razor namespace Microsoft.CodeAnalysis.Razor
{ {
@ -10,6 +11,8 @@ namespace Microsoft.CodeAnalysis.Razor
{ {
public abstract void ReportError(Exception exception); 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, 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,21 +18,25 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// at once. // at once.
internal class DefaultProjectSnapshot : ProjectSnapshot 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) if (other == null)
@ -40,11 +44,36 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
throw new ArgumentNullException(nameof(other)); throw new ArgumentNullException(nameof(other));
} }
UnderlyingProject = underlyingProject; ComputedVersion = other.ComputedVersion;
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; ComputedVersion = other.ComputedVersion;
Configuration = other.Configuration;
FilePath = other.FilePath;
TagHelpers = other.TagHelpers; TagHelpers = other.TagHelpers;
HostProject = other.HostProject;
WorkspaceProject = workspaceProject;
Version = other.Version.GetNewerVersion();
} }
private DefaultProjectSnapshot(ProjectSnapshotUpdateContext update, DefaultProjectSnapshot other) private DefaultProjectSnapshot(ProjectSnapshotUpdateContext update, DefaultProjectSnapshot other)
@ -59,16 +88,28 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
throw new ArgumentNullException(nameof(other)); throw new ArgumentNullException(nameof(other));
} }
UnderlyingProject = other.UnderlyingProject; ComputedVersion = update.Version;
ComputedVersion = update.UnderlyingProject.Version; FilePath = other.FilePath;
Configuration = update.Configuration; HostProject = other.HostProject;
TagHelpers = update.TagHelpers ?? Array.Empty<TagHelperDescriptor>(); 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>(); 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. // 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. // 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) return new ProjectSnapshotUpdateContext(FilePath, HostProject, WorkspaceProject, Version);
{
throw new ArgumentNullException(nameof(project));
}
return new DefaultProjectSnapshot(project, this);
} }
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) if (update == null)
{ {
@ -99,7 +161,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
return new DefaultProjectSnapshot(update, this); return new DefaultProjectSnapshot(update, this);
} }
public bool HasConfigurationChanged(ProjectSnapshot original) public bool HasConfigurationChanged(DefaultProjectSnapshot original)
{ {
if (original == null) if (original == null)
{ {

View File

@ -7,6 +7,22 @@ using System.Linq;
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem 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 internal class DefaultProjectSnapshotManager : ProjectSnapshotManagerBase
{ {
public override event EventHandler<ProjectChangeEventArgs> Changed; public override event EventHandler<ProjectChangeEventArgs> Changed;
@ -17,7 +33,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
private readonly ProjectSnapshotWorkerQueue _workerQueue; private readonly ProjectSnapshotWorkerQueue _workerQueue;
private readonly ProjectSnapshotWorker _worker; private readonly ProjectSnapshotWorker _worker;
private readonly Dictionary<ProjectId, DefaultProjectSnapshot> _projects; private readonly Dictionary<string, DefaultProjectSnapshot> _projects;
public DefaultProjectSnapshotManager( public DefaultProjectSnapshotManager(
ForegroundDispatcher foregroundDispatcher, ForegroundDispatcher foregroundDispatcher,
@ -57,7 +73,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
_triggers = triggers.ToArray(); _triggers = triggers.ToArray();
Workspace = workspace; Workspace = workspace;
_projects = new Dictionary<ProjectId, DefaultProjectSnapshot>(); _projects = new Dictionary<string, DefaultProjectSnapshot>(FilePathComparer.Instance);
_workerQueue = new ProjectSnapshotWorkerQueue(_foregroundDispatcher, this, worker); _workerQueue = new ProjectSnapshotWorkerQueue(_foregroundDispatcher, this, worker);
for (var i = 0; i < _triggers.Length; i++) for (var i = 0; i < _triggers.Length; i++)
@ -70,63 +87,13 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{ {
get get
{ {
_foregroundDispatcher.AssertForegroundThread();
return _projects.Values.ToArray(); 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 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) public override void ProjectUpdated(ProjectSnapshotUpdateContext update)
{ {
if (update == null) if (update == null)
@ -134,25 +101,203 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
throw new ArgumentNullException(nameof(update)); 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 // This is an update to the project's computed values, so everything should be overwritten
var snapshot = original.WithProjectChange(update); var snapshot = original.WithComputedUpdate(update);
_projects[update.UnderlyingProject.Id] = snapshot; _projects[update.WorkspaceProject.FilePath] = snapshot;
if (snapshot.IsDirty) if (snapshot.IsDirty)
{ {
// It's possible that the snapshot can still be dirty if we got a project update while computing state in // 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. // the background. We need to trigger the background work to asynchronously compute the effect of the updates.
NotifyBackgroundWorker(snapshot.UnderlyingProject); NotifyBackgroundWorker(snapshot.CreateUpdateContext());
} }
// Now we need to know if the changes that we applied are significant. If that's the case then if (!object.Equals(snapshot.ComputedVersion, original.ComputedVersion))
// we need to notify listeners.
if (snapshot.HasConfigurationChanged(original))
{ {
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());
}
// 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)); 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)) 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)) _foregroundDispatcher.AssertForegroundThread();
{
_projects.Remove(underlyingProject.Id);
// We need to notify listeners about every project removal. if (!IsSupportedWorkspaceProject(workspaceProject))
NotifyListeners(new ProjectChangeEventArgs(snapshot, ProjectChangeKind.Removed)); {
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)) _errorReporter.ReportError(exception);
{
// 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);
}
} }
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); throw new ArgumentNullException(nameof(exception));
// We need to notify listeners about every project removal.
NotifyListeners(new ProjectChangeEventArgs(kvp.Value, ProjectChangeKind.Removed));
} }
_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 // virtual so it can be overridden in tests
protected virtual void NotifyBackgroundWorker(Project project) protected virtual void NotifyBackgroundWorker(ProjectSnapshotUpdateContext context)
{ {
_foregroundDispatcher.AssertForegroundThread(); _foregroundDispatcher.AssertForegroundThread();
_workerQueue.Enqueue(project); _workerQueue.Enqueue(context);
} }
// virtual so it can be overridden in tests // virtual so it can be overridden in tests
@ -226,15 +449,5 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
handler(this, e); 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 internal class DefaultProjectSnapshotWorker : ProjectSnapshotWorker
{ {
private readonly ProjectExtensibilityConfigurationFactory _configurationFactory;
private readonly ForegroundDispatcher _foregroundDispatcher; private readonly ForegroundDispatcher _foregroundDispatcher;
private readonly TagHelperResolver _tagHelperResolver; private readonly TagHelperResolver _tagHelperResolver;
public DefaultProjectSnapshotWorker( public DefaultProjectSnapshotWorker(ForegroundDispatcher foregroundDispatcher, TagHelperResolver tagHelperResolver)
ForegroundDispatcher foregroundDispatcher,
ProjectExtensibilityConfigurationFactory configurationFactory,
TagHelperResolver tagHelperResolver)
{ {
if (foregroundDispatcher == null) if (foregroundDispatcher == null)
{ {
throw new ArgumentNullException(nameof(foregroundDispatcher)); throw new ArgumentNullException(nameof(foregroundDispatcher));
} }
if (configurationFactory == null)
{
throw new ArgumentNullException(nameof(configurationFactory));
}
if (tagHelperResolver == null) if (tagHelperResolver == null)
{ {
throw new ArgumentNullException(nameof(tagHelperResolver)); throw new ArgumentNullException(nameof(tagHelperResolver));
} }
_foregroundDispatcher = foregroundDispatcher; _foregroundDispatcher = foregroundDispatcher;
_configurationFactory = configurationFactory;
_tagHelperResolver = tagHelperResolver; _tagHelperResolver = tagHelperResolver;
} }
@ -54,16 +44,18 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
return ProjectUpdatesCoreAsync(update); return ProjectUpdatesCoreAsync(update);
} }
protected virtual void OnProcessingUpdate()
{
}
private async Task ProjectUpdatesCoreAsync(object state) private async Task ProjectUpdatesCoreAsync(object state)
{ {
var update = (ProjectSnapshotUpdateContext)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); var snapshot = new DefaultProjectSnapshot(update.HostProject, update.WorkspaceProject, update.Version);
update.Configuration = configuration; var result = await _tagHelperResolver.GetTagHelpersAsync(snapshot, CancellationToken.None);
var result = await _tagHelperResolver.GetTagHelpersAsync(update.UnderlyingProject, CancellationToken.None);
update.TagHelpers = result.Descriptors; update.TagHelpers = result.Descriptors;
} }
} }

View File

@ -26,10 +26,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
public ILanguageService CreateLanguageService(HostLanguageServices languageServices) public ILanguageService CreateLanguageService(HostLanguageServices languageServices)
{ {
return new DefaultProjectSnapshotWorker( return new DefaultProjectSnapshotWorker(_foregroundDispatcher, languageServices.GetRequiredService<TagHelperResolver>());
_foregroundDispatcher,
languageServices.GetRequiredService<ProjectExtensibilityConfigurationFactory>(),
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 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 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 Workspace Workspace { get; }
public abstract void ProjectAdded(Project underlyingProject);
public abstract void ProjectChanged(Project underlyingProject);
public abstract void ProjectUpdated(ProjectSnapshotUpdateContext update); 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);
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++) for (var i = 0; i< projects.Count; i++)
{ {
var project = projects[i]; var project = projects[i];
if (string.Equals(filePath, project.UnderlyingProject.FilePath, StringComparison.OrdinalIgnoreCase)) if (FilePathComparer.Instance.Equals(filePath, project.FilePath))
{ {
return project; return project;
} }

View File

@ -9,20 +9,37 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{ {
internal class ProjectSnapshotUpdateContext 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 IReadOnlyList<TagHelperDescriptor> TagHelpers { get; set; }
public VersionStamp Version { get; }
} }
} }

View File

@ -3,7 +3,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -16,7 +15,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
private readonly DefaultProjectSnapshotManager _projectManager; private readonly DefaultProjectSnapshotManager _projectManager;
private readonly ProjectSnapshotWorker _projectWorker; private readonly ProjectSnapshotWorker _projectWorker;
private readonly Dictionary<ProjectId, Project> _projects; private readonly Dictionary<string, ProjectSnapshotUpdateContext> _projects;
private Timer _timer; private Timer _timer;
public ProjectSnapshotWorkerQueue(ForegroundDispatcher foregroundDispatcher, DefaultProjectSnapshotManager projectManager, ProjectSnapshotWorker projectWorker) public ProjectSnapshotWorkerQueue(ForegroundDispatcher foregroundDispatcher, DefaultProjectSnapshotManager projectManager, ProjectSnapshotWorker projectWorker)
@ -40,7 +39,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
_projectManager = projectManager; _projectManager = projectManager;
_projectWorker = projectWorker; _projectWorker = projectWorker;
_projects = new Dictionary<ProjectId, Project>(); _projects = new Dictionary<string, ProjectSnapshotUpdateContext>(FilePathComparer.Instance);
} }
public bool HasPendingNotifications 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(); _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 // 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. // it's always the best version to use.
_projects[project.Id] = project; _projects[context.FilePath] = context;
StartWorker(); StartWorker();
} }
@ -133,7 +132,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
OnStartingBackgroundWork(); OnStartingBackgroundWork();
Project[] work; ProjectSnapshotUpdateContext[] work;
lock (_projects) lock (_projects)
{ {
work = _projects.Values.ToArray(); work = _projects.Values.ToArray();
@ -145,7 +144,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{ {
try try
{ {
updates[i] = (new ProjectSnapshotUpdateContext(work[i]), null); updates[i] = (work[i], null);
await _projectWorker.ProcessUpdateAsync(updates[i].context); await _projectWorker.ProcessUpdateAsync(updates[i].context);
} }
catch (Exception projectException) catch (Exception projectException)
@ -196,7 +195,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
} }
else 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); Debug.Assert(solution != null);
_projectManager.ProjectsCleared();
foreach (var project in solution.Projects) foreach (var project in solution.Projects)
{ {
if (project.Language == LanguageNames.CSharp) _projectManager.WorkspaceProjectAdded(project);
{
_projectManager.ProjectAdded(project);
}
} }
} }
// Internal for testing // Internal for testing
internal void Workspace_WorkspaceChanged(object sender, WorkspaceChangeEventArgs e) internal void Workspace_WorkspaceChanged(object sender, WorkspaceChangeEventArgs e)
{ {
Project underlyingProject; Project project;
switch (e.Kind) switch (e.Kind)
{ {
case WorkspaceChangeKind.ProjectAdded: case WorkspaceChangeKind.ProjectAdded:
{ {
underlyingProject = e.NewSolution.GetProject(e.ProjectId); project = e.NewSolution.GetProject(e.ProjectId);
Debug.Assert(underlyingProject != null); Debug.Assert(project != null);
if (underlyingProject.Language == LanguageNames.CSharp) _projectManager.WorkspaceProjectAdded(project);
{
_projectManager.ProjectAdded(underlyingProject);
}
break; break;
} }
case WorkspaceChangeKind.ProjectChanged: case WorkspaceChangeKind.ProjectChanged:
case WorkspaceChangeKind.ProjectReloaded: case WorkspaceChangeKind.ProjectReloaded:
{ {
underlyingProject = e.NewSolution.GetProject(e.ProjectId); project = e.NewSolution.GetProject(e.ProjectId);
Debug.Assert(underlyingProject != null); Debug.Assert(project != null);
_projectManager.ProjectChanged(underlyingProject); _projectManager.WorkspaceProjectChanged(project);
break; break;
} }
case WorkspaceChangeKind.ProjectRemoved: case WorkspaceChangeKind.ProjectRemoved:
{ {
underlyingProject = e.OldSolution.GetProject(e.ProjectId); project = e.OldSolution.GetProject(e.ProjectId);
Debug.Assert(underlyingProject != null); Debug.Assert(project != null);
_projectManager.ProjectRemoved(underlyingProject); _projectManager.WorkspaceProjectRemoved(project);
break; break;
} }
@ -76,6 +68,15 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
case WorkspaceChangeKind.SolutionCleared: case WorkspaceChangeKind.SolutionCleared:
case WorkspaceChangeKind.SolutionReloaded: case WorkspaceChangeKind.SolutionReloaded:
case WorkspaceChangeKind.SolutionRemoved: case WorkspaceChangeKind.SolutionRemoved:
if (e.OldSolution != null)
{
foreach (var p in e.OldSolution.Projects)
{
_projectManager.WorkspaceProjectRemoved(p);
}
}
InitializeSolution(e.NewSolution); InitializeSolution(e.NewSolution);
break; 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Runtime.CompilerServices; 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.Razor.Workspaces.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.CodeAnalysis.Remote.Razor, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Microsoft.CodeAnalysis.Remote.Razor, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]
[assembly: InternalsVisibleTo("Microsoft.VisualStudio.Editor.Razor, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Microsoft.VisualStudio.Editor.Razor, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")]

View File

@ -4,11 +4,20 @@
using System; using System;
using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
namespace Microsoft.CodeAnalysis.Razor namespace Microsoft.CodeAnalysis.Razor
{ {
internal abstract class RazorProjectEngineFactoryService : ILanguageService 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. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language;
@ -8,6 +9,8 @@ namespace Microsoft.CodeAnalysis.Razor
{ {
public sealed class TagHelperResolutionResult public sealed class TagHelperResolutionResult
{ {
internal static TagHelperResolutionResult Empty = new TagHelperResolutionResult(Array.Empty<TagHelperDescriptor>(), Array.Empty<RazorDiagnostic>());
public TagHelperResolutionResult(IReadOnlyList<TagHelperDescriptor> descriptors, IReadOnlyList<RazorDiagnostic> diagnostics) public TagHelperResolutionResult(IReadOnlyList<TagHelperDescriptor> descriptors, IReadOnlyList<RazorDiagnostic> diagnostics)
{ {
Descriptors = descriptors; Descriptors = descriptors;

View File

@ -1,14 +1,57 @@
// 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. // 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;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
namespace Microsoft.CodeAnalysis.Razor namespace Microsoft.CodeAnalysis.Razor
{ {
internal abstract class TagHelperResolver : ILanguageService 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> </PropertyGroup>
<ItemGroup> <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>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Microsoft.CodeAnalysis.Razor.Workspaces\Microsoft.CodeAnalysis.Razor.Workspaces.csproj" /> <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" /> <ProjectReference Include="..\Microsoft.AspNetCore.Razor.Language\Microsoft.AspNetCore.Razor.Language.csproj" />
</ItemGroup> </ItemGroup>

View File

@ -10,34 +10,22 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis.Razor; using Microsoft.CodeAnalysis.Razor;
using Microsoft.VisualStudio.LanguageServices.Razor; using Microsoft.CodeAnalysis.Razor.ProjectSystem;
namespace Microsoft.CodeAnalysis.Remote.Razor namespace Microsoft.CodeAnalysis.Remote.Razor
{ {
internal class RazorLanguageService : ServiceHubServiceBase internal class RazorLanguageService : RazorServiceBase
{ {
public RazorLanguageService(Stream stream, IServiceProvider serviceProvider) 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); return await RazorServices.TagHelperResolver.GetTagHelpersAsync(project, factoryTypeName, cancellationToken);
var project = solution.GetProject(projectId);
var resolver = new DefaultTagHelperResolver(designTime: true);
var result = await resolver.GetTagHelpersAsync(project, cancellationToken).ConfigureAwait(false);
return result;
} }
public Task<IEnumerable<DirectiveDescriptor>> GetDirectivesAsync(Guid projectIdBytes, string projectDebugName, CancellationToken cancellationToken = default(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;
using Microsoft.CodeAnalysis.Razor; using Microsoft.CodeAnalysis.Razor;
using Microsoft.CodeAnalysis.Razor.ProjectSystem; 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 namespace Microsoft.VisualStudio.Editor.Razor
{ {
internal class DefaultProjectEngineFactoryService : RazorProjectEngineFactoryService internal class DefaultProjectEngineFactoryService : RazorProjectEngineFactoryService
{ {
private readonly static MvcExtensibilityConfiguration DefaultConfiguration = new MvcExtensibilityConfiguration( private readonly static RazorConfiguration DefaultConfiguration = FallbackRazorConfiguration.MVC_2_0;
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 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) if (projectManager == null)
{ {
throw new ArgumentNullException(nameof(projectManager)); throw new ArgumentNullException(nameof(projectManager));
} }
if (defaultFactory == null)
{
throw new ArgumentNullException(nameof(defaultFactory));
}
if (customFactories == null)
{
throw new ArgumentNullException(nameof(customFactories));
}
_projectManager = projectManager; _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. return SelectFactory(project.Configuration ?? DefaultConfiguration, requireSerializable: false);
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;
} }
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); directory = NormalizeDirectoryPath(directory);
if (_projectManager == null)
{
_projectManager = _workspace.Services.GetLanguageServices(RazorLanguage.Name).GetRequiredService<ProjectSnapshotManager>();
}
var projects = _projectManager.Projects; var projects = _projectManager.Projects;
for (var i = 0; i < projects.Count; i++) for (var i = 0; i < projects.Count; i++)
{ {
var project = projects[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; return project;
} }

View File

@ -1,6 +1,10 @@
// 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. // 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;
using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Razor; using Microsoft.CodeAnalysis.Razor;
@ -11,9 +15,34 @@ namespace Microsoft.VisualStudio.Editor.Razor
[ExportLanguageServiceFactory(typeof(RazorProjectEngineFactoryService), RazorLanguage.Name, ServiceLayer.Default)] [ExportLanguageServiceFactory(typeof(RazorProjectEngineFactoryService), RazorLanguage.Name, ServiceLayer.Default)]
internal class DefaultProjectEngineFactoryServiceFactory : ILanguageServiceFactory 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) 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Razor.Extensions;
using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Razor; using Microsoft.CodeAnalysis.Razor;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
namespace Microsoft.VisualStudio.Editor.Razor namespace Microsoft.VisualStudio.Editor.Razor
{ {
internal class DefaultTagHelperResolver : TagHelperResolver internal class DefaultTagHelperResolver : TagHelperResolver
{ {
// Hack for testability. The view component visitor will normally just no op if we're not referencing private readonly RazorProjectEngineFactoryService _engineFactory;
// an appropriate version of MVC.
internal bool ForceEnableViewComponentDiscovery { get; set; }
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) if (project == null)
{ {
throw new ArgumentNullException(nameof(project)); throw new ArgumentNullException(nameof(project));
} }
var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false); if (project.Configuration == null || project.WorkspaceProject == null)
var result = GetTagHelpers(compilation);
return result;
}
// Internal for testing
internal TagHelperResolutionResult GetTagHelpers(Compilation compilation)
{
var descriptors = new List<TagHelperDescriptor>();
var providers = new ITagHelperDescriptorProvider[]
{ {
new DefaultTagHelperDescriptorProvider() { DesignTime = true, }, return Task.FromResult(TagHelperResolutionResult.Empty);
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);
} }
var diagnostics = new List<RazorDiagnostic>(); var engine = _engineFactory.Create(project, RazorProjectFileSystem.Empty, b =>
var resolutionResult = new TagHelperResolutionResult(results, diagnostics); {
b.Features.Add(new DefaultTagHelperDescriptorProvider() { DesignTime = true, });
return resolutionResult; });
return GetTagHelpersAsync(project, engine);
} }
} }
} }

View File

@ -14,7 +14,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
{ {
public ILanguageService CreateLanguageService(HostLanguageServices languageServices) 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>(); _textViews = new List<ITextView>();
} }
internal override ProjectExtensibilityConfiguration Configuration => _project?.Configuration; public override RazorConfiguration Configuration => _project?.Configuration;
public override EditorSettings EditorSettings => _workspaceEditorSettings.Current; public override EditorSettings EditorSettings => _workspaceEditorSettings.Current;
@ -99,7 +99,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
public override bool IsSupportedProject => _isSupportedProject; 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; public override ITextBuffer TextBuffer => _textBuffer;
@ -196,7 +196,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
internal void ProjectManager_Changed(object sender, ProjectChangeEventArgs e) internal void ProjectManager_Changed(object sender, ProjectChangeEventArgs e)
{ {
if (_projectPath != null && 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) 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> <ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.CodeAnalysis.Razor.Workspaces\Microsoft.CodeAnalysis.Razor.Workspaces.csproj" /> <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> </ItemGroup>
</Project> </Project>

View File

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

View File

@ -6,6 +6,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Razor; using Microsoft.CodeAnalysis.Razor;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using Microsoft.VisualStudio.Editor.Razor; using Microsoft.VisualStudio.Editor.Razor;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
@ -15,53 +16,67 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
internal class OOPTagHelperResolver : TagHelperResolver internal class OOPTagHelperResolver : TagHelperResolver
{ {
private readonly DefaultTagHelperResolver _defaultResolver; private readonly DefaultTagHelperResolver _defaultResolver;
private readonly RazorProjectEngineFactoryService _engineFactory;
private readonly ErrorReporter _errorReporter;
private readonly Workspace _workspace; 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) if (workspace == null)
{ {
throw new ArgumentNullException(nameof(workspace)); throw new ArgumentNullException(nameof(workspace));
} }
_engineFactory = engineFactory;
_errorReporter = errorReporter;
_workspace = workspace; _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) if (project == null)
{ {
throw new ArgumentNullException(nameof(project)); 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 try
{ {
TagHelperResolutionResult result = null; TagHelperResolutionResult result = null;
if (factory != 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)
{ {
using (var session = await client.CreateSessionAsync(project.Solution)) result = await ResolveTagHelpersOutOfProcessAsync(factory, project);
{
if (session != null)
{
var jsonObject = await session.InvokeAsync<JObject>(
"GetTagHelpersAsync",
new object[] { project.Id.Id, "Foo", },
cancellationToken).ConfigureAwait(false);
result = GetTagHelperResolutionResult(jsonObject);
}
}
} }
if (result == null) if (result == null)
{ {
// Was unable to get tag helpers OOP, fallback to default behavior. // Was unable to get tag helpers OOP, fallback to default behavior.
result = await _defaultResolver.GetTagHelpersAsync(project, cancellationToken); result = await ResolveTagHelpersInProcessAsync(project);
} }
return result; 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(); var serializer = new JsonSerializer();
serializer.Converters.Add(TagHelperDescriptorJsonConverter.Instance); serializer.Converters.RegisterRazorConverters();
serializer.Converters.Add(RazorDiagnosticJsonConverter.Instance);
return JObject.FromObject(snapshot, serializer);
}
private TagHelperResolutionResult Deserialize(JObject jsonObject)
{
var serializer = new JsonSerializer();
serializer.Converters.RegisterRazorConverters();
using (var reader = jsonObject.CreateReader()) using (var reader = jsonObject.CreateReader())
{ {

View File

@ -14,8 +14,10 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
{ {
public ILanguageService CreateLanguageService(HostLanguageServices languageServices) public ILanguageService CreateLanguageService(HostLanguageServices languageServices)
{ {
var workspace = languageServices.WorkspaceServices.Workspace; return new OOPTagHelperResolver(
return new OOPTagHelperResolver(workspace); 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.ComponentModel.Composition; using System.ComponentModel.Composition;

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;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
namespace Microsoft.VisualStudio.LanguageServices.Razor namespace Microsoft.VisualStudio.LanguageServices.Razor.Serialization
{ {
internal class RazorDiagnosticJsonConverter : JsonConverter 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;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
namespace Microsoft.VisualStudio.LanguageServices.Razor namespace Microsoft.VisualStudio.LanguageServices.Razor.Serialization
{ {
internal class TagHelperDescriptorJsonConverter : JsonConverter internal class TagHelperDescriptorJsonConverter : JsonConverter
{ {

View File

@ -4,6 +4,7 @@
using System; using System;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Razor; using Microsoft.CodeAnalysis.Razor;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Shell.Interop;
namespace Microsoft.VisualStudio.LanguageServices.Razor 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) if (exception == null)
{ {
@ -53,7 +54,25 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
var hr = activityLog.LogEntry( var hr = activityLog.LogEntry(
(uint)__ACTIVITYLOG_ENTRYTYPE.ALE_ERROR, (uint)__ACTIVITYLOG_ENTRYTYPE.ALE_ERROR,
"Razor Language Services", "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); ErrorHandler.ThrowOnFailure(hr);
} }
} }

View File

@ -85,16 +85,14 @@ namespace Microsoft.VisualStudio.LanguageServices.Razor
// This gets called when the project has finished building. // 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) 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); var projectPath = _projectService.GetProjectPath(pHierProj);
// Get the corresponding roslyn project by matching the project name and the project path. // 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) && if (string.Equals(projectPath, projectSnapshot.FilePath, StringComparison.OrdinalIgnoreCase))
string.Equals(projectPath, project.FilePath, StringComparison.OrdinalIgnoreCase))
{ {
_projectManager.ProjectBuildComplete(project); _projectManager.HostProjectBuildComplete(projectSnapshot.HostProject);
break; break;
} }
} }

View File

@ -96,16 +96,14 @@ namespace Microsoft.VisualStudio.Mac.LanguageServices.Razor
return; return;
} }
var projectName = _projectService.GetProjectName(projectItem);
var projectPath = _projectService.GetProjectPath(projectItem); var projectPath = _projectService.GetProjectPath(projectItem);
// Get the corresponding roslyn project by matching the project name and the project path. // 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) && if (string.Equals(projectPath, projectSnapshot.FilePath, StringComparison.OrdinalIgnoreCase))
string.Equals(projectPath, project.FilePath, StringComparison.OrdinalIgnoreCase))
{ {
_projectManager.ProjectBuildComplete(project); _projectManager.HostProjectBuildComplete(projectSnapshot.HostProject);
break; break;
} }
} }

View File

@ -52,6 +52,22 @@ namespace Microsoft.VisualStudio.Mac.LanguageServices.Razor
internal static string FormatRazorLanguageServiceProjectError(object p0) internal static string FormatRazorLanguageServiceProjectError(object p0)
=> string.Format(CultureInfo.CurrentCulture, GetString("RazorLanguageServiceProjectError"), 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) private static string GetString(string name, params string[] formatterNames)
{ {
var value = _resourceManager.GetString(name); var value = _resourceManager.GetString(name);

View File

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

View File

@ -5,6 +5,7 @@ using System;
using System.Diagnostics; using System.Diagnostics;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Razor; using Microsoft.CodeAnalysis.Razor;
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using MonoDevelop.Core; using MonoDevelop.Core;
namespace Microsoft.VisualStudio.Mac.LanguageServices.Razor namespace Microsoft.VisualStudio.Mac.LanguageServices.Razor
@ -36,5 +37,18 @@ namespace Microsoft.VisualStudio.Mac.LanguageServices.Razor
Resources.FormatRazorLanguageServiceProjectError(project?.Name), Resources.FormatRazorLanguageServiceProjectError(project?.Name),
exception); 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> <ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="$(VSIX_MicrosoftCodeAnalysisWorkspacesCommonPackageVersion)" /> <PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="$(VSIX_MicrosoftCodeAnalysisWorkspacesCommonPackageVersion)" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="$(VSIX_MicrosoftCodeAnalysisCSharpWorkspacesPackageVersion)" /> <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.Extensions.DependencyModel" Version="$(MicrosoftExtensionsDependencyModelPackageVersion)" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkPackageVersion)" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkPackageVersion)" />
<PackageReference Include="Moq" Version="$(MoqPackageVersion)" /> <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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language;
using Moq;
using Xunit; using Xunit;
namespace Microsoft.CodeAnalysis.Razor.ProjectSystem namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
@ -15,19 +10,20 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
public class DefaultProjectSnapshotTest public class DefaultProjectSnapshotTest
{ {
[Fact] [Fact]
public void WithProjectChange_WithProject_CreatesSnapshot_UpdatesUnderlyingProject() public void WithWorkspaceProject_CreatesSnapshot_UpdatesUnderlyingProject()
{ {
// Arrange // Arrange
var underlyingProject = GetProject("Test1"); var hostProject = new HostProject("Test.cshtml", FallbackRazorConfiguration.MVC_2_0);
var original = new DefaultProjectSnapshot(underlyingProject); var workspaceProject = GetWorkspaceProject("Test1");
var original = new DefaultProjectSnapshot(hostProject, workspaceProject);
var anotherProject = GetProject("Test1"); var anotherProject = GetWorkspaceProject("Test1");
// Act // Act
var snapshot = original.WithProjectChange(anotherProject); var snapshot = original.WithWorkspaceProject(anotherProject);
// Assert // Assert
Assert.Same(anotherProject, snapshot.UnderlyingProject); Assert.Same(anotherProject, snapshot.WorkspaceProject);
Assert.Equal(original.ComputedVersion, snapshot.ComputedVersion); Assert.Equal(original.ComputedVersion, snapshot.ComputedVersion);
Assert.Equal(original.Configuration, snapshot.Configuration); Assert.Equal(original.Configuration, snapshot.Configuration);
Assert.Equal(original.TagHelpers, snapshot.TagHelpers); Assert.Equal(original.TagHelpers, snapshot.TagHelpers);
@ -37,23 +33,21 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
public void WithProjectChange_WithProject_CreatesSnapshot_UpdatesValues() public void WithProjectChange_WithProject_CreatesSnapshot_UpdatesValues()
{ {
// Arrange // Arrange
var underlyingProject = GetProject("Test1"); var hostProject = new HostProject("Test.cshtml", FallbackRazorConfiguration.MVC_2_0);
var original = new DefaultProjectSnapshot(underlyingProject); var workspaceProject = GetWorkspaceProject("Test1");
var original = new DefaultProjectSnapshot(hostProject, workspaceProject);
var anotherProject = GetProject("Test1"); var anotherProject = GetWorkspaceProject("Test1");
var update = new ProjectSnapshotUpdateContext(anotherProject) var update = new ProjectSnapshotUpdateContext(original.FilePath, hostProject, anotherProject, original.Version)
{ {
Configuration = Mock.Of<ProjectExtensibilityConfiguration>(),
TagHelpers = Array.Empty<TagHelperDescriptor>(), TagHelpers = Array.Empty<TagHelperDescriptor>(),
}; };
// Act // Act
var snapshot = original.WithProjectChange(update); var snapshot = original.WithComputedUpdate(update);
// Assert // Assert
Assert.Same(original.UnderlyingProject, snapshot.UnderlyingProject); Assert.Same(original.WorkspaceProject, snapshot.WorkspaceProject);
Assert.Equal(update.UnderlyingProject.Version, snapshot.ComputedVersion);
Assert.Same(update.Configuration, snapshot.Configuration);
Assert.Same(update.TagHelpers, snapshot.TagHelpers); Assert.Same(update.TagHelpers, snapshot.TagHelpers);
} }
@ -61,12 +55,13 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
public void HaveTagHelpersChanged_NoUpdatesToTagHelpers_ReturnsFalse() public void HaveTagHelpersChanged_NoUpdatesToTagHelpers_ReturnsFalse()
{ {
// Arrange // Arrange
var underlyingProject = GetProject("Test1"); var hostProject = new HostProject("Test1.csproj", RazorConfiguration.Default);
var original = new DefaultProjectSnapshot(underlyingProject); var workspaceProject = GetWorkspaceProject("Test1");
var original = new DefaultProjectSnapshot(hostProject, workspaceProject);
var anotherProject = GetProject("Test1"); var anotherProject = GetWorkspaceProject("Test1");
var update = new ProjectSnapshotUpdateContext(anotherProject); var update = new ProjectSnapshotUpdateContext("Test1.csproj", hostProject, anotherProject, VersionStamp.Default);
var snapshot = original.WithProjectChange(update); var snapshot = original.WithComputedUpdate(update);
// Act // Act
var result = snapshot.HaveTagHelpersChanged(original); var result = snapshot.HaveTagHelpersChanged(original);
@ -79,11 +74,12 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
public void HaveTagHelpersChanged_TagHelpersUpdated_ReturnsTrue() public void HaveTagHelpersChanged_TagHelpersUpdated_ReturnsTrue()
{ {
// Arrange // Arrange
var underlyingProject = GetProject("Test1"); var hostProject = new HostProject("Test1.csproj", RazorConfiguration.Default);
var original = new DefaultProjectSnapshot(underlyingProject); var workspaceProject = GetWorkspaceProject("Test1");
var original = new DefaultProjectSnapshot(hostProject, workspaceProject);
var anotherProject = GetProject("Test1"); var anotherProject = GetWorkspaceProject("Test1");
var update = new ProjectSnapshotUpdateContext(anotherProject) var update = new ProjectSnapshotUpdateContext("Test1.csproj", hostProject, anotherProject, VersionStamp.Default)
{ {
TagHelpers = new[] TagHelpers = new[]
{ {
@ -91,7 +87,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
TagHelperDescriptorBuilder.Create("Two", "TestAssembly").Build(), TagHelperDescriptorBuilder.Create("Two", "TestAssembly").Build(),
}, },
}; };
var snapshot = original.WithProjectChange(update); var snapshot = original.WithComputedUpdate(update);
// Act // Act
var result = snapshot.HaveTagHelpersChanged(original); var result = snapshot.HaveTagHelpersChanged(original);
@ -100,7 +96,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
Assert.True(result); Assert.True(result);
} }
private Project GetProject(string name) private Project GetWorkspaceProject(string name)
{ {
Project project = null; Project project = null;
TestWorkspace.Create(workspace => TestWorkspace.Create(workspace =>

View File

@ -14,33 +14,53 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{ {
public WorkspaceProjectSnapshotChangeTriggerTest() public WorkspaceProjectSnapshotChangeTriggerTest()
{ {
Solution emptySolution = null; Workspace = TestWorkspace.Create();
Project project1 = null; EmptySolution = Workspace.CurrentSolution.GetIsolatedSolution();
Project project2 = null;
Project project3 = null;
Solution solutionWithTwoProjects = null;
Solution solutionWithOneProject = null;
Workspace = TestWorkspace.Create(ws => var projectId1 = ProjectId.CreateNewId("One");
{ var projectId2 = ProjectId.CreateNewId("Two");
emptySolution = ws.CurrentSolution.GetIsolatedSolution(); var projectId3 = ProjectId.CreateNewId("Three");
project1 = ws.CurrentSolution.AddProject("One", "One", LanguageNames.CSharp);
project2 = project1.Solution.AddProject("Two", "Two", LanguageNames.CSharp);
solutionWithTwoProjects = project2.Solution;
project3 = emptySolution.GetIsolatedSolution().AddProject("Three", "Three", LanguageNames.CSharp); SolutionWithTwoProjects = Workspace.CurrentSolution
solutionWithOneProject = project3.Solution; .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; SolutionWithOneProject = EmptySolution.GetIsolatedSolution()
ProjectNumberOne = project1; .AddProject(ProjectInfo.Create(
ProjectNumberTwo = project2; projectId3,
ProjectNumberThree = project3; VersionStamp.Default,
SolutionWithTwoProjects = solutionWithTwoProjects; "Three",
SolutionWithOneProject = solutionWithOneProject; "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 EmptySolution { get; }
private Solution SolutionWithOneProject { get; } private Solution SolutionWithOneProject { get; }
@ -66,6 +86,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Arrange // Arrange
var trigger = new WorkspaceProjectSnapshotChangeTrigger(); var trigger = new WorkspaceProjectSnapshotChangeTrigger();
var projectManager = new TestProjectSnapshotManager(new[] { trigger }, Workspace); var projectManager = new TestProjectSnapshotManager(new[] { trigger }, Workspace);
projectManager.HostProjectAdded(HostProjectOne);
projectManager.HostProjectAdded(HostProjectTwo);
var e = new WorkspaceChangeEventArgs(kind, oldSolution: EmptySolution, newSolution: SolutionWithTwoProjects); var e = new WorkspaceChangeEventArgs(kind, oldSolution: EmptySolution, newSolution: SolutionWithTwoProjects);
@ -74,9 +96,9 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Assert // Assert
Assert.Collection( Assert.Collection(
projectManager.Projects.OrderBy(p => p.UnderlyingProject.Name), projectManager.Projects.OrderBy(p => p.WorkspaceProject.Name),
p => Assert.Equal(ProjectNumberOne.Id, p.UnderlyingProject.Id), p => Assert.Equal(ProjectNumberOne.Id, p.WorkspaceProject.Id),
p => Assert.Equal(ProjectNumberTwo.Id, p.UnderlyingProject.Id)); p => Assert.Equal(ProjectNumberTwo.Id, p.WorkspaceProject.Id));
} }
[Theory] [Theory]
@ -90,21 +112,25 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Arrange // Arrange
var trigger = new WorkspaceProjectSnapshotChangeTrigger(); var trigger = new WorkspaceProjectSnapshotChangeTrigger();
var projectManager = new TestProjectSnapshotManager(new[] { trigger }, Workspace); var projectManager = new TestProjectSnapshotManager(new[] { trigger }, Workspace);
projectManager.HostProjectAdded(HostProjectOne);
projectManager.HostProjectAdded(HostProjectTwo);
projectManager.HostProjectAdded(HostProjectThree);
// Initialize with a project. This will get removed. // Initialize with a project. This will get removed.
var e = new WorkspaceChangeEventArgs(WorkspaceChangeKind.SolutionAdded, oldSolution: EmptySolution, newSolution: SolutionWithOneProject); var e = new WorkspaceChangeEventArgs(WorkspaceChangeKind.SolutionAdded, oldSolution: EmptySolution, newSolution: SolutionWithOneProject);
trigger.Workspace_WorkspaceChanged(Workspace, e); trigger.Workspace_WorkspaceChanged(Workspace, e);
e = new WorkspaceChangeEventArgs(kind, oldSolution: EmptySolution, newSolution: SolutionWithTwoProjects); e = new WorkspaceChangeEventArgs(kind, oldSolution: SolutionWithOneProject, newSolution: SolutionWithTwoProjects);
// Act // Act
trigger.Workspace_WorkspaceChanged(Workspace, e); trigger.Workspace_WorkspaceChanged(Workspace, e);
// Assert // Assert
Assert.Collection( Assert.Collection(
projectManager.Projects.OrderBy(p => p.UnderlyingProject.Name), projectManager.Projects.OrderBy(p => p.WorkspaceProject?.Name),
p => Assert.Equal(ProjectNumberOne.Id, p.UnderlyingProject.Id), p => Assert.Null(p.WorkspaceProject),
p => Assert.Equal(ProjectNumberTwo.Id, p.UnderlyingProject.Id)); p => Assert.Equal(ProjectNumberOne.Id, p.WorkspaceProject.Id),
p => Assert.Equal(ProjectNumberTwo.Id, p.WorkspaceProject.Id));
} }
[Theory] [Theory]
@ -115,6 +141,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Arrange // Arrange
var trigger = new WorkspaceProjectSnapshotChangeTrigger(); var trigger = new WorkspaceProjectSnapshotChangeTrigger();
var projectManager = new TestProjectSnapshotManager(new[] { trigger }, Workspace); var projectManager = new TestProjectSnapshotManager(new[] { trigger }, Workspace);
projectManager.HostProjectAdded(HostProjectOne);
projectManager.HostProjectAdded(HostProjectTwo);
// Initialize with some projects. // Initialize with some projects.
var e = new WorkspaceChangeEventArgs(WorkspaceChangeKind.SolutionAdded, oldSolution: EmptySolution, newSolution: SolutionWithTwoProjects); var e = new WorkspaceChangeEventArgs(WorkspaceChangeKind.SolutionAdded, oldSolution: EmptySolution, newSolution: SolutionWithTwoProjects);
@ -128,13 +156,13 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Assert // Assert
Assert.Collection( Assert.Collection(
projectManager.Projects.OrderBy(p => p.UnderlyingProject.Name), projectManager.Projects.OrderBy(p => p.WorkspaceProject.Name),
p => p =>
{ {
Assert.Equal(ProjectNumberOne.Id, p.UnderlyingProject.Id); Assert.Equal(ProjectNumberOne.Id, p.WorkspaceProject.Id);
Assert.Equal("Changed", p.UnderlyingProject.AssemblyName); Assert.Equal("Changed", p.WorkspaceProject.AssemblyName);
}, },
p => Assert.Equal(ProjectNumberTwo.Id, p.UnderlyingProject.Id)); p => Assert.Equal(ProjectNumberTwo.Id, p.WorkspaceProject.Id));
} }
[Fact] [Fact]
@ -143,6 +171,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Arrange // Arrange
var trigger = new WorkspaceProjectSnapshotChangeTrigger(); var trigger = new WorkspaceProjectSnapshotChangeTrigger();
var projectManager = new TestProjectSnapshotManager(new[] { trigger }, Workspace); var projectManager = new TestProjectSnapshotManager(new[] { trigger }, Workspace);
projectManager.HostProjectAdded(HostProjectOne);
projectManager.HostProjectAdded(HostProjectTwo);
// Initialize with some projects project. // Initialize with some projects project.
var e = new WorkspaceChangeEventArgs(WorkspaceChangeKind.SolutionAdded, oldSolution: EmptySolution, newSolution: SolutionWithTwoProjects); var e = new WorkspaceChangeEventArgs(WorkspaceChangeKind.SolutionAdded, oldSolution: EmptySolution, newSolution: SolutionWithTwoProjects);
@ -156,8 +186,9 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Assert // Assert
Assert.Collection( Assert.Collection(
projectManager.Projects.OrderBy(p => p.UnderlyingProject.Name), projectManager.Projects.OrderBy(p => p.WorkspaceProject?.Name),
p => Assert.Equal(ProjectNumberTwo.Id, p.UnderlyingProject.Id)); p => Assert.Null(p.WorkspaceProject),
p => Assert.Equal(ProjectNumberTwo.Id, p.WorkspaceProject.Id));
} }
[Fact] [Fact]
@ -166,6 +197,7 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Arrange // Arrange
var trigger = new WorkspaceProjectSnapshotChangeTrigger(); var trigger = new WorkspaceProjectSnapshotChangeTrigger();
var projectManager = new TestProjectSnapshotManager(new[] { trigger }, Workspace); var projectManager = new TestProjectSnapshotManager(new[] { trigger }, Workspace);
projectManager.HostProjectAdded(HostProjectThree);
var solution = SolutionWithOneProject; var solution = SolutionWithOneProject;
var e = new WorkspaceChangeEventArgs(WorkspaceChangeKind.ProjectAdded, oldSolution: EmptySolution, newSolution: solution, projectId: ProjectNumberThree.Id); var e = new WorkspaceChangeEventArgs(WorkspaceChangeKind.ProjectAdded, oldSolution: EmptySolution, newSolution: solution, projectId: ProjectNumberThree.Id);
@ -175,8 +207,8 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
// Assert // Assert
Assert.Collection( Assert.Collection(
projectManager.Projects.OrderBy(p => p.UnderlyingProject.Name), projectManager.Projects.OrderBy(p => p.WorkspaceProject.Name),
p => Assert.Equal(ProjectNumberThree.Id, p.UnderlyingProject.Id)); p => Assert.Equal(ProjectNumberThree.Id, p.WorkspaceProject.Id));
} }
private class TestProjectSnapshotManager : DefaultProjectSnapshotManager private class TestProjectSnapshotManager : DefaultProjectSnapshotManager
@ -186,8 +218,10 @@ namespace Microsoft.CodeAnalysis.Razor.ProjectSystem
{ {
} }
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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using Microsoft.AspNetCore.Mvc.Razor.Extensions;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis.Razor; using Microsoft.CodeAnalysis.Razor;
using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using Moq; using Moq;
@ -21,7 +23,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
var testImportsPath = "C:\\path\\to\\project\\_ViewImports.cshtml"; var testImportsPath = "C:\\path\\to\\project\\_ViewImports.cshtml";
var tracker = Mock.Of<VisualStudioDocumentTracker>(t => t.FilePath == filePath && t.ProjectPath == projectPath); 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 anotherTracker = Mock.Of<VisualStudioDocumentTracker>(t => t.FilePath == anotherFilePath && t.ProjectPath == projectPath);
var templateEngineFactoryService = GetProjectEngineFactoryService(); var projectEngineFactoryService = GetProjectEngineFactoryService();
var fileChangeTracker = new Mock<FileChangeTracker>(); var fileChangeTracker = new Mock<FileChangeTracker>();
fileChangeTracker.Setup(f => f.FilePath).Returns(testImportsPath); fileChangeTracker.Setup(f => f.FilePath).Returns(testImportsPath);
var fileChangeTrackerFactory = new Mock<FileChangeTrackerFactory>(); var fileChangeTrackerFactory = new Mock<FileChangeTrackerFactory>();
@ -36,7 +38,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
.Returns(Mock.Of<FileChangeTracker>()); .Returns(Mock.Of<FileChangeTracker>());
var called = false; 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(tracker);
manager.OnSubscribed(anotherTracker); manager.OnSubscribed(anotherTracker);
manager.Changed += (sender, args) => manager.Changed += (sender, args) =>
@ -63,7 +65,18 @@ namespace Microsoft.VisualStudio.Editor.Razor
var projectManager = new Mock<ProjectSnapshotManager>(); var projectManager = new Mock<ProjectSnapshotManager>();
projectManager.Setup(p => p.Projects).Returns(Array.Empty<ProjectSnapshot>()); 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; 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using Microsoft.AspNetCore.Mvc.Razor.Extensions;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis.Razor; using Microsoft.CodeAnalysis.Razor;
using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Razor.ProjectSystem;
using Moq; using Moq;
@ -18,7 +20,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
var filePath = "C:\\path\\to\\project\\Views\\Home\\file.cshtml"; var filePath = "C:\\path\\to\\project\\Views\\Home\\file.cshtml";
var projectPath = "C:\\path\\to\\project\\project.csproj"; var projectPath = "C:\\path\\to\\project\\project.csproj";
var tracker = Mock.Of<VisualStudioDocumentTracker>(t => t.FilePath == filePath && t.ProjectPath == projectPath); var tracker = Mock.Of<VisualStudioDocumentTracker>(t => t.FilePath == filePath && t.ProjectPath == projectPath);
var templateEngineFactoryService = GetTemplateEngineFactoryService(); var projectEngineService = GetProjectEngineFactoryService();
var fileChangeTracker1 = new Mock<FileChangeTracker>(); var fileChangeTracker1 = new Mock<FileChangeTracker>();
fileChangeTracker1.Setup(f => f.StartListening()).Verifiable(); fileChangeTracker1.Setup(f => f.StartListening()).Verifiable();
var fileChangeTrackerFactory = new Mock<FileChangeTrackerFactory>(); var fileChangeTrackerFactory = new Mock<FileChangeTrackerFactory>();
@ -39,7 +41,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
.Returns(fileChangeTracker3.Object) .Returns(fileChangeTracker3.Object)
.Verifiable(); .Verifiable();
var manager = new DefaultImportDocumentManager(Dispatcher, new DefaultErrorReporter(), fileChangeTrackerFactory.Object, templateEngineFactoryService); var manager = new DefaultImportDocumentManager(Dispatcher, new DefaultErrorReporter(), fileChangeTrackerFactory.Object, projectEngineService);
// Act // Act
manager.OnSubscribed(tracker); manager.OnSubscribed(tracker);
@ -58,7 +60,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
var filePath = "C:\\path\\to\\project\\file.cshtml"; var filePath = "C:\\path\\to\\project\\file.cshtml";
var projectPath = "C:\\path\\to\\project\\project.csproj"; var projectPath = "C:\\path\\to\\project\\project.csproj";
var tracker = Mock.Of<VisualStudioDocumentTracker>(t => t.FilePath == filePath && t.ProjectPath == projectPath); var tracker = Mock.Of<VisualStudioDocumentTracker>(t => t.FilePath == filePath && t.ProjectPath == projectPath);
var templateEngineFactoryService = GetTemplateEngineFactoryService(); var projectEngineService = GetProjectEngineFactoryService();
var callCount = 0; var callCount = 0;
var fileChangeTrackerFactory = new Mock<FileChangeTrackerFactory>(); var fileChangeTrackerFactory = new Mock<FileChangeTrackerFactory>();
@ -67,7 +69,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
.Returns(Mock.Of<FileChangeTracker>()) .Returns(Mock.Of<FileChangeTracker>())
.Callback(() => callCount++); .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. manager.OnSubscribed(tracker); // Start tracking the import.
var anotherFilePath = "C:\\path\\to\\project\\anotherFile.cshtml"; 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 filePath = "C:\\path\\to\\project\\file.cshtml";
var projectPath = "C:\\path\\to\\project\\project.csproj"; var projectPath = "C:\\path\\to\\project\\project.csproj";
var tracker = Mock.Of<VisualStudioDocumentTracker>(t => t.FilePath == filePath && t.ProjectPath == projectPath); var tracker = Mock.Of<VisualStudioDocumentTracker>(t => t.FilePath == filePath && t.ProjectPath == projectPath);
var templateEngineFactoryService = GetTemplateEngineFactoryService(); var projectEngineService = GetProjectEngineFactoryService();
var fileChangeTracker = new Mock<FileChangeTracker>(); var fileChangeTracker = new Mock<FileChangeTracker>();
fileChangeTracker.Setup(f => f.StopListening()).Verifiable(); fileChangeTracker.Setup(f => f.StopListening()).Verifiable();
@ -97,7 +99,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
.Returns(fileChangeTracker.Object) .Returns(fileChangeTracker.Object)
.Verifiable(); .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. manager.OnSubscribed(tracker); // Start tracking the import.
// Act // Act
@ -115,7 +117,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
var filePath = "C:\\path\\to\\project\\file.cshtml"; var filePath = "C:\\path\\to\\project\\file.cshtml";
var projectPath = "C:\\path\\to\\project\\project.csproj"; var projectPath = "C:\\path\\to\\project\\project.csproj";
var tracker = Mock.Of<VisualStudioDocumentTracker>(t => t.FilePath == filePath && t.ProjectPath == projectPath); var tracker = Mock.Of<VisualStudioDocumentTracker>(t => t.FilePath == filePath && t.ProjectPath == projectPath);
var templateEngineFactoryService = GetTemplateEngineFactoryService(); var projectEngineService = GetProjectEngineFactoryService();
var fileChangeTracker = new Mock<FileChangeTracker>(); var fileChangeTracker = new Mock<FileChangeTracker>();
fileChangeTracker fileChangeTracker
@ -126,7 +128,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
.Setup(f => f.Create(It.IsAny<string>())) .Setup(f => f.Create(It.IsAny<string>()))
.Returns(fileChangeTracker.Object); .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. manager.OnSubscribed(tracker); // Starts tracking import for the first document.
var anotherFilePath = "C:\\path\\to\\project\\anotherFile.cshtml"; var anotherFilePath = "C:\\path\\to\\project\\anotherFile.cshtml";
@ -137,12 +139,23 @@ namespace Microsoft.VisualStudio.Editor.Razor
manager.OnUnsubscribed(tracker); manager.OnUnsubscribed(tracker);
} }
private RazorProjectEngineFactoryService GetTemplateEngineFactoryService() private RazorProjectEngineFactoryService GetProjectEngineFactoryService()
{ {
var projectManager = new Mock<ProjectSnapshotManager>(); var projectManager = new Mock<ProjectSnapshotManager>();
projectManager.Setup(p => p.Projects).Returns(Array.Empty<ProjectSnapshot>()); 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; return service;
} }
} }

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Reflection;
using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Razor; using Microsoft.CodeAnalysis.Razor;
@ -26,30 +27,67 @@ namespace Microsoft.VisualStudio.Editor.Razor
project = workspace.CurrentSolution.AddProject(info).GetProject(info.Id); 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 private Lazy<IProjectEngineFactory, ICustomProjectEngineFactoryMetadata>[] CustomFactories { get; }
public Project Project { 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] [Fact]
public void Create_CreatesTemplateEngine_ForLatest() public void Create_CreatesDesignTimeTemplateEngine_ForVersion2_1()
{ {
// Arrange // Arrange
var projectManager = new TestProjectSnapshotManager(Workspace); var projectManager = new TestProjectSnapshotManager(Workspace);
projectManager.ProjectAdded(Project); projectManager.HostProjectAdded(HostProject_For_2_1);
projectManager.ProjectUpdated(new ProjectSnapshotUpdateContext(Project) projectManager.WorkspaceProjectAdded(WorkspaceProject);
{
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")))),
});
var factoryService = new DefaultProjectEngineFactoryService(projectManager); var factoryService = new DefaultProjectEngineFactoryService(projectManager, FallbackFactory, CustomFactories);
// Act // Act
var engine = factoryService.Create("/TestPath/SomePath/", b => var engine = factoryService.Create("/TestPath/SomePath/", b =>
@ -59,6 +97,30 @@ namespace Microsoft.VisualStudio.Editor.Razor
// Assert // Assert
Assert.Single(engine.Engine.Features.OfType<MyCoolNewFeature>()); 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.MvcViewDocumentClassifierPass>());
Assert.Single(engine.Engine.Features.OfType<MvcLatest.ViewComponentTagHelperPass>()); Assert.Single(engine.Engine.Features.OfType<MvcLatest.ViewComponentTagHelperPass>());
} }
@ -68,17 +130,10 @@ namespace Microsoft.VisualStudio.Editor.Razor
{ {
// Arrange // Arrange
var projectManager = new TestProjectSnapshotManager(Workspace); var projectManager = new TestProjectSnapshotManager(Workspace);
projectManager.ProjectAdded(Project); projectManager.HostProjectAdded(HostProject_For_1_1);
projectManager.ProjectUpdated(new ProjectSnapshotUpdateContext(Project) projectManager.WorkspaceProjectAdded(WorkspaceProject);
{
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")))),
});
var factoryService = new DefaultProjectEngineFactoryService(projectManager); var factoryService = new DefaultProjectEngineFactoryService(projectManager, FallbackFactory, CustomFactories);
// Act // Act
var engine = factoryService.Create("/TestPath/SomePath/", b => var engine = factoryService.Create("/TestPath/SomePath/", b =>
@ -88,6 +143,7 @@ namespace Microsoft.VisualStudio.Editor.Razor
// Assert // Assert
Assert.Single(engine.Engine.Features.OfType<MyCoolNewFeature>()); 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.MvcViewDocumentClassifierPass>());
Assert.Single(engine.Engine.Features.OfType<Mvc1_X.ViewComponentTagHelperPass>()); Assert.Single(engine.Engine.Features.OfType<Mvc1_X.ViewComponentTagHelperPass>());
} }
@ -97,17 +153,10 @@ namespace Microsoft.VisualStudio.Editor.Razor
{ {
// Arrange // Arrange
var projectManager = new TestProjectSnapshotManager(Workspace); var projectManager = new TestProjectSnapshotManager(Workspace);
projectManager.ProjectAdded(Project); projectManager.HostProjectAdded(HostProject_For_1_0);
projectManager.ProjectUpdated(new ProjectSnapshotUpdateContext(Project) projectManager.WorkspaceProjectAdded(WorkspaceProject);
{
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")))),
});
var factoryService = new DefaultProjectEngineFactoryService(projectManager); var factoryService = new DefaultProjectEngineFactoryService(projectManager, FallbackFactory, CustomFactories);
// Act // Act
var engine = factoryService.Create("/TestPath/SomePath/", b => var engine = factoryService.Create("/TestPath/SomePath/", b =>
@ -117,26 +166,41 @@ namespace Microsoft.VisualStudio.Editor.Razor
// Assert // Assert
Assert.Single(engine.Engine.Features.OfType<MyCoolNewFeature>()); Assert.Single(engine.Engine.Features.OfType<MyCoolNewFeature>());
Assert.Single(engine.Engine.Features.OfType<Mvc1_X.MvcViewDocumentClassifierPass>()); Assert.Empty(engine.Engine.Features.OfType<MvcLatest.ViewComponentTagHelperDescriptorProvider>());
Assert.Empty(engine.Engine.Features.OfType<Mvc1_X.ViewComponentTagHelperPass>()); Assert.Empty(engine.Engine.Features.OfType<MvcLatest.MvcViewDocumentClassifierPass>());
Assert.Empty(engine.Engine.Features.OfType<MvcLatest.ViewComponentTagHelperPass>());
} }
[Fact] [Fact]
public void Create_HigherMvcVersion_UsesLatest() public void Create_UnknownProject_UsesVersion2_0()
{ {
// Arrange // Arrange
var projectManager = new TestProjectSnapshotManager(Workspace); 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( b.Features.Add(new MyCoolNewFeature());
RazorLanguageVersion.Latest,
ProjectExtensibilityConfigurationKind.ApproximateMatch,
new ProjectExtensibilityAssembly(new AssemblyIdentity("Microsoft.AspNetCore.Mvc.Razor", new Version("3.0.0.0"))),
new ProjectExtensibilityAssembly(new AssemblyIdentity("Microsoft.AspNetCore.Razor", new Version("3.0.0.0")))),
}); });
var factoryService = new DefaultProjectEngineFactoryService(projectManager); // 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 // Act
var engine = factoryService.Create("/TestPath/SomePath/", b => var engine = factoryService.Create("/TestPath/SomePath/", b =>
@ -146,49 +210,10 @@ namespace Microsoft.VisualStudio.Editor.Razor
// Assert // Assert
Assert.Single(engine.Engine.Features.OfType<MyCoolNewFeature>()); Assert.Single(engine.Engine.Features.OfType<MyCoolNewFeature>());
Assert.Single(engine.Engine.Features.OfType<MvcLatest.MvcViewDocumentClassifierPass>()); Assert.Empty(engine.Engine.Features.OfType<DefaultTagHelperDescriptorProvider>());
Assert.Single(engine.Engine.Features.OfType<MvcLatest.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_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>());
} }
private class MyCoolNewFeature : IRazorEngineFeature 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;
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Razor; using Microsoft.CodeAnalysis.Razor;
using Microsoft.CodeAnalysis.Razor.Editor; 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 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 projectChangedArgs = new ProjectChangeEventArgs(projectSnapshot, ProjectChangeKind.Changed);
var called = false; 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 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 projectChangedArgs = new ProjectChangeEventArgs(projectSnapshot, ProjectChangeKind.TagHelpersChanged);
var called = false; 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 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 projectChangedArgs = new ProjectChangeEventArgs(projectSnapshot, ProjectChangeKind.Changed);
var called = false; var called = false;

View File

@ -15,6 +15,8 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Razor.Runtime\Microsoft.AspNetCore.Razor.Runtime.csproj" /> <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" /> <ProjectReference Include="..\Microsoft.VisualStudio.Editor.Razor.Test.Common\Microsoft.VisualStudio.Editor.Razor.Test.Common.csproj" />
</ItemGroup> </ItemGroup>

View File

@ -18,6 +18,8 @@
</ItemGroup> </ItemGroup>
<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.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.Runtime\Microsoft.AspNetCore.Razor.Runtime.csproj" />
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Razor\Microsoft.AspNetCore.Razor.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