зеркало из https://github.com/dotnet/razor.git
Merge pull request #3061 from sharwell/formatting
Use the real HTML formatter
This commit is contained in:
Коммит
c9f93d4d10
|
@ -83,6 +83,12 @@
|
|||
|
||||
Versions below this comment are not managed by automation and can be changed as needed.
|
||||
-->
|
||||
<PropertyGroup>
|
||||
<!-- Several packages from the editor are used for testing HTML support, and share the following version. -->
|
||||
<Tooling_HtmlEditorPackageVersion>16.10.57-preview1</Tooling_HtmlEditorPackageVersion>
|
||||
<!-- Several packages share the MS.CA.Testing version -->
|
||||
<Tooling_MicrosoftCodeAnalysisTestingVersion>1.0.1-beta1.21103.2</Tooling_MicrosoftCodeAnalysisTestingVersion>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Label="Manual">
|
||||
<MicrosoftExtensionsNonCapturingTimerSourcesPackageVersion>5.0.0-preview.4.20205.1</MicrosoftExtensionsNonCapturingTimerSourcesPackageVersion>
|
||||
<BenchmarkDotNetPackageVersion>0.12.1</BenchmarkDotNetPackageVersion>
|
||||
|
@ -93,15 +99,16 @@
|
|||
<MicrosoftInternalVisualStudioShellEmbeddablePackageVersion>16.4.29305.180</MicrosoftInternalVisualStudioShellEmbeddablePackageVersion>
|
||||
<MicrosoftNETCoreApp50PackageVersion>$(MicrosoftNETCoreAppRuntimewinx64PackageVersion)</MicrosoftNETCoreApp50PackageVersion>
|
||||
<!-- Packages from dotnet/roslyn -->
|
||||
<MicrosoftCodeAnalysisAnalyzerTestingPackageVersion>1.0.1-beta1.21103.2</MicrosoftCodeAnalysisAnalyzerTestingPackageVersion>
|
||||
<MicrosoftCodeAnalysisAnalyzerTestingPackageVersion>$(Tooling_MicrosoftCodeAnalysisTestingVersion)</MicrosoftCodeAnalysisAnalyzerTestingPackageVersion>
|
||||
<MicrosoftCodeAnalysisTestingVerifiersXunitPackageVersion>$(Tooling_MicrosoftCodeAnalysisTestingVersion)</MicrosoftCodeAnalysisTestingVerifiersXunitPackageVersion>
|
||||
<MicrosoftNetCompilersToolsetPackageVersion>3.9.0-2.20573.10</MicrosoftNetCompilersToolsetPackageVersion>
|
||||
<MicrosoftServiceHubFrameworkPackageVersion>2.7.313-preview</MicrosoftServiceHubFrameworkPackageVersion>
|
||||
<MicrosoftVisualStudioCoreUtilityPackageVersion>16.8.272</MicrosoftVisualStudioCoreUtilityPackageVersion>
|
||||
<MicrosoftVisualStudioCoreUtilityPackageVersion>16.10.8</MicrosoftVisualStudioCoreUtilityPackageVersion>
|
||||
<MicrosoftVisualStudioComponentModelHostPackageVersion>16.0.467</MicrosoftVisualStudioComponentModelHostPackageVersion>
|
||||
<MicrosoftVisualStudioImageCatalogPackageVersion>16.8.30406.65-pre</MicrosoftVisualStudioImageCatalogPackageVersion>
|
||||
<MicrosoftVisualStudioEditorPackageVersion>16.8.272</MicrosoftVisualStudioEditorPackageVersion>
|
||||
<MicrosoftVisualStudioLanguagePackageVersion>16.8.272</MicrosoftVisualStudioLanguagePackageVersion>
|
||||
<MicrosoftVisualStudioLanguageIntellisensePackageVersion>16.8.272</MicrosoftVisualStudioLanguageIntellisensePackageVersion>
|
||||
<MicrosoftVisualStudioLanguagePackageVersion>16.10.8</MicrosoftVisualStudioLanguagePackageVersion>
|
||||
<MicrosoftVisualStudioLanguageIntellisensePackageVersion>16.10.8</MicrosoftVisualStudioLanguageIntellisensePackageVersion>
|
||||
<MicrosoftVisualStudioLanguageServerClientImplementationPackageVersion>16.9.150</MicrosoftVisualStudioLanguageServerClientImplementationPackageVersion>
|
||||
<MicrosoftVisualStudioPackageLanguageService150PackageVersion>16.7.30204.53-pre</MicrosoftVisualStudioPackageLanguageService150PackageVersion>
|
||||
<MicrosoftVisualStudioLiveSharePackageVersion>0.3.1074</MicrosoftVisualStudioLiveSharePackageVersion>
|
||||
|
@ -113,9 +120,17 @@
|
|||
<MicrosoftVisualStudioShellInterop163DesignTimePackageVersion>16.3.29316.127</MicrosoftVisualStudioShellInterop163DesignTimePackageVersion>
|
||||
<MicrosoftVisualStudioShell150PackageVersion>16.7.30204.53-pre</MicrosoftVisualStudioShell150PackageVersion>
|
||||
<MicrosoftVisualStudioShellInteropPackageVersion>7.10.6073</MicrosoftVisualStudioShellInteropPackageVersion>
|
||||
<MicrosoftVisualStudioTextDataPackageVersion>16.8.272</MicrosoftVisualStudioTextDataPackageVersion>
|
||||
<MicrosoftVisualStudioTextUIPackageVersion>16.8.272</MicrosoftVisualStudioTextUIPackageVersion>
|
||||
<MicrosoftVisualStudioThreadingPackageVersion>16.8.55</MicrosoftVisualStudioThreadingPackageVersion>
|
||||
<MicrosoftVisualStudioTextDataPackageVersion>16.10.8</MicrosoftVisualStudioTextDataPackageVersion>
|
||||
<MicrosoftVisualStudioTextImplementationPackageVersion>16.10.8</MicrosoftVisualStudioTextImplementationPackageVersion>
|
||||
<MicrosoftVisualStudioTextLogicPackageVersion>16.10.8</MicrosoftVisualStudioTextLogicPackageVersion>
|
||||
<MicrosoftVisualStudioTextUIPackageVersion>16.10.8</MicrosoftVisualStudioTextUIPackageVersion>
|
||||
<MicrosoftVisualStudioThreadingPackageVersion>16.9.45-alpha</MicrosoftVisualStudioThreadingPackageVersion>
|
||||
<MicrosoftVisualStudioWebPackageVersion>16.10.0-preview-1-31008-014</MicrosoftVisualStudioWebPackageVersion>
|
||||
<MicrosoftWebToolsLanguagesHtmlPackageVersion>$(Tooling_HtmlEditorPackageVersion)</MicrosoftWebToolsLanguagesHtmlPackageVersion>
|
||||
<MicrosoftWebToolsLanguagesLanguageServerServerPackageVersion>$(Tooling_HtmlEditorPackageVersion)</MicrosoftWebToolsLanguagesLanguageServerServerPackageVersion>
|
||||
<MicrosoftWebToolsLanguagesSharedPackageVersion>$(Tooling_HtmlEditorPackageVersion)</MicrosoftWebToolsLanguagesSharedPackageVersion>
|
||||
<MicrosoftWebToolsLanguagesSharedEditorPackageVersion>$(Tooling_HtmlEditorPackageVersion)</MicrosoftWebToolsLanguagesSharedEditorPackageVersion>
|
||||
<MicrosoftWebToolsSharedPackageVersion>$(Tooling_HtmlEditorPackageVersion)</MicrosoftWebToolsSharedPackageVersion>
|
||||
<MonoAddinsPackageVersion>1.3.8</MonoAddinsPackageVersion>
|
||||
<MonoDevelopSdkPackageVersion>1.0.15</MonoDevelopSdkPackageVersion>
|
||||
<MoqPackageVersion>4.16.0</MoqPackageVersion>
|
||||
|
@ -123,7 +138,7 @@
|
|||
<NewtonsoftJsonPackageVersion>12.0.2</NewtonsoftJsonPackageVersion>
|
||||
<OmniSharpExtensionsLanguageServerPackageVersion>0.18.1</OmniSharpExtensionsLanguageServerPackageVersion>
|
||||
<OmniSharpMSBuildPackageVersion>1.33.0</OmniSharpMSBuildPackageVersion>
|
||||
<StreamJsonRpcPackageVersion>2.7.66-alpha</StreamJsonRpcPackageVersion>
|
||||
<StreamJsonRpcPackageVersion>2.7.72</StreamJsonRpcPackageVersion>
|
||||
<SystemPrivateUriPackageVersion>4.3.2</SystemPrivateUriPackageVersion>
|
||||
<SystemCompositionPackageVersion>1.0.31.0</SystemCompositionPackageVersion>
|
||||
<SystemCollectionsImmutablePackageVersion>5.0.0</SystemCollectionsImmutablePackageVersion>
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
<PackageReference Include="Microsoft.CodeAnalysis.EditorFeatures.Text" Version="$(Tooling_MicrosoftCodeAnalysisEditorFeaturesTextPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Language.Intellisense" Version="$(MicrosoftVisualStudioLanguageIntellisensePackageVersion)" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Language" Version="$(MicrosoftVisualStudioLanguagePackageVersion)" />
|
||||
<PackageReference Include="StreamJsonRpc" Version="$(StreamJsonRpcPackageVersion)" />
|
||||
<PackageReference Include="System.Collections.Immutable" Version="$(SystemCollectionsImmutablePackageVersion)" />
|
||||
<PackageReference Include="System.Private.Uri" Version="$(SystemPrivateUriPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Reflection;
|
||||
using Microsoft.AspNetCore.Razor.Test.Common;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Test
|
||||
{
|
||||
public static class EditorTestCompositions
|
||||
{
|
||||
public static readonly TestComposition Editor = TestComposition.Empty
|
||||
.AddAssemblies(
|
||||
// Microsoft.VisualStudio.Text.Implementation.dll:
|
||||
Assembly.Load("Microsoft.VisualStudio.Text.Implementation, Version=16.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"))
|
||||
.AddParts(
|
||||
typeof(TestExportJoinableTaskContext));
|
||||
}
|
||||
}
|
|
@ -21,8 +21,7 @@ expected: @"
|
|||
@{
|
||||
if (true) { }
|
||||
}
|
||||
",
|
||||
triggerCharacter: "}");
|
||||
");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -42,8 +41,7 @@ expected: @"
|
|||
{
|
||||
}
|
||||
}
|
||||
",
|
||||
triggerCharacter: "}");
|
||||
");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -75,8 +73,7 @@ expected: @"
|
|||
{
|
||||
}
|
||||
}
|
||||
",
|
||||
triggerCharacter: "}");
|
||||
");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -92,8 +89,7 @@ expected: @"
|
|||
@{
|
||||
var x = 'foo';
|
||||
}
|
||||
",
|
||||
triggerCharacter: ";");
|
||||
");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -111,8 +107,7 @@ expected: @"
|
|||
var x = @""
|
||||
foo"";
|
||||
}
|
||||
",
|
||||
triggerCharacter: ";");
|
||||
");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,8 +20,7 @@ input: @"
|
|||
}
|
||||
}
|
||||
",
|
||||
expected: @"
|
||||
@code {
|
||||
expected: @"@code {
|
||||
public class Foo { }
|
||||
public interface Bar
|
||||
{
|
||||
|
@ -41,8 +40,7 @@ var x = ""foo"";
|
|||
<div>
|
||||
</div>
|
||||
",
|
||||
expected: @"
|
||||
@{
|
||||
expected: @"@{
|
||||
var x = ""foo"";
|
||||
}
|
||||
<div>
|
||||
|
@ -61,8 +59,7 @@ void Method() { <div></div> }
|
|||
}
|
||||
}
|
||||
",
|
||||
expected: @"
|
||||
@functions {
|
||||
expected: @"@functions {
|
||||
public class Foo
|
||||
{
|
||||
void Method()
|
||||
|
@ -85,8 +82,7 @@ void Method() { @DateTime.Now }
|
|||
}
|
||||
}
|
||||
",
|
||||
expected: @"
|
||||
@code {
|
||||
expected: @"@code {
|
||||
public class Foo
|
||||
{
|
||||
void Method()
|
||||
|
@ -109,8 +105,7 @@ void Method() { @(DateTime.Now) }
|
|||
}
|
||||
}
|
||||
",
|
||||
expected: @"
|
||||
@functions {
|
||||
expected: @"@functions {
|
||||
public class Foo
|
||||
{
|
||||
void Method()
|
||||
|
@ -138,8 +133,7 @@ void Method() { }
|
|||
<script></script>
|
||||
}
|
||||
",
|
||||
expected: @"
|
||||
@functions {
|
||||
expected: @"@functions {
|
||||
public class Foo
|
||||
{
|
||||
void Method() { }
|
||||
|
@ -165,8 +159,7 @@ void Method() { }
|
|||
}
|
||||
}
|
||||
",
|
||||
expected: @"
|
||||
@functions {
|
||||
expected: @"@functions {
|
||||
public class Foo
|
||||
{
|
||||
@* This is a Razor Comment *@
|
||||
|
@ -187,8 +180,7 @@ input: @"
|
|||
}
|
||||
}
|
||||
",
|
||||
expected: @"
|
||||
@functions {
|
||||
expected: @"@functions {
|
||||
public class Foo
|
||||
{
|
||||
@* This is a Razor Comment *@
|
||||
|
@ -258,8 +250,7 @@ Hello World
|
|||
}
|
||||
}
|
||||
",
|
||||
expected: @"
|
||||
@functions {
|
||||
expected: @"@functions {
|
||||
public class Foo { }
|
||||
public interface Bar
|
||||
{
|
||||
|
@ -294,8 +285,7 @@ public class HelloWorld
|
|||
public class Bar {}
|
||||
}
|
||||
",
|
||||
expected: @"
|
||||
Hello World
|
||||
expected: @"Hello World
|
||||
@code {
|
||||
public class HelloWorld
|
||||
{
|
||||
|
@ -317,8 +307,7 @@ input: @"
|
|||
}
|
||||
}
|
||||
",
|
||||
expected: @"
|
||||
@functions {
|
||||
expected: @"@functions {
|
||||
public class Foo
|
||||
{
|
||||
}
|
||||
|
@ -335,8 +324,7 @@ input: @"
|
|||
public class Foo{
|
||||
}}
|
||||
",
|
||||
expected: @"
|
||||
@functions {
|
||||
expected: @"@functions {
|
||||
public class Foo
|
||||
{
|
||||
}
|
||||
|
@ -352,8 +340,7 @@ input: @"
|
|||
@functions {public class Foo{}
|
||||
}
|
||||
",
|
||||
expected: @"
|
||||
@functions {
|
||||
expected: @"@functions {
|
||||
public class Foo { }
|
||||
}
|
||||
");
|
||||
|
@ -368,9 +355,8 @@ Hello World
|
|||
@functions {public class Foo{}
|
||||
}
|
||||
",
|
||||
expected: @"
|
||||
Hello World
|
||||
@functions {
|
||||
expected: @"Hello World
|
||||
@functions {
|
||||
public class Foo { }
|
||||
}
|
||||
");
|
||||
|
@ -385,10 +371,9 @@ input: @"
|
|||
public class Foo{}
|
||||
}
|
||||
",
|
||||
expected: @"
|
||||
@functions {
|
||||
expected: @" @functions {
|
||||
public class Foo { }
|
||||
}
|
||||
}
|
||||
");
|
||||
}
|
||||
|
||||
|
@ -424,8 +409,7 @@ void Method(){
|
|||
}
|
||||
}
|
||||
",
|
||||
expected: @"
|
||||
@using System.Buffers
|
||||
expected: @"@using System.Buffers
|
||||
@functions{
|
||||
public class Foo
|
||||
{
|
||||
|
@ -470,8 +454,7 @@ input: @"
|
|||
}
|
||||
}
|
||||
",
|
||||
expected: @"
|
||||
@code {
|
||||
expected: @"@code {
|
||||
public class Foo { }
|
||||
void Method()
|
||||
{
|
||||
|
@ -492,8 +475,7 @@ input: @"
|
|||
}
|
||||
}
|
||||
",
|
||||
expected: @"
|
||||
@code {
|
||||
expected: @"@code {
|
||||
public class Foo { }
|
||||
void Method()
|
||||
{
|
||||
|
@ -537,8 +519,7 @@ input: @"
|
|||
}
|
||||
}
|
||||
",
|
||||
expected: @"
|
||||
@code {
|
||||
expected: @"@code {
|
||||
public class Foo { }
|
||||
void Method()
|
||||
{
|
||||
|
|
|
@ -21,8 +21,7 @@ expected: @"
|
|||
@code {
|
||||
public class Foo { }
|
||||
}
|
||||
",
|
||||
triggerCharacter: "}");
|
||||
");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -41,8 +40,7 @@ expected: @"
|
|||
{
|
||||
}
|
||||
}
|
||||
",
|
||||
triggerCharacter: "}");
|
||||
");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -58,8 +56,7 @@ expected: @"
|
|||
@code {
|
||||
public void Foo { }
|
||||
}
|
||||
",
|
||||
triggerCharacter: "}");
|
||||
");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -78,8 +75,7 @@ expected: @"
|
|||
{
|
||||
}
|
||||
}
|
||||
",
|
||||
triggerCharacter: "}");
|
||||
");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -95,8 +91,7 @@ expected: @"
|
|||
@code {
|
||||
public string Foo { get; set; }
|
||||
}
|
||||
",
|
||||
triggerCharacter: "}");
|
||||
");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -116,8 +111,7 @@ expected: @"
|
|||
get; set;
|
||||
}
|
||||
}
|
||||
",
|
||||
triggerCharacter: "}");
|
||||
");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -132,8 +126,7 @@ expected: @"
|
|||
@code {
|
||||
public string Foo { get; set; }
|
||||
}
|
||||
",
|
||||
triggerCharacter: "}");
|
||||
");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -149,8 +142,7 @@ expected: @"
|
|||
@code {
|
||||
public class Foo { private int _hello = 0; }
|
||||
}
|
||||
",
|
||||
triggerCharacter: ";");
|
||||
");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -168,8 +160,7 @@ expected: @"
|
|||
public class Foo{
|
||||
private int _hello = 0; }
|
||||
}
|
||||
",
|
||||
triggerCharacter: ";");
|
||||
");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -191,8 +182,7 @@ expected: @"
|
|||
var hello = 0;
|
||||
}
|
||||
}
|
||||
",
|
||||
triggerCharacter: ";");
|
||||
");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
@ -211,9 +201,7 @@ expected: @"
|
|||
{
|
||||
}
|
||||
}
|
||||
",
|
||||
triggerCharacter: @"
|
||||
"); // Newline
|
||||
");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,25 +4,34 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Razor.Language;
|
||||
using Microsoft.AspNetCore.Razor.LanguageServer.Common;
|
||||
using Microsoft.AspNetCore.Razor.LanguageServer.Test;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.Formatting;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using Microsoft.VisualStudio.Text;
|
||||
using Microsoft.VisualStudio.Threading;
|
||||
using Microsoft.VisualStudio.Utilities;
|
||||
using Microsoft.Web.Editor;
|
||||
using Microsoft.WebTools.Languages.Shared.ContentTypes;
|
||||
using Microsoft.WebTools.Languages.Shared.Editor.Text;
|
||||
using Microsoft.WebTools.Shared;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OmniSharp.Extensions.JsonRpc;
|
||||
using OmniSharp.Extensions.LanguageServer.Protocol;
|
||||
using OmniSharp.Extensions.LanguageServer.Protocol.Models;
|
||||
using OmniSharp.Extensions.LanguageServer.Protocol.Progress;
|
||||
using OmniSharp.Extensions.LanguageServer.Protocol.Server;
|
||||
using OmniSharp.Extensions.LanguageServer.Protocol.Server.WorkDone;
|
||||
using Xunit.Sdk;
|
||||
using Xunit;
|
||||
using FormattingOptions = OmniSharp.Extensions.LanguageServer.Protocol.Models.FormattingOptions;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting
|
||||
|
@ -40,12 +49,6 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting
|
|||
_baselineFileName = fileName;
|
||||
}
|
||||
|
||||
#if GENERATE_BASELINES
|
||||
protected bool GenerateBaselines { get; } = true;
|
||||
#else
|
||||
protected bool GenerateBaselines { get; } = false;
|
||||
#endif
|
||||
|
||||
public IProgressManager ProgressManager => throw new NotImplementedException();
|
||||
|
||||
public IServerWorkDoneManager WorkDoneManager => throw new NotImplementedException();
|
||||
|
@ -104,62 +107,84 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting
|
|||
var codeDocument = _documents[@params.HostDocumentFilePath];
|
||||
var generatedHtml = codeDocument.GetHtmlDocument().GeneratedHtml;
|
||||
generatedHtml = generatedHtml.Replace("\r", "", StringComparison.Ordinal).Replace("\n", "\r\n", StringComparison.Ordinal);
|
||||
var generatedHtmlSource = SourceText.From(generatedHtml, Encoding.UTF8);
|
||||
|
||||
// Get formatted baseline file
|
||||
var baselineInputFileName = Path.ChangeExtension(_baselineFileName, ".input.html");
|
||||
var baselineOutputFileName = Path.ChangeExtension(_baselineFileName, ".output.html");
|
||||
var editHandlerAssembly = Assembly.Load("Microsoft.WebTools.Languages.LanguageServer.Server, Version=16.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a");
|
||||
var editHandlerType = editHandlerAssembly.GetType("Microsoft.WebTools.Languages.LanguageServer.Server.Html.OperationHandlers.ApplyFormatEditsHandler", throwOnError: true);
|
||||
var bufferManagerType = editHandlerAssembly.GetType("Microsoft.WebTools.Languages.LanguageServer.Server.Shared.Buffer.BufferManager", throwOnError: true);
|
||||
|
||||
var baselineInputFile = TestFile.Create(baselineInputFileName, GetType().GetTypeInfo().Assembly);
|
||||
var baselineOutputFile = TestFile.Create(baselineOutputFileName, GetType().GetTypeInfo().Assembly);
|
||||
var exportProvider = EditorTestCompositions.Editor.ExportProviderFactory.CreateExportProvider();
|
||||
var contentTypeService = exportProvider.GetExportedValue<IContentTypeRegistryService>();
|
||||
|
||||
if (GenerateBaselines)
|
||||
{
|
||||
if (baselineInputFile.Exists())
|
||||
{
|
||||
// If it already exists, we only want to update if the input is different.
|
||||
var inputContent = baselineInputFile.ReadAllText();
|
||||
if (string.Equals(inputContent, generatedHtml, StringComparison.Ordinal))
|
||||
{
|
||||
return response;
|
||||
}
|
||||
}
|
||||
contentTypeService.AddContentType(HtmlContentTypeDefinition.HtmlContentType, new[] { StandardContentTypeNames.Text });
|
||||
|
||||
var baselineInputFilePath = Path.Combine(_projectPath, baselineInputFileName);
|
||||
File.WriteAllText(baselineInputFilePath, generatedHtml);
|
||||
var textBufferFactoryService = exportProvider.GetExportedValue<ITextBufferFactoryService>();
|
||||
var textBufferListeners = Array.Empty<Lazy<IWebTextBufferListener, IOrderedComponentContentTypes>>();
|
||||
var bufferManager = Activator.CreateInstance(bufferManagerType, new object[] { contentTypeService, textBufferFactoryService, textBufferListeners });
|
||||
var joinableTaskFactoryThreadSwitcher = typeof(IdAttribute).Assembly.GetType("Microsoft.WebTools.Shared.Threading.JoinableTaskFactoryThreadSwitcher", throwOnError: true);
|
||||
var threadSwitcher = (IThreadSwitcher)Activator.CreateInstance(joinableTaskFactoryThreadSwitcher, new object[] { new JoinableTaskContext().Factory });
|
||||
var applyFormatEditsHandler = Activator.CreateInstance(editHandlerType, new object[] { bufferManager, threadSwitcher, textBufferFactoryService });
|
||||
|
||||
var baselineOutputFilePath = Path.Combine(_projectPath, baselineOutputFileName);
|
||||
File.WriteAllText(baselineOutputFilePath, generatedHtml);
|
||||
// Make sure the buffer manager knows about the source document
|
||||
var documentUri = DocumentUri.From($"file:///{@params.HostDocumentFilePath}");
|
||||
var contentTypeName = HtmlContentTypeDefinition.HtmlContentType;
|
||||
var initialContent = generatedHtml;
|
||||
var snapshotVersionFromLSP = 0;
|
||||
Assert.IsAssignableFrom<ITextSnapshot>(bufferManager.GetType().GetMethod("CreateBuffer").Invoke(bufferManager, new object[] { documentUri, contentTypeName, initialContent, snapshotVersionFromLSP }));
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
if (!baselineInputFile.Exists())
|
||||
{
|
||||
throw new XunitException($"The resource {baselineInputFileName} was not found.");
|
||||
}
|
||||
|
||||
if (!baselineOutputFile.Exists())
|
||||
{
|
||||
throw new XunitException($"The resource {baselineOutputFileName} was not found.");
|
||||
}
|
||||
|
||||
var baselineInputHtml = baselineInputFile.ReadAllText();
|
||||
if (!string.Equals(baselineInputHtml, generatedHtml, StringComparison.Ordinal))
|
||||
{
|
||||
throw new XunitException($"The baseline for {_baselineFileName} is out of date.");
|
||||
}
|
||||
|
||||
var baselineOutputHtml = baselineOutputFile.ReadAllText();
|
||||
var baselineInputText = SourceText.From(baselineInputHtml);
|
||||
var baselineOutputText = SourceText.From(baselineOutputHtml);
|
||||
var changes = SourceTextDiffer.GetMinimalTextChanges(baselineInputText, baselineOutputText, lineDiffOnly: false);
|
||||
var edits = changes.Select(c => c.AsTextEdit(baselineInputText)).ToArray();
|
||||
response.Edits = edits;
|
||||
var requestType = editHandlerAssembly.GetType("Microsoft.WebTools.Languages.LanguageServer.Server.ContainedLanguage.ApplyFormatEditsParamForOmniSharp", throwOnError: true);
|
||||
var serializedValue = $@"{{
|
||||
""Options"": {{
|
||||
""UseSpaces"": {(@params.Options.InsertSpaces ? "true" : "false")},
|
||||
""TabSize"": {@params.Options.TabSize},
|
||||
""IndentSize"": {@params.Options.TabSize}
|
||||
}},
|
||||
""SpanToFormat"": {{
|
||||
""start"": {@params.ProjectedRange.AsTextSpan(generatedHtmlSource).Start},
|
||||
""length"": {@params.ProjectedRange.AsTextSpan(generatedHtmlSource).Length},
|
||||
}},
|
||||
""Uri"": ""file:///{@params.HostDocumentFilePath}"",
|
||||
""GeneratedChanges"": [
|
||||
]
|
||||
}}
|
||||
";
|
||||
var request = JsonConvert.DeserializeObject(serializedValue, requestType);
|
||||
var resultTask = (Task)applyFormatEditsHandler.GetType().GetRuntimeMethod("Handle", new Type[] { requestType, typeof(CancellationToken) }).Invoke(applyFormatEditsHandler, new object[] { request, CancellationToken.None });
|
||||
var result = resultTask.GetType().GetProperty(nameof(Task<int>.Result)).GetValue(resultTask);
|
||||
var rawTextChanges = result.GetType().GetProperty("TextChanges").GetValue(result);
|
||||
var serializedTextChanges = JsonConvert.SerializeObject(rawTextChanges, Newtonsoft.Json.Formatting.Indented);
|
||||
var textChanges = JsonConvert.DeserializeObject<HtmlFormatterTextEdit[]>(serializedTextChanges);
|
||||
response.Edits = textChanges.Select(change => change.AsTextEdit(SourceText.From(generatedHtml))).ToArray();
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private struct HtmlFormatterTextEdit
|
||||
{
|
||||
#pragma warning disable CS0649 // Field 'name' is never assigned to, and will always have its default value
|
||||
public int Position;
|
||||
public int Length;
|
||||
public string NewText;
|
||||
#pragma warning restore CS0649 // Field 'name' is never assigned to, and will always have its default value
|
||||
|
||||
public TextEdit AsTextEdit(SourceText sourceText)
|
||||
{
|
||||
var startLinePosition = sourceText.Lines.GetLinePosition(Position);
|
||||
var endLinePosition = sourceText.Lines.GetLinePosition(Position + Length);
|
||||
|
||||
return new TextEdit
|
||||
{
|
||||
Range = new OmniSharp.Extensions.LanguageServer.Protocol.Models.Range()
|
||||
{
|
||||
Start = new Position(startLinePosition.Line, startLinePosition.Character),
|
||||
End = new Position(endLinePosition.Line, endLinePosition.Character),
|
||||
},
|
||||
NewText = NewText,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private static Document GetCSharpDocument(RazorCodeDocument codeDocument, FormattingOptions options)
|
||||
{
|
||||
var adhocWorkspace = new AdhocWorkspace();
|
||||
|
|
|
@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Razor.Language.IntegrationTests;
|
|||
using Microsoft.AspNetCore.Razor.LanguageServer.Common;
|
||||
using Microsoft.CodeAnalysis.Razor.ProjectSystem;
|
||||
using Microsoft.CodeAnalysis.Testing;
|
||||
using Microsoft.CodeAnalysis.Testing.Verifiers;
|
||||
using Microsoft.CodeAnalysis.Text;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Moq;
|
||||
|
@ -87,14 +88,10 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting
|
|||
var edited = ApplyEdits(source, edits);
|
||||
var actual = edited.ToString();
|
||||
|
||||
#if GENERATE_BASELINES
|
||||
Assert.False(true, "GENERATE_BASELINES is set to true.");
|
||||
#else
|
||||
Assert.Equal(expected, actual, ignoreLineEndingDifferences: true);
|
||||
#endif
|
||||
new XUnitVerifier().EqualOrDiff(expected, actual);
|
||||
}
|
||||
|
||||
protected async Task RunOnTypeFormattingTestAsync(string input, string expected, string triggerCharacter, int tabSize = 4, bool insertSpaces = true, string fileKind = null)
|
||||
protected async Task RunOnTypeFormattingTestAsync(string input, string expected, int tabSize = 4, bool insertSpaces = true, string fileKind = null)
|
||||
{
|
||||
// Arrange
|
||||
fileKind ??= FileKinds.Component;
|
||||
|
@ -122,11 +119,7 @@ namespace Microsoft.AspNetCore.Razor.LanguageServer.Formatting
|
|||
var edited = ApplyEdits(source, edits);
|
||||
var actual = edited.ToString();
|
||||
|
||||
#if GENERATE_BASELINES
|
||||
Assert.False(true, "GENERATE_BASELINES is set to true.");
|
||||
#else
|
||||
Assert.Equal(expected, actual);
|
||||
#endif
|
||||
new XUnitVerifier().EqualOrDiff(expected, actual);
|
||||
}
|
||||
|
||||
private (RazorLanguageKind, TextEdit[]) GetFormattedEdits(RazorCodeDocument codeDocument, string expected, int positionBeforeTriggerChar)
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
|
||||
~~~~~ ~
|
||||
~~~~~~ ~~~~~ ~~~~~
|
||||
~~~~ ~~~~~~~ ~ ~
|
||||
~
|
||||
~
|
|
@ -1,6 +0,0 @@
|
|||
|
||||
~~~~~ ~
|
||||
~~~~~~ ~~~~~ ~~~~~
|
||||
~~~~ ~~~~~~~ ~ ~
|
||||
~
|
||||
~
|
|
@ -1,6 +0,0 @@
|
|||
|
||||
~~~~~ ~
|
||||
~~~~~~ ~~~~~ ~~~~~
|
||||
~~~~ ~~~~~~~ ~ ~
|
||||
~
|
||||
~
|
|
@ -1,6 +0,0 @@
|
|||
|
||||
~~~~~ ~
|
||||
~~~~~~ ~~~~~ ~~~~~
|
||||
~~~~ ~~~~~~~ ~ ~
|
||||
~
|
||||
~
|
|
@ -1,6 +0,0 @@
|
|||
|
||||
~~~~~ ~
|
||||
~~~~~~ ~~~~~ ~~~~~
|
||||
~~~~ ~~~~~~~ ~ ~
|
||||
~
|
||||
~
|
|
@ -1,6 +0,0 @@
|
|||
|
||||
~~~~~ ~
|
||||
~~~~~~ ~~~~~ ~~~~~
|
||||
~~~~ ~~~~~~~ ~ ~
|
||||
~
|
||||
~
|
|
@ -1,4 +0,0 @@
|
|||
|
||||
~~~~~~~~~~ ~
|
||||
~~~~~~ ~~~~~ ~~~~
|
||||
~~
|
|
@ -1,4 +0,0 @@
|
|||
|
||||
~~~~~~~~~~ ~
|
||||
~~~~~~ ~~~~~ ~~~~
|
||||
~~
|
|
@ -1,4 +0,0 @@
|
|||
|
||||
~~~~~~~~~~ ~~~~~~~ ~~~~~ ~~~~
|
||||
~
|
||||
~
|
|
@ -1,4 +0,0 @@
|
|||
|
||||
~~~~~~~~~~ ~~~~~~~ ~~~~~ ~~~~
|
||||
~
|
||||
~
|
|
@ -1,27 +0,0 @@
|
|||
|
||||
~~~~~~ ~~~~~~~~~~~~~~
|
||||
~~~~~~~~~~~
|
||||
~~~~~~ ~~~~~ ~~~
|
||||
~
|
||||
~~~~~~ ~~~~~
|
||||
~
|
||||
~~~ ~~~ ~ ~~~ ~~~~~~~ ~ ~
|
||||
~~~~~~ ~~~~~~
|
||||
~~~~~~~
|
||||
~~
|
||||
~~~ ~~~ ~ ~~
|
||||
~~~~ ~~~~~~
|
||||
~~~
|
||||
~~ ~~~~~~~~~
|
||||
~~
|
||||
~
|
||||
~~~~~~ ~~~ ~~~~~~~~~~ ~ ~~~
|
||||
~
|
||||
~~~~~~ ~ ~
|
||||
~ ~~~ ~~ ~
|
||||
|
||||
~~~~ ~~~~~~~~~
|
||||
|
||||
~
|
||||
~
|
||||
~
|
|
@ -1,27 +0,0 @@
|
|||
|
||||
~~~~~~ ~~~~~~~~~~~~~~
|
||||
~~~~~~~~~~~
|
||||
~~~~~~ ~~~~~ ~~~
|
||||
~
|
||||
~~~~~~ ~~~~~
|
||||
~
|
||||
~~~ ~~~ ~ ~~~ ~~~~~~~ ~ ~
|
||||
~~~~~~ ~~~~~~
|
||||
~~~~~~~
|
||||
~~
|
||||
~~~ ~~~ ~ ~~
|
||||
~~~~ ~~~~~~
|
||||
~~~
|
||||
~~ ~~~~~~~~~
|
||||
~~
|
||||
~
|
||||
~~~~~~ ~~~ ~~~~~~~~~~ ~ ~~~
|
||||
~
|
||||
~~~~~~ ~ ~
|
||||
~ ~~~ ~~ ~
|
||||
|
||||
~~~~ ~~~~~~~~~
|
||||
|
||||
~
|
||||
~
|
||||
~
|
|
@ -1,6 +0,0 @@
|
|||
|
||||
~~~~~~~~~~ ~
|
||||
~~~~~~ ~~~~~ ~~~~
|
||||
~~~~ ~~~~~~~~ ~ ~~~~~~~~~~~~~~~ ~
|
||||
~
|
||||
~
|
|
@ -1,6 +0,0 @@
|
|||
|
||||
~~~~~~~~~~ ~
|
||||
~~~~~~ ~~~~~ ~~~~
|
||||
~~~~ ~~~~~~~~ ~ ~~~~~~~~~~~~~~~ ~
|
||||
~
|
||||
~
|
|
@ -1,7 +0,0 @@
|
|||
|
||||
<div>Foo</div>
|
||||
~~~~~~~~~~ ~
|
||||
~~~~~~ ~~~~~ ~~~~~
|
||||
~~~~~~ ~~~~~~~~~ ~~~ ~
|
||||
~
|
||||
~
|
|
@ -1,7 +0,0 @@
|
|||
|
||||
<div>Foo</div>
|
||||
~~~~~~~~~~ ~
|
||||
~~~~~~ ~~~~~ ~~~~~
|
||||
~~~~~~ ~~~~~~~~~ ~~~ ~
|
||||
~
|
||||
~
|
|
@ -1,10 +0,0 @@
|
|||
|
||||
~~~~~~~~~~ ~
|
||||
~~~~~~ ~~~~~ ~~~~
|
||||
~~~~ ~~~~~~~~ ~ ~
|
||||
~
|
||||
~
|
||||
|
||||
~~~~~~~~ Scripts {
|
||||
<script></script>
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
|
||||
~~~~~~~~~~ ~
|
||||
~~~~~~ ~~~~~ ~~~~
|
||||
~~~~ ~~~~~~~~ ~ ~
|
||||
~
|
||||
~
|
||||
|
||||
~~~~~~~~ Scripts {
|
||||
<script></script>
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
|
||||
~~~~~ ~
|
||||
~~~~~~ ~~~~~ ~~~~~
|
||||
~~~~~~ ~~~~~~~~~ ~~~ ~
|
||||
~
|
||||
~
|
|
@ -1,6 +0,0 @@
|
|||
|
||||
~~~~~ ~
|
||||
~~~~~~ ~~~~~ ~~~~~
|
||||
~~~~~~ ~~~~~~~~~ ~~~ ~
|
||||
~
|
||||
~
|
|
@ -1,6 +0,0 @@
|
|||
|
||||
~~~~~ ~
|
||||
~~~~~~ ~~~~~ ~~~~
|
||||
~~~~ ~~~~~~~~ ~ ~~~~~~~~~~~~~ ~
|
||||
~
|
||||
~
|
|
@ -1,6 +0,0 @@
|
|||
|
||||
~~~~~ ~
|
||||
~~~~~~ ~~~~~ ~~~~
|
||||
~~~~ ~~~~~~~~ ~ ~~~~~~~~~~~~~ ~
|
||||
~
|
||||
~
|
|
@ -1,6 +0,0 @@
|
|||
|
||||
~~~~~~~~~~ ~
|
||||
~~~~~~ ~~~~~ ~~~~
|
||||
~~~~ ~~~~~~~~ ~ <div></div> ~
|
||||
~
|
||||
~
|
|
@ -1,6 +0,0 @@
|
|||
|
||||
~~~~~~~~~~ ~
|
||||
~~~~~~ ~~~~~ ~~~~
|
||||
~~~~ ~~~~~~~~ ~ <div></div> ~
|
||||
~
|
||||
~
|
|
@ -1,7 +0,0 @@
|
|||
|
||||
~~~~~~~~~~ ~
|
||||
~~~~~~ ~~~~~ ~~~~
|
||||
~~ ~~~~ ~~ ~ ~~~~~ ~~~~~~~ ~~
|
||||
~~~~ ~~~~~~~~ ~ ~
|
||||
~
|
||||
~
|
|
@ -1,7 +0,0 @@
|
|||
|
||||
~~~~~~~~~~ ~
|
||||
~~~~~~ ~~~~~ ~~~~
|
||||
~~ ~~~~ ~~ ~ ~~~~~ ~~~~~~~ ~~
|
||||
~~~~ ~~~~~~~~ ~ ~
|
||||
~
|
||||
~
|
|
@ -1,6 +0,0 @@
|
|||
|
||||
~~~~~~~~~~ ~
|
||||
~~~~~~ ~~~~~ ~~~~
|
||||
~~ ~~~~ ~~ ~ ~~~~~ ~~~~~~~ ~~
|
||||
~
|
||||
~
|
|
@ -1,6 +0,0 @@
|
|||
|
||||
~~~~~~~~~~ ~
|
||||
~~~~~~ ~~~~~ ~~~~
|
||||
~~ ~~~~ ~~ ~ ~~~~~ ~~~~~~~ ~~
|
||||
~
|
||||
~
|
|
@ -1,6 +0,0 @@
|
|||
|
||||
~~
|
||||
~~~ ~ ~ ~~~~~~
|
||||
~
|
||||
<div>
|
||||
</div>
|
|
@ -1,6 +0,0 @@
|
|||
|
||||
~~
|
||||
~~~ ~ ~ ~~~~~~
|
||||
~
|
||||
<div>
|
||||
</div>
|
|
@ -1,4 +0,0 @@
|
|||
|
||||
~~~~~~~~~~ ~
|
||||
~~~~~~ ~~~~~ ~~~~~
|
||||
~
|
|
@ -1,4 +0,0 @@
|
|||
|
||||
~~~~~~~~~~ ~
|
||||
~~~~~~ ~~~~~ ~~~~~
|
||||
~
|
|
@ -1,4 +0,0 @@
|
|||
|
||||
Hello World
|
||||
~~~~~~~~~~ ~~~~~~~ ~~~~~ ~~~~~
|
||||
~
|
|
@ -1,4 +0,0 @@
|
|||
|
||||
Hello World
|
||||
~~~~~~~~~~ ~~~~~~~ ~~~~~ ~~~~~
|
||||
~
|
|
@ -1,13 +0,0 @@
|
|||
|
||||
~~~~~~~~~~ ~
|
||||
~~~~~~ ~~~~~ ~~~~~
|
||||
~~~~~~ ~~~~~~~~~ ~~~ ~
|
||||
~
|
||||
~
|
||||
Hello World
|
||||
~~~~~~~~~~ ~
|
||||
~~~~~~ ~~~~~ ~~~ ~
|
||||
~~~~ ~~~~~~ ~ ~
|
||||
~ ~
|
||||
~
|
||||
~
|
|
@ -1,13 +0,0 @@
|
|||
|
||||
~~~~~~~~~~ ~
|
||||
~~~~~~ ~~~~~ ~~~~~
|
||||
~~~~~~ ~~~~~~~~~ ~~~ ~
|
||||
~
|
||||
~
|
||||
Hello World
|
||||
~~~~~~~~~~ ~
|
||||
~~~~~~ ~~~~~ ~~~ ~
|
||||
~~~~ ~~~~~~ ~ ~
|
||||
~ ~
|
||||
~
|
||||
~
|
|
@ -1,12 +0,0 @@
|
|||
|
||||
Hello World
|
||||
~~~~~ ~
|
||||
~~~~~~ ~~~~~ ~~~~~~~~~~
|
||||
~
|
||||
~
|
||||
~
|
||||
|
||||
~~~~~~~~~~~
|
||||
|
||||
~~~~~~ ~~~~~ ~~~ ~~
|
||||
~
|
|
@ -1,12 +0,0 @@
|
|||
|
||||
Hello World
|
||||
~~~~~ ~
|
||||
~~~~~~ ~~~~~ ~~~~~~~~~~
|
||||
~
|
||||
~
|
||||
~
|
||||
|
||||
~~~~~~~~~~~
|
||||
|
||||
~~~~~~ ~~~~~ ~~~ ~~
|
||||
~
|
|
@ -1,6 +0,0 @@
|
|||
|
||||
~~~~~~~~~~ ~
|
||||
~~~~~~ ~~~~~ ~~~~~
|
||||
~~~~~~ ~~~~~~~~~ ~~~ ~
|
||||
~
|
||||
~
|
|
@ -1,6 +0,0 @@
|
|||
|
||||
~~~~~~~~~~ ~
|
||||
~~~~~~ ~~~~~ ~~~~~
|
||||
~~~~~~ ~~~~~~~~~ ~~~ ~
|
||||
~
|
||||
~
|
|
@ -1,3 +0,0 @@
|
|||
|
||||
~~~~~~~~~~ ~~~~~~~ ~~~~~ ~~~~~
|
||||
~
|
|
@ -1,3 +0,0 @@
|
|||
|
||||
~~~~~~~~~~ ~~~~~~~ ~~~~~ ~~~~~
|
||||
~
|
|
@ -1,35 +0,0 @@
|
|||
~~~~~ ~~~
|
||||
|
||||
<h1>Hello, world!</h1>
|
||||
|
||||
Welcome to your new app.
|
||||
|
||||
<SurveyPrompt Title="How is Blazor working for you?" />
|
||||
|
||||
<div class="FF"
|
||||
id="ERT">
|
||||
asdf
|
||||
<div class="3"
|
||||
id="3">
|
||||
~~~~~~~~~~<p></p>~
|
||||
</div>
|
||||
</div>
|
||||
|
||||
~~
|
||||
<div class="FF"
|
||||
id="ERT">
|
||||
asdf
|
||||
<div class="3"
|
||||
id="3">
|
||||
~~~~~~~~~~<p></p>~
|
||||
</div>
|
||||
</div>
|
||||
~
|
||||
|
||||
~~~~~~~~~~ ~
|
||||
~~~~~~ ~~~~~ ~~~
|
||||
~
|
||||
~~ ~~~~ ~~ ~ ~~~~~ ~~~~~~~ ~~
|
||||
~~~~ ~~~~~~~~ ~ ~
|
||||
~
|
||||
~
|
|
@ -1,35 +0,0 @@
|
|||
~~~~~ ~~~
|
||||
|
||||
<h1>Hello, world!</h1>
|
||||
|
||||
Welcome to your new app.
|
||||
|
||||
<SurveyPrompt Title="How is Blazor working for you?" />
|
||||
|
||||
<div class="FF"
|
||||
id="ERT">
|
||||
asdf
|
||||
<div class="3"
|
||||
id="3">
|
||||
~~~~~~~~~~<p></p>~
|
||||
</div>
|
||||
</div>
|
||||
|
||||
~~
|
||||
<div class="FF"
|
||||
id="ERT">
|
||||
asdf
|
||||
<div class="3"
|
||||
id="3">
|
||||
~~~~~~~~~~<p></p>~
|
||||
</div>
|
||||
</div>
|
||||
~
|
||||
|
||||
~~~~~~~~~~ ~
|
||||
~~~~~~ ~~~~~ ~~~
|
||||
~
|
||||
~~ ~~~~ ~~ ~ ~~~~~ ~~~~~~~ ~~
|
||||
~~~~ ~~~~~~~~ ~ ~
|
||||
~
|
||||
~
|
|
@ -1,14 +0,0 @@
|
|||
|
||||
<Counter>
|
||||
~~~~~~~~~~
|
||||
<p>~~~~~~~~~~~~~</p>
|
||||
~
|
||||
</Counter>
|
||||
|
||||
<GridTable>
|
||||
~~~~~~~~ ~~~~ ~~~ ~~ ~~~~~~
|
||||
<GridRow @onclick="SelectRow(row)">
|
||||
~~~~~~~~ ~~~~ ~~~~ ~~ ~~~~~
|
||||
<GridCell>~~~~~</GridCell>~</GridRow>
|
||||
~
|
||||
</GridTable>
|
|
@ -1,15 +0,0 @@
|
|||
|
||||
<Counter>
|
||||
~~~~~~~~~~
|
||||
<p>~~~~~~~~~~~~~</p>
|
||||
~
|
||||
</Counter>
|
||||
|
||||
<GridTable>
|
||||
~~~~~~~~ ~~~~ ~~~ ~~ ~~~~~~
|
||||
<GridRow @onclick="SelectRow(row)">
|
||||
~~~~~~~~ ~~~~ ~~~~ ~~ ~~~~~
|
||||
<GridCell>~~~~~</GridCell>~
|
||||
</GridRow>
|
||||
~
|
||||
</GridTable>
|
|
@ -1,27 +0,0 @@
|
|||
~~~~~ ~~~~~~~
|
||||
|
||||
<div
|
||||
attr='val'
|
||||
class=~~~~~~~~~~>Some Text</div>
|
||||
|
||||
~~
|
||||
~~ Hi!
|
||||
~~~ ~ ~ ~~~~~~~~
|
||||
~~~~~~~~~~~~~~~~
|
||||
<p>
|
||||
~~~ ~~~~~~ ~
|
||||
~~~ ~ ~ ~~
|
||||
~
|
||||
</p>
|
||||
~
|
||||
|
||||
~~~~~~~~~~
|
||||
~~~~
|
||||
~~~~~~~~~~~~
|
||||
|
||||
~~
|
||||
~~~~~~~~~~~~~~~~~~~ ~~
|
||||
~
|
||||
~~~~~~ ~~~~~~~~~~~~~
|
||||
~~
|
||||
~
|
|
@ -1,28 +0,0 @@
|
|||
~~~~~ ~~~~~~~
|
||||
|
||||
<div attr='val'
|
||||
class=~~~~~~~~~~>
|
||||
Some Text
|
||||
</div>
|
||||
|
||||
~~
|
||||
~~ Hi!
|
||||
~~~ ~ ~ ~~~~~~~~
|
||||
~~~~~~~~~~~~~~~~
|
||||
<p>
|
||||
~~~ ~~~~~~ ~
|
||||
~~~ ~ ~ ~~
|
||||
~
|
||||
</p>
|
||||
~
|
||||
|
||||
~~~~~~~~~~
|
||||
~~~~
|
||||
~~~~~~~~~~~~
|
||||
|
||||
~~
|
||||
~~~~~~~~~~~~~~~~~~~ ~~
|
||||
~
|
||||
~~~~~~ ~~~~~~~~~~~~~
|
||||
~~
|
||||
~
|
|
@ -1,21 +0,0 @@
|
|||
~~~~~ ~~~~~~~
|
||||
~~
|
||||
<p>
|
||||
~~
|
||||
~~~ ~ ~ ~~
|
||||
~~ ~~~~~~
|
||||
~
|
||||
|
||||
~
|
||||
~
|
||||
</p>
|
||||
<div>
|
||||
~~
|
||||
<div>
|
||||
<div>
|
||||
This is heavily nested
|
||||
</div>
|
||||
</div>
|
||||
~
|
||||
</div>
|
||||
~
|
|
@ -1,21 +0,0 @@
|
|||
~~~~~ ~~~~~~~
|
||||
~~
|
||||
<p>
|
||||
~~
|
||||
~~~ ~ ~ ~~
|
||||
~~ ~~~~~~
|
||||
~
|
||||
|
||||
~
|
||||
~
|
||||
</p>
|
||||
<div>
|
||||
~~
|
||||
<div>
|
||||
<div>
|
||||
This is heavily nested
|
||||
</div>
|
||||
</div>
|
||||
~
|
||||
</div>
|
||||
~
|
|
@ -1,20 +0,0 @@
|
|||
~~~~~ ~~~~~~~
|
||||
|
||||
<div class=~~~~~~~~~~>Some Text</div>
|
||||
|
||||
~~
|
||||
~~ Hi!
|
||||
~~~ ~ ~ ~~~~
|
||||
<p>
|
||||
~~~ ~~~~~~ ~
|
||||
~~~ ~ ~ ~~
|
||||
~~ ~~~~~~
|
||||
~
|
||||
<div>~~~~~~~~~~~~~</div>
|
||||
~
|
||||
|
||||
~~~~~~~~~~~~~
|
||||
~
|
||||
~
|
||||
</p>
|
||||
~
|
|
@ -1,20 +0,0 @@
|
|||
~~~~~ ~~~~~~~
|
||||
|
||||
<div class=~~~~~~~~~~>Some Text</div>
|
||||
|
||||
~~
|
||||
~~ Hi!
|
||||
~~~ ~ ~ ~~~~
|
||||
<p>
|
||||
~~~ ~~~~~~ ~
|
||||
~~~ ~ ~ ~~
|
||||
~~ ~~~~~~
|
||||
~
|
||||
<div>~~~~~~~~~~~~~</div>
|
||||
~
|
||||
|
||||
~~~~~~~~~~~~~
|
||||
~
|
||||
~
|
||||
</p>
|
||||
~
|
|
@ -1,22 +0,0 @@
|
|||
~~~~~ ~~~~~~~~
|
||||
|
||||
<h1 class=
|
||||
"text-danger">Error.</h1>
|
||||
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
||||
|
||||
<h3>Development Mode</h3>
|
||||
<p>
|
||||
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.</p>
|
||||
<p>
|
||||
<strong>The Development environment shouldn't be enabled for deployed applications.
|
||||
</strong>
|
||||
<div>
|
||||
<div>
|
||||
<div>
|
||||
<div>
|
||||
This is heavily nested
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</p>
|
|
@ -1,25 +0,0 @@
|
|||
~~~~~ ~~~~~~~~
|
||||
|
||||
<h1 class="text-danger">
|
||||
Error.
|
||||
</h1>
|
||||
<h2 class="text-danger">An error occurred while processing your request.</h2>
|
||||
|
||||
<h3>Development Mode</h3>
|
||||
<p>
|
||||
Swapping to <strong>Development</strong> environment will display more detailed information about the error that occurred.
|
||||
</p>
|
||||
<p>
|
||||
<strong>
|
||||
The Development environment shouldn't be enabled for deployed applications.
|
||||
</strong>
|
||||
<div>
|
||||
<div>
|
||||
<div>
|
||||
<div>
|
||||
This is heavily nested
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</p>
|
|
@ -1,8 +0,0 @@
|
|||
|
||||
<html>
|
||||
<head>
|
||||
<title>Hello</title></head>
|
||||
<body><div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -1,10 +0,0 @@
|
|||
|
||||
<html>
|
||||
<head>
|
||||
<title>Hello</title>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
|
@ -12,13 +12,24 @@
|
|||
<None Include="xunit.runner.json" CopyToOutputDirectory="PreserveNewest" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.WebTools.Languages.Html" Version="$(MicrosoftWebToolsLanguagesHtmlPackageVersion)" NoWarn="NU1701" />
|
||||
<PackageReference Include="Microsoft.WebTools.Languages.LanguageServer.Server" Version="$(MicrosoftWebToolsLanguagesLanguageServerServerPackageVersion)" NoWarn="NU1701" />
|
||||
<PackageReference Include="Microsoft.WebTools.Languages.Shared" Version="$(MicrosoftWebToolsLanguagesSharedPackageVersion)" NoWarn="NU1701" />
|
||||
<PackageReference Include="Microsoft.WebTools.Languages.Shared.Editor" Version="$(MicrosoftWebToolsLanguagesSharedEditorPackageVersion)" NoWarn="NU1701" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Text.Data" Version="$(MicrosoftVisualStudioTextDataPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Text.Implementation" Version="$(MicrosoftVisualStudioTextImplementationPackageVersion)" NoWarn="NU1701" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Text.Logic" Version="$(MicrosoftVisualStudioTextLogicPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Web" Version="$(MicrosoftVisualStudioWebPackageVersion)" NoWarn="NU1701" />
|
||||
<PackageReference Include="Microsoft.WebTools.Shared" Version="$(MicrosoftWebToolsSharedPackageVersion)" NoWarn="NU1701" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Microsoft.AspNetCore.Razor.LanguageServer\Microsoft.AspNetCore.Razor.LanguageServer.csproj" />
|
||||
<ProjectReference Include="..\Microsoft.AspNetCore.Razor.LanguageServer.Test.Common\Microsoft.AspNetCore.Razor.LanguageServer.Test.Common.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Include="Formatting\TestFiles\**\*" />
|
||||
<EmbeddedResource Include="Semantic\TestFiles\**\*" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
@ -0,0 +1,292 @@
|
|||
// 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.
|
||||
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.CodeAnalysis.Host.Mef;
|
||||
using Microsoft.VisualStudio.Composition;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Test.Common
|
||||
{
|
||||
public static class ExportProviderCache
|
||||
{
|
||||
private static readonly PartDiscovery s_partDiscovery = CreatePartDiscovery(Resolver.DefaultInstance);
|
||||
|
||||
private static readonly TestComposition s_defaultHostExportProviderComposition = TestComposition.Empty
|
||||
.AddAssemblies(MefHostServices.DefaultAssemblies);
|
||||
|
||||
private static bool _enabled;
|
||||
|
||||
private static readonly ConcurrentDictionary<string, Scope> _scopes = new ConcurrentDictionary<string, Scope>();
|
||||
private static readonly string DefaultScope = "default";
|
||||
|
||||
internal static bool Enabled => _enabled;
|
||||
|
||||
internal static ExportProvider[] ExportProvidersForCleanup
|
||||
{
|
||||
get
|
||||
{
|
||||
var scopes = _scopes.Values.ToArray();
|
||||
var defaultScope = scopes.Where(scope => scope.Name == DefaultScope);
|
||||
var allButDefault = scopes.Where(scope => scope.Name != DefaultScope);
|
||||
|
||||
// Make sure to return the default scope as the last element
|
||||
return allButDefault.Concat(defaultScope)
|
||||
.Where(scope => scope.CurrentExportProvider is { })
|
||||
.Select(scope => scope.CurrentExportProvider!)
|
||||
.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
internal static void SetEnabled_OnlyUseExportProviderAttributeCanCall(bool value)
|
||||
{
|
||||
_enabled = value;
|
||||
if (!_enabled)
|
||||
{
|
||||
foreach (var scope in _scopes.Values.ToArray())
|
||||
{
|
||||
scope.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Use to create <see cref="IExportProviderFactory"/> for default instances of <see cref="MefHostServices"/>.
|
||||
/// </summary>
|
||||
public static IExportProviderFactory GetOrCreateExportProviderFactory(IEnumerable<Assembly> assemblies)
|
||||
{
|
||||
if (assemblies is ImmutableArray<Assembly> assembliesArray &&
|
||||
assembliesArray == MefHostServices.DefaultAssemblies)
|
||||
{
|
||||
return s_defaultHostExportProviderComposition.ExportProviderFactory;
|
||||
}
|
||||
|
||||
return CreateExportProviderFactory(CreateAssemblyCatalog(assemblies), scopeName: DefaultScope);
|
||||
}
|
||||
|
||||
public static ComposableCatalog CreateAssemblyCatalog(IEnumerable<Assembly> assemblies, Resolver? resolver = null)
|
||||
{
|
||||
var discovery = resolver == null ? s_partDiscovery : CreatePartDiscovery(resolver);
|
||||
|
||||
// If we run CreatePartsAsync on the test thread we may deadlock since it'll schedule stuff back
|
||||
// on the thread.
|
||||
#pragma warning disable VSTHRD002 // Avoid problematic synchronous waits
|
||||
var parts = Task.Run(async () => await discovery.CreatePartsAsync(assemblies).ConfigureAwait(false)).Result;
|
||||
#pragma warning restore VSTHRD002 // Avoid problematic synchronous waits
|
||||
|
||||
return ComposableCatalog.Create(resolver ?? Resolver.DefaultInstance).AddParts(parts);
|
||||
}
|
||||
|
||||
public static ComposableCatalog CreateTypeCatalog(IEnumerable<Type> types, Resolver? resolver = null)
|
||||
{
|
||||
var discovery = resolver == null ? s_partDiscovery : CreatePartDiscovery(resolver);
|
||||
|
||||
// If we run CreatePartsAsync on the test thread we may deadlock since it'll schedule stuff back
|
||||
// on the thread.
|
||||
#pragma warning disable VSTHRD002 // Avoid problematic synchronous waits
|
||||
var parts = Task.Run(async () => await discovery.CreatePartsAsync(types).ConfigureAwait(false)).Result;
|
||||
#pragma warning restore VSTHRD002 // Avoid problematic synchronous waits
|
||||
|
||||
return ComposableCatalog.Create(resolver ?? Resolver.DefaultInstance).AddParts(parts);
|
||||
}
|
||||
|
||||
public static Resolver CreateResolver()
|
||||
{
|
||||
// simple assembly loader is stateless, so okay to share
|
||||
return new Resolver(SimpleAssemblyLoader.Instance);
|
||||
}
|
||||
|
||||
public static PartDiscovery CreatePartDiscovery(Resolver resolver)
|
||||
=> PartDiscovery.Combine(new AttributedPartDiscoveryV1(resolver), new AttributedPartDiscovery(resolver, isNonPublicSupported: true));
|
||||
|
||||
public static ComposableCatalog WithParts(this ComposableCatalog catalog, IEnumerable<Type> types)
|
||||
=> catalog.AddParts(CreateTypeCatalog(types).DiscoveredParts);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="ComposableCatalog"/> derived from <paramref name="catalog"/>, but with all exported
|
||||
/// parts assignable to any type in <paramref name="types"/> removed from the catalog.
|
||||
/// </summary>
|
||||
public static ComposableCatalog WithoutPartsOfTypes(this ComposableCatalog catalog, IEnumerable<Type> types)
|
||||
{
|
||||
var parts = catalog.Parts.Where(composablePartDefinition => !IsExcludedPart(composablePartDefinition));
|
||||
return ComposableCatalog.Create(Resolver.DefaultInstance).AddParts(parts);
|
||||
|
||||
bool IsExcludedPart(ComposablePartDefinition part)
|
||||
{
|
||||
return types.Any(excludedType => excludedType.IsAssignableFrom(part.Type));
|
||||
}
|
||||
}
|
||||
|
||||
public static IExportProviderFactory CreateExportProviderFactory(ComposableCatalog catalog, string? scopeName = null)
|
||||
{
|
||||
var scope = _scopes.GetOrAdd(scopeName ?? DefaultScope, scopeName => new Scope(scopeName));
|
||||
var configuration = CompositionConfiguration.Create(catalog.WithCompositionService());
|
||||
var runtimeComposition = RuntimeComposition.CreateRuntimeComposition(configuration);
|
||||
var exportProviderFactory = runtimeComposition.CreateExportProviderFactory();
|
||||
|
||||
return new SingleExportProviderFactory(scope, catalog, configuration, exportProviderFactory);
|
||||
}
|
||||
|
||||
private sealed class SingleExportProviderFactory : IExportProviderFactory
|
||||
{
|
||||
private readonly Scope _scope;
|
||||
private readonly ComposableCatalog _catalog;
|
||||
private readonly CompositionConfiguration _configuration;
|
||||
private readonly IExportProviderFactory _exportProviderFactory;
|
||||
|
||||
public SingleExportProviderFactory(Scope scope, ComposableCatalog catalog, CompositionConfiguration configuration, IExportProviderFactory exportProviderFactory)
|
||||
{
|
||||
_scope = scope;
|
||||
_catalog = catalog;
|
||||
_configuration = configuration;
|
||||
_exportProviderFactory = exportProviderFactory;
|
||||
}
|
||||
|
||||
public ExportProvider GetOrCreateExportProvider()
|
||||
{
|
||||
if (!Enabled)
|
||||
{
|
||||
// The [UseExportProvider] attribute on tests ensures that the pre- and post-conditions of methods
|
||||
// in this type are met during test conditions.
|
||||
throw new InvalidOperationException($"{nameof(ExportProviderCache)} may only be used from tests marked with {nameof(UseExportProviderAttribute)}");
|
||||
}
|
||||
|
||||
var expectedCatalog = Interlocked.CompareExchange(ref _scope.ExpectedCatalog, _catalog, null) ?? _catalog;
|
||||
RequireForSingleExportProvider(expectedCatalog == _catalog);
|
||||
|
||||
var expected = _scope.ExpectedProviderForCatalog;
|
||||
if (expected == null)
|
||||
{
|
||||
foreach (var errorCollection in _configuration.CompositionErrors)
|
||||
{
|
||||
foreach (var error in errorCollection)
|
||||
{
|
||||
foreach (var part in error.Parts)
|
||||
{
|
||||
foreach (var pair in part.SatisfyingExports)
|
||||
{
|
||||
var (importBinding, exportBindings) = (pair.Key, pair.Value);
|
||||
if (exportBindings.Count <= 1)
|
||||
{
|
||||
// Ignore composition errors for missing parts
|
||||
continue;
|
||||
}
|
||||
|
||||
if (importBinding.ImportDefinition.Cardinality != ImportCardinality.ZeroOrMore)
|
||||
{
|
||||
// This failure occurs when a binding fails because multiple exports were
|
||||
// provided but only a single one (at most) is expected. This typically occurs
|
||||
// when a test ExportProvider is created with a mock implementation without
|
||||
// first removing a value provided by default.
|
||||
throw new InvalidOperationException(
|
||||
"Failed to construct the MEF catalog for testing. Multiple exports were found for a part for which only one export is expected:" + Environment.NewLine
|
||||
+ error.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expected = _exportProviderFactory.CreateExportProvider();
|
||||
expected = Interlocked.CompareExchange(ref _scope.ExpectedProviderForCatalog, expected, null) ?? expected;
|
||||
Interlocked.CompareExchange(ref _scope.CurrentExportProvider, expected, null);
|
||||
}
|
||||
|
||||
var exportProvider = _scope.CurrentExportProvider;
|
||||
RequireForSingleExportProvider(exportProvider == expected);
|
||||
|
||||
return exportProvider!;
|
||||
}
|
||||
|
||||
ExportProvider IExportProviderFactory.CreateExportProvider()
|
||||
{
|
||||
// Currently this implementation deviates from the typical behavior of IExportProviderFactory. For the
|
||||
// duration of a single test, an instance of SingleExportProviderFactory will continue returning the
|
||||
// same ExportProvider instance each time this method is called.
|
||||
//
|
||||
// It may be clearer to refactor the implementation to only allow one call to CreateExportProvider in
|
||||
// the context of a single test. https://github.com/dotnet/roslyn/issues/25863
|
||||
return GetOrCreateExportProvider();
|
||||
}
|
||||
|
||||
private void RequireForSingleExportProvider([DoesNotReturnIf(false)] bool condition)
|
||||
{
|
||||
if (!condition)
|
||||
{
|
||||
// The ExportProvider provides services that act as singleton instances in the context of an
|
||||
// application (this include cases of multiple exports, where the 'singleton' is the list of all
|
||||
// exports matching the contract). When reasoning about the behavior of test code, it is valuable to
|
||||
// know service instances will be used in a consistent manner throughout the execution of a test,
|
||||
// regardless of whether they are passed as arguments or obtained through requests to the
|
||||
// ExportProvider.
|
||||
//
|
||||
// Restricting a test to a single ExportProvider guarantees that objects that *look* like singletons
|
||||
// will *behave* like singletons for the duration of the test. Each test is expected to create and
|
||||
// use its ExportProvider in a consistent manner.
|
||||
//
|
||||
// A test that validates remote services is allowed to create a couple of ExportProviders:
|
||||
// one for local workspace and the other for the remote one.
|
||||
//
|
||||
// When this exception is thrown by a test, it typically means one of the following occurred:
|
||||
//
|
||||
// * A test failed to pass an ExportProvider via an optional argument to a method, resulting in the
|
||||
// method attempting to create a default ExportProvider which did not match the one assigned to
|
||||
// the test.
|
||||
// * A test attempted to perform multiple test sequences in the context of a single test method,
|
||||
// rather than break up the test into distinct tests for each case.
|
||||
// * A test referenced different predefined ExportProvider instances within the context of a test.
|
||||
// Each test is expected to use the same ExportProvider throughout the test.
|
||||
throw new InvalidOperationException($"Only one {_scope.Name} {nameof(ExportProvider)} can be created in the context of a single test.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class Scope
|
||||
{
|
||||
public readonly string Name;
|
||||
public ExportProvider? CurrentExportProvider;
|
||||
public ComposableCatalog? ExpectedCatalog;
|
||||
public ExportProvider? ExpectedProviderForCatalog;
|
||||
|
||||
public Scope(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
CurrentExportProvider = null;
|
||||
ExpectedCatalog = null;
|
||||
ExpectedProviderForCatalog = null;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class SimpleAssemblyLoader : IAssemblyLoader
|
||||
{
|
||||
public static readonly IAssemblyLoader Instance = new SimpleAssemblyLoader();
|
||||
|
||||
public Assembly LoadAssembly(AssemblyName assemblyName)
|
||||
=> Assembly.Load(assemblyName);
|
||||
|
||||
public Assembly LoadAssembly(string assemblyFullName, string codeBasePath)
|
||||
{
|
||||
var assemblyName = new AssemblyName(assemblyFullName);
|
||||
if (!string.IsNullOrEmpty(codeBasePath))
|
||||
{
|
||||
assemblyName.CodeBase = codeBasePath;
|
||||
}
|
||||
|
||||
return this.LoadAssembly(assemblyName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -497,7 +497,7 @@ namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
|
|||
{
|
||||
var span = spans[i];
|
||||
var sourceSpan = span.GetSourceSpan(codeDocument.Source);
|
||||
if (sourceSpan == null)
|
||||
if (sourceSpan == SourceSpan.Undefined)
|
||||
{
|
||||
// Not in the main file, skip.
|
||||
continue;
|
||||
|
|
|
@ -10,6 +10,7 @@ using System.Runtime.InteropServices;
|
|||
using System.Text;
|
||||
using Microsoft.AspNetCore.Components;
|
||||
using Microsoft.AspNetCore.Razor.Language.CodeGeneration;
|
||||
using Microsoft.AspNetCore.Razor.Test.Common;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
using Microsoft.CodeAnalysis.Razor;
|
||||
|
@ -18,6 +19,7 @@ using Xunit.Sdk;
|
|||
|
||||
namespace Microsoft.AspNetCore.Razor.Language.IntegrationTests
|
||||
{
|
||||
[UseExportProvider]
|
||||
public class RazorIntegrationTestBase
|
||||
{
|
||||
internal const string ArbitraryWindowsPath = "x:\\dir\\subdir\\Test";
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<!-- To generate baselines, run tests with /p:GenerateBaselines=true -->
|
||||
<DefineConstants Condition="'$(GenerateBaselines)'=='true'">$(DefineConstants);GENERATE_BASELINES</DefineConstants>
|
||||
<DefineConstants>$(DefineConstants);__RemoveThisBitTo__GENERATE_BASELINES</DefineConstants>
|
||||
<TargetFrameworks>netstandard2.0;net461</TargetFrameworks>
|
||||
<TargetFrameworks>net5.0;netstandard2.0;net461</TargetFrameworks>
|
||||
<IsShipping>false</IsShipping>
|
||||
</PropertyGroup>
|
||||
|
||||
|
@ -18,10 +18,16 @@
|
|||
<PackageReference Include="Microsoft.CodeAnalysis.Analyzer.Testing" Version="$(MicrosoftCodeAnalysisAnalyzerTestingPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="$(Tooling_MicrosoftCodeAnalysisCSharpPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Razor" Version="$(MicrosoftCodeAnalysisRazorPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Testing.Verifiers.XUnit" Version="$(MicrosoftCodeAnalysisTestingVerifiersXunitPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="$(Tooling_MicrosoftCodeAnalysisWorkspacesCommonPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="$(MicrosoftExtensionsDependencyModelPackageVersion)" />
|
||||
<PackageReference Include="Microsoft.VisualStudio.Threading" Version="$(MicrosoftVisualStudioThreadingPackageVersion)" />
|
||||
<PackageReference Include="xunit.analyzers" Version="$(XunitAnalyzersPackageVersion)" />
|
||||
<PackageReference Include="Xunit.Combinatorial" Version="$(XunitCombinatorialPackageVersion)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(TargetFramework)' == 'net461'">
|
||||
<Reference Include="WindowsBase" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
@ -0,0 +1,151 @@
|
|||
// 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.
|
||||
|
||||
#nullable enable
|
||||
|
||||
// This was copied from https://github.com/dotnet/runtime/blob/39b9607807f29e48cae4652cd74735182b31182e/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis/NullableAttributes.cs
|
||||
// and updated to have the scope of the attributes be internal.
|
||||
namespace System.Diagnostics.CodeAnalysis
|
||||
{
|
||||
#if !NETCOREAPP
|
||||
|
||||
/// <summary>Specifies that null is allowed as an input even if the corresponding type disallows it.</summary>
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)]
|
||||
internal sealed class AllowNullAttribute : Attribute { }
|
||||
|
||||
/// <summary>Specifies that null is disallowed as an input even if the corresponding type allows it.</summary>
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property, Inherited = false)]
|
||||
internal sealed class DisallowNullAttribute : Attribute { }
|
||||
|
||||
/// <summary>Specifies that an output may be null even if the corresponding type disallows it.</summary>
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)]
|
||||
internal sealed class MaybeNullAttribute : Attribute { }
|
||||
|
||||
/// <summary>Specifies that an output will not be null even if the corresponding type allows it.</summary>
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)]
|
||||
internal sealed class NotNullAttribute : Attribute { }
|
||||
|
||||
/// <summary>Specifies that when a method returns <see cref="ReturnValue"/>, the parameter may be null even if the corresponding type disallows it.</summary>
|
||||
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
|
||||
internal sealed class MaybeNullWhenAttribute : Attribute
|
||||
{
|
||||
/// <summary>Initializes the attribute with the specified return value condition.</summary>
|
||||
/// <param name="returnValue">
|
||||
/// The return value condition. If the method returns this value, the associated parameter may be null.
|
||||
/// </param>
|
||||
public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;
|
||||
|
||||
/// <summary>Gets the return value condition.</summary>
|
||||
public bool ReturnValue { get; }
|
||||
}
|
||||
|
||||
/// <summary>Specifies that when a method returns <see cref="ReturnValue"/>, the parameter will not be null even if the corresponding type allows it.</summary>
|
||||
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
|
||||
internal sealed class NotNullWhenAttribute : Attribute
|
||||
{
|
||||
/// <summary>Initializes the attribute with the specified return value condition.</summary>
|
||||
/// <param name="returnValue">
|
||||
/// The return value condition. If the method returns this value, the associated parameter will not be null.
|
||||
/// </param>
|
||||
public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;
|
||||
|
||||
/// <summary>Gets the return value condition.</summary>
|
||||
public bool ReturnValue { get; }
|
||||
}
|
||||
|
||||
/// <summary>Specifies that the output will be non-null if the named parameter is non-null.</summary>
|
||||
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, AllowMultiple = true, Inherited = false)]
|
||||
internal sealed class NotNullIfNotNullAttribute : Attribute
|
||||
{
|
||||
/// <summary>Initializes the attribute with the associated parameter name.</summary>
|
||||
/// <param name="parameterName">
|
||||
/// The associated parameter name. The output will be non-null if the argument to the parameter specified is non-null.
|
||||
/// </param>
|
||||
public NotNullIfNotNullAttribute(string parameterName) => ParameterName = parameterName;
|
||||
|
||||
/// <summary>Gets the associated parameter name.</summary>
|
||||
public string ParameterName { get; }
|
||||
}
|
||||
|
||||
/// <summary>Applied to a method that will never return under any circumstance.</summary>
|
||||
[AttributeUsage(AttributeTargets.Method, Inherited = false)]
|
||||
internal sealed class DoesNotReturnAttribute : Attribute { }
|
||||
|
||||
/// <summary>Specifies that the method will not return if the associated Boolean parameter is passed the specified value.</summary>
|
||||
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
|
||||
internal sealed class DoesNotReturnIfAttribute : Attribute
|
||||
{
|
||||
/// <summary>Initializes the attribute with the specified parameter value.</summary>
|
||||
/// <param name="parameterValue">
|
||||
/// The condition parameter value. Code after the method will be considered unreachable by diagnostics if the argument to
|
||||
/// the associated parameter matches this value.
|
||||
/// </param>
|
||||
public DoesNotReturnIfAttribute(bool parameterValue) => ParameterValue = parameterValue;
|
||||
|
||||
/// <summary>Gets the condition parameter value.</summary>
|
||||
public bool ParameterValue { get; }
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if !NETCOREAPP || NETCOREAPP3_1
|
||||
|
||||
/// <summary>Specifies that the method or property will ensure that the listed field and property members have not-null values.</summary>
|
||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
|
||||
internal sealed class MemberNotNullAttribute : Attribute
|
||||
{
|
||||
/// <summary>Initializes the attribute with a field or property member.</summary>
|
||||
/// <param name="member">
|
||||
/// The field or property member that is promised to be not-null.
|
||||
/// </param>
|
||||
public MemberNotNullAttribute(string member) => Members = new[] { member };
|
||||
|
||||
/// <summary>Initializes the attribute with the list of field and property members.</summary>
|
||||
/// <param name="members">
|
||||
/// The list of field and property members that are promised to be not-null.
|
||||
/// </param>
|
||||
public MemberNotNullAttribute(params string[] members) => Members = members;
|
||||
|
||||
/// <summary>Gets field or property member names.</summary>
|
||||
public string[] Members { get; }
|
||||
}
|
||||
|
||||
/// <summary>Specifies that the method or property will ensure that the listed field and property members have not-null values when returning with the specified return value condition.</summary>
|
||||
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
|
||||
internal sealed class MemberNotNullWhenAttribute : Attribute
|
||||
{
|
||||
/// <summary>Initializes the attribute with the specified return value condition and a field or property member.</summary>
|
||||
/// <param name="returnValue">
|
||||
/// The return value condition. If the method returns this value, the associated parameter will not be null.
|
||||
/// </param>
|
||||
/// <param name="member">
|
||||
/// The field or property member that is promised to be not-null.
|
||||
/// </param>
|
||||
public MemberNotNullWhenAttribute(bool returnValue, string member)
|
||||
{
|
||||
ReturnValue = returnValue;
|
||||
Members = new[] { member };
|
||||
}
|
||||
|
||||
/// <summary>Initializes the attribute with the specified return value condition and list of field and property members.</summary>
|
||||
/// <param name="returnValue">
|
||||
/// The return value condition. If the method returns this value, the associated parameter will not be null.
|
||||
/// </param>
|
||||
/// <param name="members">
|
||||
/// The list of field and property members that are promised to be not-null.
|
||||
/// </param>
|
||||
public MemberNotNullWhenAttribute(bool returnValue, params string[] members)
|
||||
{
|
||||
ReturnValue = returnValue;
|
||||
Members = members;
|
||||
}
|
||||
|
||||
/// <summary>Gets the return value condition.</summary>
|
||||
public bool ReturnValue { get; }
|
||||
|
||||
/// <summary>Gets field or property member names.</summary>
|
||||
public string[] Members { get; }
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
|
@ -0,0 +1,243 @@
|
|||
// 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.
|
||||
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Microsoft.VisualStudio.Composition;
|
||||
using Roslyn.Utilities;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Test.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a MEF composition used for testing.
|
||||
/// </summary>
|
||||
public sealed class TestComposition
|
||||
{
|
||||
public static readonly TestComposition Empty = new TestComposition(ImmutableHashSet<Assembly>.Empty, ImmutableHashSet<Type>.Empty, ImmutableHashSet<Type>.Empty, scope: null);
|
||||
|
||||
private static readonly Dictionary<CacheKey, IExportProviderFactory> s_factoryCache = new Dictionary<CacheKey, IExportProviderFactory>();
|
||||
|
||||
private readonly struct CacheKey : IEquatable<CacheKey>
|
||||
{
|
||||
private readonly ImmutableArray<Assembly> _assemblies;
|
||||
private readonly ImmutableArray<Type> _parts;
|
||||
private readonly ImmutableArray<Type> _excludedPartTypes;
|
||||
|
||||
public CacheKey(ImmutableHashSet<Assembly> assemblies, ImmutableHashSet<Type> parts, ImmutableHashSet<Type> excludedPartTypes)
|
||||
{
|
||||
_assemblies = assemblies.OrderBy(a => a.FullName, StringComparer.Ordinal).ToImmutableArray();
|
||||
_parts = parts.OrderBy(a => a.FullName, StringComparer.Ordinal).ToImmutableArray();
|
||||
_excludedPartTypes = excludedPartTypes.OrderBy(a => a.FullName, StringComparer.Ordinal).ToImmutableArray();
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> obj is CacheKey key && Equals(key);
|
||||
|
||||
public bool Equals(CacheKey other)
|
||||
=> _parts.SequenceEqual(other._parts) &&
|
||||
_excludedPartTypes.SequenceEqual(other._excludedPartTypes) &&
|
||||
_assemblies.SequenceEqual(other._assemblies);
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var hashCode = -744873704;
|
||||
|
||||
foreach (var assembly in _assemblies)
|
||||
hashCode = hashCode * -1521134295 + assembly.GetHashCode();
|
||||
|
||||
foreach (var part in _parts)
|
||||
hashCode = hashCode * -1521134295 + part.GetHashCode();
|
||||
|
||||
foreach (var excludedPartType in _excludedPartTypes)
|
||||
hashCode = hashCode * -1521134295 + excludedPartType.GetHashCode();
|
||||
|
||||
return hashCode;
|
||||
}
|
||||
|
||||
public static bool operator ==(CacheKey left, CacheKey right)
|
||||
=> left.Equals(right);
|
||||
|
||||
public static bool operator !=(CacheKey left, CacheKey right)
|
||||
=> !(left == right);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assemblies to include in the composition.
|
||||
/// </summary>
|
||||
public readonly ImmutableHashSet<Assembly> Assemblies;
|
||||
|
||||
/// <summary>
|
||||
/// Types to exclude from the composition.
|
||||
/// All subtypes of types specified in <see cref="ExcludedPartTypes"/> and defined in <see cref="Assemblies"/> are excluded before <see cref="Parts"/> are added.
|
||||
/// </summary>
|
||||
public readonly ImmutableHashSet<Type> ExcludedPartTypes;
|
||||
|
||||
/// <summary>
|
||||
/// Additional part types to add to the composition.
|
||||
/// </summary>
|
||||
public readonly ImmutableHashSet<Type> Parts;
|
||||
|
||||
/// <summary>
|
||||
/// The scope in which to create the export provider, or <see langword="null"/> to use the default scope.
|
||||
/// </summary>
|
||||
public readonly string? Scope;
|
||||
|
||||
private readonly Lazy<IExportProviderFactory> _exportProviderFactory;
|
||||
|
||||
private TestComposition(ImmutableHashSet<Assembly> assemblies, ImmutableHashSet<Type> parts, ImmutableHashSet<Type> excludedPartTypes, string? scope)
|
||||
{
|
||||
Assemblies = assemblies;
|
||||
Parts = parts;
|
||||
ExcludedPartTypes = excludedPartTypes;
|
||||
Scope = scope;
|
||||
|
||||
_exportProviderFactory = new Lazy<IExportProviderFactory>(GetOrCreateFactory);
|
||||
}
|
||||
|
||||
#if false
|
||||
/// <summary>
|
||||
/// Returns a new instance of <see cref="HostServices"/> for the composition. This will either be a MEF composition or VS MEF composition host,
|
||||
/// depending on what layer the composition is for. Editor Features and VS layers use VS MEF composition while anything else uses System.Composition.
|
||||
/// </summary>
|
||||
public HostServices GetHostServices()
|
||||
=> VisualStudioMefHostServices.Create(ExportProviderFactory.CreateExportProvider());
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// VS MEF <see cref="ExportProvider"/>.
|
||||
/// </summary>
|
||||
public IExportProviderFactory ExportProviderFactory => _exportProviderFactory.Value;
|
||||
|
||||
private IExportProviderFactory GetOrCreateFactory()
|
||||
{
|
||||
var key = new CacheKey(Assemblies, Parts, ExcludedPartTypes);
|
||||
|
||||
lock (s_factoryCache)
|
||||
{
|
||||
if (s_factoryCache.TryGetValue(key, out var existing))
|
||||
{
|
||||
return existing;
|
||||
}
|
||||
}
|
||||
|
||||
var newFactory = ExportProviderCache.CreateExportProviderFactory(GetCatalog(), Scope);
|
||||
|
||||
lock (s_factoryCache)
|
||||
{
|
||||
if (s_factoryCache.TryGetValue(key, out var existing))
|
||||
{
|
||||
return existing;
|
||||
}
|
||||
|
||||
s_factoryCache.Add(key, newFactory);
|
||||
}
|
||||
|
||||
return newFactory;
|
||||
}
|
||||
|
||||
private ComposableCatalog GetCatalog()
|
||||
=> ExportProviderCache.CreateAssemblyCatalog(Assemblies, ExportProviderCache.CreateResolver()).WithoutPartsOfTypes(ExcludedPartTypes).WithParts(Parts);
|
||||
|
||||
public TestComposition Add(TestComposition composition)
|
||||
=> AddAssemblies(composition.Assemblies).AddParts(composition.Parts).AddExcludedPartTypes(composition.ExcludedPartTypes);
|
||||
|
||||
public TestComposition AddAssemblies(params Assembly[]? assemblies)
|
||||
=> AddAssemblies((IEnumerable<Assembly>?)assemblies);
|
||||
|
||||
public TestComposition AddAssemblies(IEnumerable<Assembly>? assemblies)
|
||||
=> WithAssemblies(Assemblies.Union(assemblies ?? Array.Empty<Assembly>()));
|
||||
|
||||
public TestComposition AddParts(IEnumerable<Type>? types)
|
||||
=> WithParts(Parts.Union(types ?? Array.Empty<Type>()));
|
||||
|
||||
public TestComposition AddParts(params Type[]? types)
|
||||
=> AddParts((IEnumerable<Type>?)types);
|
||||
|
||||
public TestComposition AddExcludedPartTypes(IEnumerable<Type>? types)
|
||||
=> WithExcludedPartTypes(ExcludedPartTypes.Union(types ?? Array.Empty<Type>()));
|
||||
|
||||
public TestComposition AddExcludedPartTypes(params Type[]? types)
|
||||
=> AddExcludedPartTypes((IEnumerable<Type>?)types);
|
||||
|
||||
public TestComposition Remove(TestComposition composition)
|
||||
=> RemoveAssemblies(composition.Assemblies).RemoveParts(composition.Parts).RemoveExcludedPartTypes(composition.ExcludedPartTypes);
|
||||
|
||||
public TestComposition RemoveAssemblies(params Assembly[]? assemblies)
|
||||
=> RemoveAssemblies((IEnumerable<Assembly>?)assemblies);
|
||||
|
||||
public TestComposition RemoveAssemblies(IEnumerable<Assembly>? assemblies)
|
||||
=> WithAssemblies(Assemblies.Except(assemblies ?? Array.Empty<Assembly>()));
|
||||
|
||||
public TestComposition RemoveParts(IEnumerable<Type>? types)
|
||||
=> WithParts(Parts.Except(types ?? Array.Empty<Type>()));
|
||||
|
||||
public TestComposition RemoveParts(params Type[]? types)
|
||||
=> RemoveParts((IEnumerable<Type>?)types);
|
||||
|
||||
public TestComposition RemoveExcludedPartTypes(IEnumerable<Type>? types)
|
||||
=> WithExcludedPartTypes(ExcludedPartTypes.Except(types ?? Array.Empty<Type>()));
|
||||
|
||||
public TestComposition RemoveExcludedPartTypes(params Type[]? types)
|
||||
=> RemoveExcludedPartTypes((IEnumerable<Type>?)types);
|
||||
|
||||
public TestComposition WithAssemblies(ImmutableHashSet<Assembly> assemblies)
|
||||
{
|
||||
if (assemblies == Assemblies)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
var testAssembly = assemblies.FirstOrDefault(IsTestAssembly);
|
||||
Verify.Operation(testAssembly == null, $"Test assemblies are not allowed in test composition: {testAssembly}. Specify explicit test parts instead.");
|
||||
|
||||
return new TestComposition(assemblies, Parts, ExcludedPartTypes, Scope);
|
||||
|
||||
static bool IsTestAssembly(Assembly assembly)
|
||||
{
|
||||
var name = assembly.GetName().Name!;
|
||||
return
|
||||
name.EndsWith(".Test", StringComparison.OrdinalIgnoreCase) ||
|
||||
name.EndsWith(".Tests", StringComparison.OrdinalIgnoreCase) ||
|
||||
name.EndsWith(".UnitTests", StringComparison.OrdinalIgnoreCase) ||
|
||||
name.IndexOf("Test.Utilities", StringComparison.OrdinalIgnoreCase) >= 0 ||
|
||||
name.IndexOf("Test.Common", StringComparison.OrdinalIgnoreCase) >= 0;
|
||||
}
|
||||
}
|
||||
|
||||
public TestComposition WithParts(ImmutableHashSet<Type> parts)
|
||||
=> (parts == Parts) ? this : new TestComposition(Assemblies, parts, ExcludedPartTypes, Scope);
|
||||
|
||||
public TestComposition WithExcludedPartTypes(ImmutableHashSet<Type> excludedPartTypes)
|
||||
=> (excludedPartTypes == ExcludedPartTypes) ? this : new TestComposition(Assemblies, Parts, excludedPartTypes, Scope);
|
||||
|
||||
public TestComposition WithScope(string? scope)
|
||||
=> scope == Scope ? this : new TestComposition(Assemblies, Parts, ExcludedPartTypes, scope);
|
||||
|
||||
/// <summary>
|
||||
/// Use for VS MEF composition troubleshooting.
|
||||
/// </summary>
|
||||
/// <returns>All composition error messages.</returns>
|
||||
internal string GetCompositionErrorLog()
|
||||
{
|
||||
var configuration = CompositionConfiguration.Create(GetCatalog());
|
||||
|
||||
var sb = new StringBuilder();
|
||||
foreach (var errorGroup in configuration.CompositionErrors)
|
||||
{
|
||||
foreach (var error in errorGroup)
|
||||
{
|
||||
sb.Append(error.Message);
|
||||
sb.AppendLine();
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
// 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.
|
||||
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Test.Common
|
||||
{
|
||||
internal partial class TestExportJoinableTaskContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a <see cref="SynchronizationContext"/> for use in cases where the synchronization context should not
|
||||
/// be used for execution of code. Attempting to execute code through this synchronization context will record
|
||||
/// an exception (with stack trace) for the first occurrence, which can be re-thrown by calling
|
||||
/// <see cref="ThrowIfSwitchOccurred"/> at an appropriate time.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This synchronization context is used in cases where code expects a synchronization context with
|
||||
/// specific properties (e.g. code is executed on a "main thread"), but no synchronization context with those
|
||||
/// properties is available. Due to the positioning of synchronization contexts within asynchronous code
|
||||
/// patterns, detection of misused synchronization contexts without crashes is often challenging. Test cases can
|
||||
/// use this synchronization context in place of a mock to capture </para>
|
||||
///
|
||||
/// <note type="important">
|
||||
/// <para>This synchronization context will not directly block the execution of scheduled tasks, and will fall
|
||||
/// back to execution on an underlying synchronization context (typically the thread pool). Tests using this
|
||||
/// synchronization context should verify the synchronization context was not used by calling
|
||||
/// <see cref="ThrowIfSwitchOccurred"/> at the end of the test.</para>
|
||||
/// </note>
|
||||
/// </remarks>
|
||||
internal sealed class DenyExecutionSynchronizationContext : SynchronizationContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Records the first case where the synchronization context was used for scheduling an operation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>The <see cref="StrongBox{T}"/> wrapper allows copies of the synchronization context (created by
|
||||
/// <see cref="CreateCopy"/>) to record information about incorrect synchronization context in the original
|
||||
/// synchronization context from which it was created.</para>
|
||||
/// </remarks>
|
||||
private readonly StrongBox<ExceptionDispatchInfo> _failedTransfer;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DenyExecutionSynchronizationContext"/> class.
|
||||
/// </summary>
|
||||
/// <param name="underlyingContext">The fallback synchronization context to use for scheduling operations
|
||||
/// posted to this synchronization context.</param>
|
||||
public DenyExecutionSynchronizationContext(SynchronizationContext? underlyingContext)
|
||||
: this(underlyingContext, mainThread: null, failedTransfer: null)
|
||||
{
|
||||
}
|
||||
|
||||
private DenyExecutionSynchronizationContext(SynchronizationContext? underlyingContext, Thread? mainThread, StrongBox<ExceptionDispatchInfo>? failedTransfer)
|
||||
{
|
||||
UnderlyingContext = underlyingContext ?? new SynchronizationContext();
|
||||
MainThread = mainThread ?? new Thread(MainThreadStart);
|
||||
_failedTransfer = failedTransfer ?? new StrongBox<ExceptionDispatchInfo>();
|
||||
}
|
||||
|
||||
internal event EventHandler? InvalidSwitch;
|
||||
|
||||
private SynchronizationContext UnderlyingContext
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="Thread"/> to treat as the main thread.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This thread will never be started, and will never have work scheduled for execution. The value is
|
||||
/// used for checking if <see cref="Thread.CurrentThread"/> matches a known "main thread", and ensures that
|
||||
/// the comparison will always result in a failure (not currently on the main thread).</para>
|
||||
/// </remarks>
|
||||
internal Thread MainThread
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
private void MainThreadStart() => throw new InvalidOperationException("This location is thought to be unreachable");
|
||||
|
||||
/// <summary>
|
||||
/// Verifies that the current synchronization context has not been used for scheduling work. If the
|
||||
/// synchronization was used, an exception is thrown with information about the first such case.
|
||||
/// </summary>
|
||||
internal void ThrowIfSwitchOccurred()
|
||||
{
|
||||
if (_failedTransfer.Value == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_failedTransfer.Value.Throw();
|
||||
}
|
||||
|
||||
public override void Post(SendOrPostCallback d, object? state)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_failedTransfer.Value == null)
|
||||
{
|
||||
ThrowFailedTransferExceptionForCapture();
|
||||
}
|
||||
}
|
||||
catch (InvalidOperationException e)
|
||||
{
|
||||
_failedTransfer.Value = ExceptionDispatchInfo.Capture(e);
|
||||
InvalidSwitch?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
#pragma warning disable VSTHRD001 // Avoid legacy thread switching APIs
|
||||
UnderlyingContext.Post(d, state);
|
||||
#pragma warning restore VSTHRD001 // Avoid legacy thread switching APIs
|
||||
}
|
||||
|
||||
public override void Send(SendOrPostCallback d, object? state)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (_failedTransfer.Value == null)
|
||||
{
|
||||
ThrowFailedTransferExceptionForCapture();
|
||||
}
|
||||
}
|
||||
catch (InvalidOperationException e)
|
||||
{
|
||||
_failedTransfer.Value = ExceptionDispatchInfo.Capture(e);
|
||||
InvalidSwitch?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
#pragma warning disable VSTHRD001 // Avoid legacy thread switching APIs
|
||||
UnderlyingContext.Send(d, state);
|
||||
#pragma warning restore VSTHRD001 // Avoid legacy thread switching APIs
|
||||
}
|
||||
|
||||
public override SynchronizationContext CreateCopy()
|
||||
=> new DenyExecutionSynchronizationContext(UnderlyingContext.CreateCopy(), MainThread, _failedTransfer);
|
||||
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowFailedTransferExceptionForCapture()
|
||||
=> throw new InvalidOperationException($"Code cannot switch to the main thread without configuring the threading context.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
// 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.
|
||||
|
||||
#nullable enable
|
||||
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Threading;
|
||||
using Microsoft.VisualStudio.Threading;
|
||||
using Xunit.Sdk;
|
||||
|
||||
#if NETFRAMEWORK
|
||||
using System.Windows.Threading;
|
||||
#endif
|
||||
|
||||
#if false
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
#endif
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Test.Common
|
||||
{
|
||||
// Starting with 15.3 the editor took a dependency on JoinableTaskContext
|
||||
// in Text.Logic and IntelliSense layers as an editor host provided service.
|
||||
[Export]
|
||||
internal partial class TestExportJoinableTaskContext
|
||||
{
|
||||
[ImportingConstructor]
|
||||
//[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
|
||||
public TestExportJoinableTaskContext()
|
||||
{
|
||||
var synchronizationContext = SynchronizationContext.Current;
|
||||
try
|
||||
{
|
||||
SynchronizationContext.SetSynchronizationContext(GetEffectiveSynchronizationContext());
|
||||
(JoinableTaskContext, SynchronizationContext) = CreateJoinableTaskContext();
|
||||
#if false
|
||||
ResetThreadAffinity(JoinableTaskContext.Factory);
|
||||
#endif
|
||||
}
|
||||
finally
|
||||
{
|
||||
SynchronizationContext.SetSynchronizationContext(synchronizationContext);
|
||||
}
|
||||
}
|
||||
|
||||
private static (JoinableTaskContext joinableTaskContext, SynchronizationContext synchronizationContext) CreateJoinableTaskContext()
|
||||
{
|
||||
Thread mainThread;
|
||||
SynchronizationContext synchronizationContext;
|
||||
#if NETFRAMEWORK
|
||||
if (SynchronizationContext.Current is DispatcherSynchronizationContext)
|
||||
{
|
||||
// The current thread is the main thread, and provides a suitable synchronization context
|
||||
mainThread = Thread.CurrentThread;
|
||||
synchronizationContext = SynchronizationContext.Current;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
// The current thread is not known to be the main thread; we have no way to know if the
|
||||
// synchronization context of the current thread will behave in a manner consistent with main thread
|
||||
// synchronization contexts, so we use DenyExecutionSynchronizationContext to track any attempted
|
||||
// use of it.
|
||||
var denyExecutionSynchronizationContext = new DenyExecutionSynchronizationContext(SynchronizationContext.Current);
|
||||
mainThread = denyExecutionSynchronizationContext.MainThread;
|
||||
synchronizationContext = denyExecutionSynchronizationContext;
|
||||
}
|
||||
|
||||
return (new JoinableTaskContext(mainThread, synchronizationContext), synchronizationContext);
|
||||
}
|
||||
|
||||
[Export]
|
||||
private JoinableTaskContext JoinableTaskContext
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
internal SynchronizationContext SynchronizationContext
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
internal static SynchronizationContext? GetEffectiveSynchronizationContext()
|
||||
{
|
||||
if (SynchronizationContext.Current is AsyncTestSyncContext asyncTestSyncContext)
|
||||
{
|
||||
SynchronizationContext? innerSynchronizationContext = null;
|
||||
asyncTestSyncContext.Send(
|
||||
_ =>
|
||||
{
|
||||
innerSynchronizationContext = SynchronizationContext.Current;
|
||||
},
|
||||
null);
|
||||
|
||||
return innerSynchronizationContext;
|
||||
}
|
||||
else
|
||||
{
|
||||
return SynchronizationContext.Current;
|
||||
}
|
||||
}
|
||||
|
||||
#if false
|
||||
/// <summary>
|
||||
/// Reset the thread affinity, in particular the designated foreground thread, to the active
|
||||
/// thread.
|
||||
/// </summary>
|
||||
internal static void ResetThreadAffinity(JoinableTaskFactory joinableTaskFactory)
|
||||
{
|
||||
// HACK: When the platform team took over several of our components they created a copy
|
||||
// of ForegroundThreadAffinitizedObject. This needs to be reset in the same way as our copy
|
||||
// does. Reflection is the only choice at the moment.
|
||||
var thread = joinableTaskFactory.Context.MainThread;
|
||||
var taskScheduler = new JoinableTaskFactoryTaskScheduler(joinableTaskFactory);
|
||||
|
||||
foreach (var assembly in System.AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
var type = assembly.GetType("Microsoft.VisualStudio.Language.Intellisense.Implementation.ForegroundThreadAffinitizedObject", throwOnError: false);
|
||||
if (type != null)
|
||||
{
|
||||
type.GetField("foregroundThread", BindingFlags.Static | BindingFlags.NonPublic)!.SetValue(null, thread);
|
||||
type.GetField("ForegroundTaskScheduler", BindingFlags.Static | BindingFlags.NonPublic)!.SetValue(null, taskScheduler);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// HACK: Part of ResetThreadAffinity
|
||||
private class JoinableTaskFactoryTaskScheduler : TaskScheduler
|
||||
{
|
||||
private readonly JoinableTaskFactory _joinableTaskFactory;
|
||||
|
||||
public JoinableTaskFactoryTaskScheduler(JoinableTaskFactory joinableTaskFactory)
|
||||
=> _joinableTaskFactory = joinableTaskFactory;
|
||||
|
||||
public override int MaximumConcurrencyLevel => 1;
|
||||
|
||||
protected override IEnumerable<Task>? GetScheduledTasks() => null;
|
||||
|
||||
protected override void QueueTask(Task task)
|
||||
{
|
||||
_joinableTaskFactory.RunAsync(async () =>
|
||||
{
|
||||
await _joinableTaskFactory.SwitchToMainThreadAsync();
|
||||
TryExecuteTask(task);
|
||||
});
|
||||
}
|
||||
|
||||
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
|
||||
{
|
||||
if (_joinableTaskFactory.Context.IsOnMainThread)
|
||||
{
|
||||
return TryExecuteTask(task);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
|
@ -0,0 +1,235 @@
|
|||
// 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.
|
||||
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using Microsoft.CodeAnalysis.Host;
|
||||
using Microsoft.VisualStudio.Composition;
|
||||
using Xunit.Sdk;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Test.Common
|
||||
{
|
||||
/// <summary>
|
||||
/// This attribute supports tests that need to use a MEF container (<see cref="ExportProvider"/>) directly or
|
||||
/// indirectly during the test sequence. It ensures production code uniformly handles the export provider created
|
||||
/// during a test, and cleans up the state before the test completes.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>This attribute serves several important functions for tests that use state variables which are otherwise
|
||||
/// shared at runtime:</para>
|
||||
/// <list type="bullet">
|
||||
/// <item>Ensures <see cref="HostServices"/> implementations all use the same <see cref="ExportProvider"/>, which is
|
||||
/// the one created by the test.</item>
|
||||
/// <item>Clears static cached values in production code holding instances of <see cref="HostServices"/>, or any
|
||||
/// object obtained from it or one of its related interfaces such as <see cref="HostLanguageServices"/>.</item>
|
||||
/// <item>Isolates tests by waiting for asynchronous operations to complete before a test is considered
|
||||
/// complete.</item>
|
||||
/// <item>When required, provides a separate <see cref="ExportProvider"/> for the <see cref="T:RemoteWorkspace"/>
|
||||
/// executing in the test process. If this provider is created during testing, it is cleaned up with the primary
|
||||
/// export provider during test teardown.</item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
|
||||
public class UseExportProviderAttribute : BeforeAfterTestAttribute
|
||||
{
|
||||
/// <summary>
|
||||
/// Asynchronous operations are expected to be cancelled at the end of the test that started them. Operations
|
||||
/// cancelled by the test are cleaned up immediately. The remaining operations are given an opportunity to run
|
||||
/// to completion. If this timeout is exceeded by the asynchronous operations running after a test completes,
|
||||
/// the test is failed.
|
||||
/// </summary>
|
||||
private static readonly TimeSpan CleanupTimeout = TimeSpan.FromMinutes(1);
|
||||
|
||||
#if false
|
||||
private MefHostServices? _hostServices;
|
||||
#endif
|
||||
|
||||
public override void Before(MethodInfo? methodUnderTest)
|
||||
{
|
||||
#if false
|
||||
MefHostServices.TestAccessor.HookServiceCreation(CreateMefHostServices);
|
||||
|
||||
// make sure we enable this for all unit tests
|
||||
AsynchronousOperationListenerProvider.Enable(enable: true, diagnostics: true);
|
||||
#endif
|
||||
|
||||
ExportProviderCache.SetEnabled_OnlyUseExportProviderAttributeCanCall(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// To the extent reasonably possible, this method resets the state of the test environment to the same state as
|
||||
/// it started, ensuring that tests running in sequence cannot influence the outcome of later tests.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>The test cleanup runs in two primary steps:</para>
|
||||
/// <list type="number">
|
||||
/// <item>Waiting for asynchronous operations started by the test to complete.</item>
|
||||
/// <item>Disposing of mutable resources created by the test.</item>
|
||||
/// <item>Clearing static state variables related to the use of MEF during a test.</item>
|
||||
/// </list>
|
||||
/// </remarks>
|
||||
public override void After(MethodInfo? methodUnderTest)
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var exportProvider in ExportProviderCache.ExportProvidersForCleanup)
|
||||
{
|
||||
DisposeExportProvider(exportProvider);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
#if false
|
||||
// Replace hooks with ones that always throw exceptions. These hooks detect cases where code executing
|
||||
// after the end of a test attempts to create an ExportProvider.
|
||||
MefHostServices.TestAccessor.HookServiceCreation(DenyMefHostServicesCreationBetweenTests);
|
||||
#endif
|
||||
|
||||
// Reset static state variables.
|
||||
#if false
|
||||
_hostServices = null;
|
||||
#endif
|
||||
ExportProviderCache.SetEnabled_OnlyUseExportProviderAttributeCanCall(false);
|
||||
}
|
||||
}
|
||||
|
||||
private static void DisposeExportProvider(ExportProvider? exportProvider)
|
||||
{
|
||||
if (exportProvider == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Dispose of the export provider, including calling Dispose for any IDisposable services created during the test.
|
||||
using var _ = exportProvider;
|
||||
|
||||
#if false
|
||||
if (exportProvider.GetExportedValues<IAsynchronousOperationListenerProvider>().SingleOrDefault() is { } listenerProvider)
|
||||
{
|
||||
if (exportProvider.GetExportedValues<IThreadingContext>().SingleOrDefault()?.HasMainThread ?? false)
|
||||
{
|
||||
// Immediately clear items from the foreground notification service for which cancellation is
|
||||
// requested. This service maintains a queue separately from Tasks, and work items scheduled for
|
||||
// execution after a delay are not immediately purged when cancellation is requested. This code
|
||||
// instructs the service to walk the list of queued work items and immediately cancel and purge any
|
||||
// which are already cancelled.
|
||||
var foregroundNotificationService = exportProvider.GetExportedValues<IForegroundNotificationService>().SingleOrDefault() as ForegroundNotificationService;
|
||||
foregroundNotificationService?.ReleaseCancelledItems();
|
||||
}
|
||||
|
||||
// Verify the synchronization context was not used incorrectly
|
||||
var testExportJoinableTaskContext = exportProvider.GetExportedValues<TestExportJoinableTaskContext>().SingleOrDefault();
|
||||
var denyExecutionSynchronizationContext = testExportJoinableTaskContext?.SynchronizationContext as TestExportJoinableTaskContext.DenyExecutionSynchronizationContext;
|
||||
|
||||
// Join remaining operations with a timeout
|
||||
using (var timeoutTokenSource = new CancellationTokenSource(CleanupTimeout))
|
||||
{
|
||||
if (denyExecutionSynchronizationContext is object)
|
||||
{
|
||||
// Immediately cancel the test if the synchronization context is improperly used
|
||||
denyExecutionSynchronizationContext.InvalidSwitch += delegate { timeoutTokenSource.CancelAfter(0); };
|
||||
denyExecutionSynchronizationContext.ThrowIfSwitchOccurred();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// This attribute cleans up the in-process and out-of-process export providers separately, so we
|
||||
// don't need to provide a workspace when waiting for operations to complete.
|
||||
var waiter = ((AsynchronousOperationListenerProvider)listenerProvider).WaitAllDispatcherOperationAndTasksAsync(workspace: null);
|
||||
waiter.JoinUsingDispatcher(timeoutTokenSource.Token);
|
||||
}
|
||||
catch (OperationCanceledException ex) when (timeoutTokenSource.IsCancellationRequested)
|
||||
{
|
||||
// If the failure was caused by an invalid thread change, throw that exception
|
||||
denyExecutionSynchronizationContext?.ThrowIfSwitchOccurred();
|
||||
|
||||
var messageBuilder = new StringBuilder("Failed to clean up listeners in a timely manner.");
|
||||
foreach (var token in ((AsynchronousOperationListenerProvider)listenerProvider).GetTokens())
|
||||
{
|
||||
messageBuilder.AppendLine().Append($" {token}");
|
||||
}
|
||||
|
||||
throw new TimeoutException(messageBuilder.ToString(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
denyExecutionSynchronizationContext?.ThrowIfSwitchOccurred();
|
||||
|
||||
foreach (var testErrorHandler in exportProvider.GetExportedValues<ITestErrorHandler>())
|
||||
{
|
||||
var exceptions = testErrorHandler.Exceptions;
|
||||
if (exceptions.Count > 0)
|
||||
{
|
||||
throw new AggregateException("Tests threw unexpected exceptions", exceptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#if false
|
||||
private MefHostServices CreateMefHostServices(IEnumerable<Assembly> assemblies)
|
||||
{
|
||||
ExportProvider exportProvider;
|
||||
|
||||
if (assemblies is ImmutableArray<Assembly> array &&
|
||||
array == MefHostServices.DefaultAssemblies &&
|
||||
ExportProviderCache.LocalExportProviderForCleanup != null)
|
||||
{
|
||||
if (_hostServices != null)
|
||||
{
|
||||
return _hostServices;
|
||||
}
|
||||
|
||||
exportProvider = ExportProviderCache.LocalExportProviderForCleanup;
|
||||
}
|
||||
else
|
||||
{
|
||||
exportProvider = ExportProviderCache.GetOrCreateExportProviderFactory(assemblies).CreateExportProvider();
|
||||
}
|
||||
|
||||
Interlocked.CompareExchange(
|
||||
ref _hostServices,
|
||||
new ExportProviderMefHostServices(exportProvider),
|
||||
null);
|
||||
|
||||
return _hostServices;
|
||||
}
|
||||
|
||||
private static MefHostServices DenyMefHostServicesCreationBetweenTests(IEnumerable<Assembly> assemblies)
|
||||
{
|
||||
// If you hit this, one of three situations occurred:
|
||||
//
|
||||
// 1. A test method that uses ExportProvider is not marked with UseExportProviderAttribute (can also be
|
||||
// applied to the containing type or a base type.
|
||||
// 2. A test attempted to create an ExportProvider during the test cleanup operations after the
|
||||
// ExportProvider was already disposed.
|
||||
// 3. A test attempted to use an ExportProvider in the constructor of the test, or during the initialization
|
||||
// of a field in the test class.
|
||||
throw new InvalidOperationException("Cannot create host services after test tear down.");
|
||||
}
|
||||
|
||||
private class ExportProviderMefHostServices : MefHostServices, IMefHostExportProvider
|
||||
{
|
||||
private readonly VisualStudioMefHostServices _vsHostServices;
|
||||
|
||||
public ExportProviderMefHostServices(ExportProvider exportProvider)
|
||||
: base(new ContainerConfiguration().CreateContainer())
|
||||
{
|
||||
_vsHostServices = VisualStudioMefHostServices.Create(exportProvider);
|
||||
}
|
||||
|
||||
protected internal override HostWorkspaceServices CreateWorkspaceServices(Workspace workspace)
|
||||
=> _vsHostServices.CreateWorkspaceServices(workspace);
|
||||
|
||||
IEnumerable<Lazy<TExtension, TMetadata>> IMefHostExportProvider.GetExports<TExtension, TMetadata>()
|
||||
=> _vsHostServices.GetExports<TExtension, TMetadata>();
|
||||
|
||||
IEnumerable<Lazy<TExtension>> IMefHostExportProvider.GetExports<TExtension>()
|
||||
=> _vsHostServices.GetExports<TExtension>();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче