未验证 提交 fe97c06f 编写于 作者: M msftbot[bot] 提交者: GitHub

Merge pull request #43875 from dotnet/merges/release/dev16.6-to-release/dev16.7-preview1

Merge release/dev16.6 to release/dev16.7-preview1
......@@ -4,7 +4,6 @@
using System.Linq;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Editor.UnitTests;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.LanguageServices.CSharp.Utilities;
......@@ -47,15 +46,9 @@ static string DefaultNamespaceOfSingleProject(TestEnvironment environment)
[InlineData(null)]
public void SetProperty_MaxSupportedLangVersion_CPS(LanguageVersion? maxSupportedLangVersion)
{
var catalog = TestEnvironment.s_exportCatalog.Value
.WithParts(
typeof(CSharpParseOptionsChangingService));
const LanguageVersion attemptedVersion = LanguageVersion.CSharp8;
var factory = ExportProviderCache.GetOrCreateExportProviderFactory(catalog);
using (var environment = new TestEnvironment(exportProviderFactory: factory))
using (var environment = new TestEnvironment(typeof(CSharpParseOptionsChangingService)))
using (var cpsProject = CSharpHelpers.CreateCSharpCPSProject(environment, "Test"))
{
var project = environment.Workspace.CurrentSolution.Projects.Single();
......@@ -82,15 +75,9 @@ public void SetProperty_MaxSupportedLangVersion_CPS(LanguageVersion? maxSupporte
[WpfFact]
public void SetProperty_MaxSupportedLangVersion_CPS_NotSet()
{
var catalog = TestEnvironment.s_exportCatalog.Value
.WithParts(
typeof(CSharpParseOptionsChangingService));
const LanguageVersion attemptedVersion = LanguageVersion.CSharp8;
var factory = ExportProviderCache.GetOrCreateExportProviderFactory(catalog);
using (var environment = new TestEnvironment(exportProviderFactory: factory))
using (var environment = new TestEnvironment(typeof(CSharpParseOptionsChangingService)))
using (var cpsProject = CSharpHelpers.CreateCSharpCPSProject(environment, "Test"))
{
var project = environment.Workspace.CurrentSolution.Projects.Single();
......
......@@ -180,12 +180,7 @@ string getCurrentCompilationOutputAssemblyPath()
[InlineData(null)]
public void SetProperty_MaxSupportedLangVersion(LanguageVersion? maxSupportedLangVersion)
{
var catalog = TestEnvironment.s_exportCatalog.Value
.WithParts(
typeof(CSharpParseOptionsChangingService));
var factory = ExportProviderCache.GetOrCreateExportProviderFactory(catalog);
using var environment = new TestEnvironment(exportProviderFactory: factory);
using var environment = new TestEnvironment(typeof(CSharpParseOptionsChangingService));
var hierarchy = environment.CreateHierarchy("CSharpProject", "Bin", projectRefPath: null, projectCapabilities: "CSharp");
var storage = Assert.IsAssignableFrom<IVsBuildPropertyStorage>(hierarchy);
......@@ -220,12 +215,7 @@ public void SetProperty_MaxSupportedLangVersion(LanguageVersion? maxSupportedLan
[WpfFact]
public void SetProperty_MaxSupportedLangVersion_NotSet()
{
var catalog = TestEnvironment.s_exportCatalog.Value
.WithParts(
typeof(CSharpParseOptionsChangingService));
var factory = ExportProviderCache.GetOrCreateExportProviderFactory(catalog);
using var environment = new TestEnvironment(exportProviderFactory: factory);
using var environment = new TestEnvironment(typeof(CSharpParseOptionsChangingService));
var hierarchy = environment.CreateHierarchy("CSharpProject", "Bin", projectRefPath: null, projectCapabilities: "CSharp");
var storage = Assert.IsAssignableFrom<IVsBuildPropertyStorage>(hierarchy);
......
......@@ -96,15 +96,18 @@ internal sealed class VisualStudioProject
private readonly HashSet<IDynamicFileInfoProvider> _eventSubscriptionTracker = new HashSet<IDynamicFileInfoProvider>();
/// <summary>
/// map original dynamic file path to <see cref="DynamicFileInfo.FilePath"/>
///
/// original dyanmic file path points to something like xxx.cshtml that are given to project system
/// and <see cref="DynamicFileInfo.FilePath"/> points to a mapped file path provided by <see cref="IDynamicFileInfoProvider"/>
/// and how and what it got mapped to is up to the provider.
///
/// Workspace will only knows about <see cref="DynamicFileInfo.FilePath"/> but not the original dynamic file path
/// Map of the original dynamic file path to the <see cref="DynamicFileInfo.FilePath"/> that was associated with it.
///
/// For example, the key is something like Page.cshtml which is given to us from the project system calling
/// <see cref="AddDynamicSourceFile(string, ImmutableArray{string})"/>. The value of the map is a generated file that
/// corresponds to the original path, say Page.g.cs. If we were given a file by the project system but no
/// <see cref="IDynamicFileInfoProvider"/> provided a file for it, we will record the value as null so we still can track
/// the addition of the .cshtml file for a later call to <see cref="RemoveDynamicSourceFile(string)"/>.
///
/// The workspace snapshot will only have a document with <see cref="DynamicFileInfo.FilePath"/> (the value) but not the
/// original dynamic file path (the key).
/// </summary>
private readonly Dictionary<string, string> _dynamicFilePathMaps = new Dictionary<string, string>();
private readonly Dictionary<string, string?> _dynamicFilePathMaps = new Dictionary<string, string?>();
private readonly BatchingDocumentCollection _sourceFiles;
private readonly BatchingDocumentCollection _additionalFiles;
......@@ -612,43 +615,71 @@ public void RemoveAnalyzerConfigFile(string fullPath)
public void AddDynamicSourceFile(string dynamicFilePath, ImmutableArray<string> folders)
{
DynamicFileInfo? fileInfo = null;
IDynamicFileInfoProvider? providerForFileInfo = null;
var extension = FileNameUtilities.GetExtension(dynamicFilePath)?.TrimStart('.');
if (extension?.Length == 0)
{
return;
fileInfo = null;
}
foreach (var provider in _dynamicFileInfoProviders)
else
{
// skip unrelated providers
if (!provider.Metadata.Extensions.Any(e => string.Equals(e, extension, StringComparison.OrdinalIgnoreCase)))
foreach (var provider in _dynamicFileInfoProviders)
{
continue;
}
// skip unrelated providers
if (!provider.Metadata.Extensions.Any(e => string.Equals(e, extension, StringComparison.OrdinalIgnoreCase)))
{
continue;
}
// Don't get confused by _filePath and filePath.
// VisualStudioProject._filePath points to csproj/vbproj of the project
// and the parameter filePath points to dynamic file such as ASP.NET .g.cs files.
//
// Also, provider is free-threaded. so fine to call Wait rather than JTF.
var fileInfo = provider.Value.GetDynamicFileInfoAsync(
projectId: Id, projectFilePath: _filePath, filePath: dynamicFilePath, CancellationToken.None).WaitAndGetResult_CanCallOnBackground(CancellationToken.None);
// Don't get confused by _filePath and filePath.
// VisualStudioProject._filePath points to csproj/vbproj of the project
// and the parameter filePath points to dynamic file such as ASP.NET .g.cs files.
//
// Also, provider is free-threaded. so fine to call Wait rather than JTF.
fileInfo = provider.Value.GetDynamicFileInfoAsync(
projectId: Id, projectFilePath: _filePath, filePath: dynamicFilePath, CancellationToken.None).WaitAndGetResult_CanCallOnBackground(CancellationToken.None);
if (fileInfo == null)
if (fileInfo != null)
{
fileInfo = FixUpDynamicFileInfo(fileInfo, dynamicFilePath);
providerForFileInfo = provider.Value;
break;
}
}
}
lock (_gate)
{
if (_dynamicFilePathMaps.ContainsKey(dynamicFilePath))
{
continue;
// TODO: if we have a duplicate, we are not calling RemoveDynamicFileInfoAsync since we
// don't want to call with that under a lock. If we are getting duplicates we have bigger problems
// at that point since our workspace is generally out of sync with the project system.
// Given we're taking this as a late fix prior to a release, I don't think it's worth the added
// risk to handle a case that wasn't handled before either.
throw new ArgumentException($"{dynamicFilePath} has already been added to this project.");
}
fileInfo = FixUpDynamicFileInfo(fileInfo, dynamicFilePath);
// Record the mapping from the dynamic file path to the source file it generated. We will record
// 'null' if no provider was able to produce a source file for this input file. That could happen
// if the provider (say ASP.NET Razor) doesn't recognize the file, or the wrong type of file
// got passed through the system. That's not a failure from the project system's perspective:
// adding dynamic files is a hint at best that doesn't impact it.
_dynamicFilePathMaps.Add(dynamicFilePath, fileInfo?.FilePath);
// remember map between original dynamic file path to DynamicFileInfo.FilePath
_dynamicFilePathMaps.Add(dynamicFilePath, fileInfo.FilePath);
_sourceFiles.AddDynamicFile(provider.Value, fileInfo, folders);
return;
if (fileInfo != null)
{
// If fileInfo is not null, that means we found a provider so this should be not-null as well
// since we had to go through the earlier assignment.
Contract.ThrowIfNull(providerForFileInfo);
_sourceFiles.AddDynamicFile(providerForFileInfo, fileInfo, folders);
}
}
}
private DynamicFileInfo FixUpDynamicFileInfo(DynamicFileInfo fileInfo, string filePath)
private static DynamicFileInfo FixUpDynamicFileInfo(DynamicFileInfo fileInfo, string filePath)
{
// we might change contract and just throw here. but for now, we keep existing contract where one can return null for DynamicFileInfo.FilePath.
// In this case we substitute the file being generated from so we still have some path.
......@@ -662,7 +693,26 @@ private DynamicFileInfo FixUpDynamicFileInfo(DynamicFileInfo fileInfo, string fi
public void RemoveDynamicSourceFile(string dynamicFilePath)
{
var provider = _sourceFiles.RemoveDynamicFile(_dynamicFilePathMaps[dynamicFilePath]);
IDynamicFileInfoProvider provider;
lock (_gate)
{
if (!_dynamicFilePathMaps.TryGetValue(dynamicFilePath, out var sourceFilePath))
{
throw new ArgumentException($"{dynamicFilePath} wasn't added by a previous call to {nameof(AddDynamicSourceFile)}");
}
_dynamicFilePathMaps.Remove(dynamicFilePath);
// If we got a null path back, it means we never had a source file to add. In that case,
// we're done
if (sourceFilePath == null)
{
return;
}
provider = _sourceFiles.RemoveDynamicFile(sourceFilePath);
}
// provider is free-threaded. so fine to call Wait rather than JTF
provider.RemoveDynamicFileInfoAsync(
......@@ -671,14 +721,22 @@ public void RemoveDynamicSourceFile(string dynamicFilePath)
private void OnDynamicFileInfoUpdated(object sender, string dynamicFilePath)
{
if (!_dynamicFilePathMaps.TryGetValue(dynamicFilePath, out var fileInfoPath))
string? fileInfoPath;
lock (_gate)
{
// given file doesn't belong to this project.
// this happen since the event this is handling is shared between all projects
return;
if (!_dynamicFilePathMaps.TryGetValue(dynamicFilePath, out fileInfoPath))
{
// given file doesn't belong to this project.
// this happen since the event this is handling is shared between all projects
return;
}
}
_sourceFiles.ProcessFileChange(dynamicFilePath, fileInfoPath);
if (fileInfoPath != null)
{
_sourceFiles.ProcessFileChange(dynamicFilePath, fileInfoPath);
}
}
#endregion
......
' Licensed to the .NET Foundation under one or more agreements.
' The .NET Foundation licenses this file to you under the MIT license.
' See the LICENSE file in the project root for more information.
Imports System.Collections.Immutable
Imports System.Threading
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.Test.Utilities
Imports Microsoft.CodeAnalysis.UnitTests
Imports Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Framework
Imports Roslyn.Test.Utilities
Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim
<[UseExportProvider]>
Public Class DynamicFileTests
<WpfFact>
Public Sub AddAndRemoveFileWhenDynamicFileInfoProviderProducesNothing()
Using environment = New TestEnvironment(GetType(TestDynamicFileInfoProviderThatProducesNoFiles))
Dim project = environment.ProjectFactory.CreateAndAddToWorkspace(
"project",
LanguageNames.CSharp)
Const DynamicFileName As String = "DynamicFile.cshtml"
project.AddDynamicSourceFile(DynamicFileName, ImmutableArray(Of String).Empty)
project.RemoveDynamicSourceFile(DynamicFileName)
End Using
End Sub
<WpfFact>
Public Sub AddAndRemoveFileWhenDynamicFileInfoProviderProducesSomething()
Using environment = New TestEnvironment(GetType(TestDynamicFileInfoProviderThatProducesFiles))
Dim project = environment.ProjectFactory.CreateAndAddToWorkspace(
"project",
LanguageNames.CSharp)
Const DynamicFileName As String = "DynamicFile.cshtml"
project.AddDynamicSourceFile(DynamicFileName, ImmutableArray(Of String).Empty)
Dim dynamicSourceFile = environment.Workspace.CurrentSolution.Projects.Single().Documents.Single()
Assert.Equal(
TestDynamicFileInfoProviderThatProducesFiles.GetDynamicFileText(DynamicFileName),
dynamicSourceFile.GetTextSynchronously(CancellationToken.None).ToString())
project.RemoveDynamicSourceFile(DynamicFileName)
Assert.Empty(environment.Workspace.CurrentSolution.Projects.Single().Documents)
End Using
End Sub
<WpfFact>
Public Sub AddAndRemoveFileAndAddAgain()
Using environment = New TestEnvironment(GetType(TestDynamicFileInfoProviderThatProducesFiles))
Dim project = environment.ProjectFactory.CreateAndAddToWorkspace(
"project",
LanguageNames.CSharp)
Const DynamicFileName As String = "DynamicFile.cshtml"
project.AddDynamicSourceFile(DynamicFileName, ImmutableArray(Of String).Empty)
project.RemoveDynamicSourceFile(DynamicFileName)
project.AddDynamicSourceFile(DynamicFileName, ImmutableArray(Of String).Empty)
End Using
End Sub
<WpfFact>
Public Sub AddAndRemoveExtensionlessFile()
Using environment = New TestEnvironment(GetType(TestDynamicFileInfoProviderThatProducesFiles))
Dim project = environment.ProjectFactory.CreateAndAddToWorkspace(
"project",
LanguageNames.CSharp)
Const DynamicFileName As String = "DynamicFile"
project.AddDynamicSourceFile(DynamicFileName, ImmutableArray(Of String).Empty)
Assert.Empty(environment.Workspace.CurrentSolution.Projects.Single().Documents)
project.RemoveDynamicSourceFile(DynamicFileName)
Assert.Empty(environment.Workspace.CurrentSolution.Projects.Single().Documents)
End Using
End Sub
End Class
End Namespace
......@@ -37,7 +37,7 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Fr
Friend Class TestEnvironment
Implements IDisposable
Friend Shared ReadOnly s_exportCatalog As Lazy(Of ComposableCatalog) = New Lazy(Of ComposableCatalog)(
Private Shared ReadOnly s_exportCatalog As Lazy(Of ComposableCatalog) = New Lazy(Of ComposableCatalog)(
Function()
Dim catalog = TestExportProvider.EntireAssemblyCatalogWithCSharpAndVisualBasic
catalog = catalog.WithParts(GetType(FileChangeWatcherProvider),
......@@ -61,19 +61,25 @@ Namespace Microsoft.VisualStudio.LanguageServices.UnitTests.ProjectSystemShim.Fr
Private ReadOnly _workspace As VisualStudioWorkspaceImpl
Private ReadOnly _projectFilePaths As New List(Of String)
Public Sub New(Optional solutionIsFullyLoaded As Boolean = True, Optional exportProviderFactory As IExportProviderFactory = Nothing)
Public Sub New(ParamArray extraParts As Type())
Dim exportCatalog = s_exportCatalog.Value
If exportProviderFactory Is Nothing Then
exportProviderFactory = ExportProviderCache.GetOrCreateExportProviderFactory(s_exportCatalog.Value)
' Don't create a new catalog instance if we aren't adding extra parts; some tests create a
' TestEnvironment without extra parts more than once in the same test and that breaks the validation
' made in SingleExportProviderFactory that prevents multiple compositions being created with unique
' catalogs in the same test.
If extraParts.Any() Then
exportCatalog = exportCatalog.WithParts(extraParts)
End If
Dim exportProviderFactory = ExportProviderCache.GetOrCreateExportProviderFactory(exportCatalog)
ExportProvider = exportProviderFactory.CreateExportProvider()
_workspace = ExportProvider.GetExportedValue(Of VisualStudioWorkspaceImpl)
ThreadingContext = ExportProvider.GetExportedValue(Of IThreadingContext)()
Interop.WrapperPolicy.s_ComWrapperFactory = MockComWrapperFactory.Instance
Dim mockServiceProvider As MockServiceProvider = ExportProvider.GetExportedValue(Of MockServiceProvider)()
mockServiceProvider.MockMonitorSelection = New MockShellMonitorSelection(solutionIsFullyLoaded)
mockServiceProvider.MockMonitorSelection = New MockShellMonitorSelection(True)
ServiceProvider = mockServiceProvider
End Sub
......
......@@ -3,10 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
using System.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Test.Utilities;
......@@ -45,37 +42,12 @@ public void TestInvalidArgument2()
});
}
#region Helpers
internal static Lazy<IDynamicFileInfoProvider, FileExtensionsMetadata> GetDynamicFileInfoProvider()
{
var catalog = ExportProviderCache.CreateTypeCatalog(new Type[] { typeof(TestDynamicFileInfoProvider) });
var catalog = ExportProviderCache.CreateTypeCatalog(new Type[] { typeof(TestDynamicFileInfoProviderThatProducesNoFiles) });
var factory = ExportProviderCache.GetOrCreateExportProviderFactory(catalog);
return factory.CreateExportProvider().GetExport<IDynamicFileInfoProvider, FileExtensionsMetadata>();
}
[ExportDynamicFileInfoProvider("cshtml", "vbhtml")]
[Shared]
[PartNotDiscoverable]
internal class TestDynamicFileInfoProvider : IDynamicFileInfoProvider
{
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public TestDynamicFileInfoProvider()
{
}
public event EventHandler<string> Updated;
public Task<DynamicFileInfo> GetDynamicFileInfoAsync(ProjectId projectId, string projectFilePath, string filePath, CancellationToken cancellationToken)
=> Task.FromResult<DynamicFileInfo>(null);
public Task RemoveDynamicFileInfoAsync(ProjectId projectId, string projectFilePath, string filePath, CancellationToken cancellationToken)
=> Task.CompletedTask;
private void OnUpdate()
=> Updated?.Invoke(this, "test");
}
#endregion
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Roslyn.Test.Utilities;
namespace Microsoft.CodeAnalysis.Test.Utilities
{
[ExportDynamicFileInfoProvider("cshtml", "vbhtml")]
[Shared]
[PartNotDiscoverable]
internal class TestDynamicFileInfoProviderThatProducesFiles : IDynamicFileInfoProvider
{
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public TestDynamicFileInfoProviderThatProducesFiles()
{
}
public event EventHandler<string> Updated;
public Task<DynamicFileInfo> GetDynamicFileInfoAsync(ProjectId projectId, string projectFilePath, string filePath, CancellationToken cancellationToken)
{
return Task.FromResult(new DynamicFileInfo(
filePath + ".fromdynamicfile",
SourceCodeKind.Regular,
new TestTextLoader(GetDynamicFileText(filePath)),
new TestDocumentServiceProvider()));
}
public static string GetDynamicFileText(string filePath)
{
if (filePath.EndsWith(".cshtml"))
{
return "// dynamic file from " + filePath;
}
else
{
return "' dynamic file from " + filePath;
}
}
public Task RemoveDynamicFileInfoAsync(ProjectId projectId, string projectFilePath, string filePath, CancellationToken cancellationToken)
=> Task.CompletedTask;
private void OnUpdate()
=> Updated?.Invoke(this, "test");
}
}
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
namespace Microsoft.CodeAnalysis.Test.Utilities
{
[ExportDynamicFileInfoProvider("cshtml", "vbhtml")]
[Shared]
[PartNotDiscoverable]
internal class TestDynamicFileInfoProviderThatProducesNoFiles : IDynamicFileInfoProvider
{
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public TestDynamicFileInfoProviderThatProducesNoFiles()
{
}
public event EventHandler<string> Updated;
public Task<DynamicFileInfo> GetDynamicFileInfoAsync(ProjectId projectId, string projectFilePath, string filePath, CancellationToken cancellationToken)
=> Task.FromResult<DynamicFileInfo>(null);
public Task RemoveDynamicFileInfoAsync(ProjectId projectId, string projectFilePath, string filePath, CancellationToken cancellationToken)
=> Task.CompletedTask;
private void OnUpdate()
=> Updated?.Invoke(this, "test");
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册