diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/SolutionChangeAccumulator.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/SolutionChangeAccumulator.cs
index d8978c4761789a3af2eec8f35b7154338b0702b8..bd88465eae2c511781feb642b36ab70c70771530 100644
--- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/SolutionChangeAccumulator.cs
+++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/SolutionChangeAccumulator.cs
@@ -2,6 +2,9 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
+#nullable enable
+
+using System;
using System.Collections.Generic;
using Microsoft.CodeAnalysis;
@@ -19,6 +22,7 @@ internal class SolutionChangeAccumulator
/// if we can't give a more precise type.
///
private WorkspaceChangeKind? _workspaceChangeKind;
+ private readonly List _documentIdsRemoved = new List();
public SolutionChangeAccumulator(Solution startingSolution)
{
@@ -26,12 +30,13 @@ public SolutionChangeAccumulator(Solution startingSolution)
}
public Solution Solution { get; private set; }
+ public IEnumerable DocumentIdsRemoved => _documentIdsRemoved;
public bool HasChange => _workspaceChangeKind.HasValue;
- public WorkspaceChangeKind WorkspaceChangeKind => _workspaceChangeKind.Value;
+ public WorkspaceChangeKind WorkspaceChangeKind => _workspaceChangeKind!.Value;
- public ProjectId WorkspaceChangeProjectId { get; private set; }
- public DocumentId WorkspaceChangeDocumentId { get; private set; }
+ public ProjectId? WorkspaceChangeProjectId { get; private set; }
+ public DocumentId? WorkspaceChangeDocumentId { get; private set; }
public void UpdateSolutionForDocumentAction(Solution newSolution, WorkspaceChangeKind changeKind, IEnumerable documentIds)
{
@@ -75,6 +80,17 @@ public void UpdateSolutionForDocumentAction(Solution newSolution, WorkspaceChang
}
}
+ ///
+ /// The same as but also records
+ /// the removed documents into .
+ ///
+ public void UpdateSolutionForRemovedDocumentAction(Solution solution, WorkspaceChangeKind removeDocumentChangeKind, IEnumerable documentIdsRemoved)
+ {
+ UpdateSolutionForDocumentAction(solution, removeDocumentChangeKind, documentIdsRemoved);
+
+ _documentIdsRemoved.AddRange(documentIdsRemoved);
+ }
+
///
/// Should be called to update the solution if there isn't a specific document change kind that should be
/// given to
diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProject.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProject.cs
index ae7037c173507a4a49e168b576131cf5f4d88311..22c1a19e04c3a1c51c7081ea87943cb1c93b57b5 100644
--- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProject.cs
+++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioProject.cs
@@ -422,13 +422,7 @@ private void OnBatchScopeDisposed()
documentsToOpen,
(s, documents) => s.AddDocuments(documents),
WorkspaceChangeKind.DocumentAdded,
- (s, id) =>
- {
- // Clear any document-specific data now (like open file trackers, etc.). If we called OnRemoveDocument directly this is
- // called, but since we're doing this in one large batch we need to do it now.
- _workspace.ClearDocumentData(id);
- return s.RemoveDocument(id);
- },
+ (s, ids) => s.RemoveDocuments(ids),
WorkspaceChangeKind.DocumentRemoved);
_additionalFiles.UpdateSolutionForBatch(
@@ -445,13 +439,7 @@ private void OnBatchScopeDisposed()
return s;
},
WorkspaceChangeKind.AdditionalDocumentAdded,
- (s, id) =>
- {
- // Clear any document-specific data now (like open file trackers, etc.). If we called OnRemoveDocument directly this is
- // called, but since we're doing this in one large batch we need to do it now.
- _workspace.ClearDocumentData(id);
- return s.RemoveAdditionalDocument(id);
- },
+ (s, ids) => s.RemoveAdditionalDocuments(ids),
WorkspaceChangeKind.AdditionalDocumentRemoved);
_analyzerConfigFiles.UpdateSolutionForBatch(
@@ -460,13 +448,7 @@ private void OnBatchScopeDisposed()
analyzerConfigDocumentsToOpen,
(s, documents) => s.AddAnalyzerConfigDocuments(documents),
WorkspaceChangeKind.AnalyzerConfigDocumentAdded,
- (s, id) =>
- {
- // Clear any document-specific data now (like open file trackers, etc.). If we called OnRemoveAnalyzerConfigDocument directly this is
- // called, but since we're doing this in one large batch we need to do it now.
- _workspace.ClearDocumentData(id);
- return s.RemoveAnalyzerConfigDocument(id);
- },
+ (s, ids) => s.RemoveAnalyzerConfigDocuments(ids),
WorkspaceChangeKind.AnalyzerConfigDocumentRemoved);
// Metadata reference adding...
@@ -1570,7 +1552,7 @@ public void ReorderFiles(ImmutableArray filePaths)
List<(DocumentId documentId, SourceTextContainer textContainer)> documentsToOpen,
Func, Solution> addDocuments,
WorkspaceChangeKind addDocumentChangeKind,
- Func removeDocument,
+ Func, Solution> removeDocuments,
WorkspaceChangeKind removeDocumentChangeKind)
{
// Document adding...
@@ -1592,12 +1574,9 @@ public void ReorderFiles(ImmutableArray filePaths)
ClearAndZeroCapacity(_documentsAddedInBatch);
// Document removing...
- foreach (var documentId in _documentsRemovedInBatch)
- {
- solutionChanges.UpdateSolutionForDocumentAction(removeDocument(solutionChanges.Solution, documentId),
- removeDocumentChangeKind,
- SpecializedCollections.SingletonEnumerable(documentId));
- }
+ solutionChanges.UpdateSolutionForRemovedDocumentAction(removeDocuments(solutionChanges.Solution, _documentsRemovedInBatch.ToImmutableArray()),
+ removeDocumentChangeKind,
+ _documentsRemovedInBatch);
ClearAndZeroCapacity(_documentsRemovedInBatch);
diff --git a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs
index afa356cf0172600ade5d7a5f4dee81de4057d26a..44c81d65f6871152693d0605f147ddcf78e2f78b 100644
--- a/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs
+++ b/src/VisualStudio/Core/Def/Implementation/ProjectSystem/VisualStudioWorkspaceImpl.cs
@@ -1468,6 +1468,8 @@ internal override bool CanAddProjectReference(ProjectId referencingProject, Proj
return result != (uint)__VSREFERENCEQUERYRESULT.REFERENCE_DENY;
}
+#nullable enable
+
///
/// Applies a single operation to the workspace. should be a call to one of the protected Workspace.On* methods.
///
@@ -1496,6 +1498,11 @@ public void ApplyBatchChangeToWorkspace(Func _projectReferenceInfoMap = new Dictionary();
private ProjectReferenceInformation GetReferenceInfo_NoLock(ProjectId projectId)
diff --git a/src/Workspaces/Core/Portable/PublicAPI.Unshipped.txt b/src/Workspaces/Core/Portable/PublicAPI.Unshipped.txt
index b6abe4720320cc6060b6eadd57dce112b0458efc..fc046ba72a8ab8c08a938baf214283dcfe696d08 100644
--- a/src/Workspaces/Core/Portable/PublicAPI.Unshipped.txt
+++ b/src/Workspaces/Core/Portable/PublicAPI.Unshipped.txt
@@ -1,6 +1,10 @@
*REMOVED*Microsoft.CodeAnalysis.TextDocument.Project.set -> void
*REMOVED*Microsoft.CodeAnalysis.TextDocument.TextDocument() -> void
Microsoft.CodeAnalysis.Options.DocumentOptionSet.WithChangedOption(Microsoft.CodeAnalysis.Options.PerLanguageOption option, T value) -> Microsoft.CodeAnalysis.Options.DocumentOptionSet
+Microsoft.CodeAnalysis.Project.RemoveDocuments(System.Collections.Immutable.ImmutableArray documentIds) -> Microsoft.CodeAnalysis.Project
+Microsoft.CodeAnalysis.Solution.RemoveAdditionalDocuments(System.Collections.Immutable.ImmutableArray documentIds) -> Microsoft.CodeAnalysis.Solution
+Microsoft.CodeAnalysis.Solution.RemoveAnalyzerConfigDocuments(System.Collections.Immutable.ImmutableArray documentIds) -> Microsoft.CodeAnalysis.Solution
+Microsoft.CodeAnalysis.Solution.RemoveDocuments(System.Collections.Immutable.ImmutableArray documentIds) -> Microsoft.CodeAnalysis.Solution
Microsoft.CodeAnalysis.Solution.WithOptions(Microsoft.CodeAnalysis.Options.OptionSet options) -> Microsoft.CodeAnalysis.Solution
static Microsoft.CodeAnalysis.Formatting.Formatter.OrganizeImportsAsync(Microsoft.CodeAnalysis.Document document, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task
static Microsoft.CodeAnalysis.Simplification.Simplifier.AddImportsAnnotation.get -> Microsoft.CodeAnalysis.SyntaxAnnotation
diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs
index fc94400de51d8655fdcf6da399ac3d484409ea26..84abc817f33eb3b1d9bbe41a7919d6b1d32aac96 100644
--- a/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs
+++ b/src/Workspaces/Core/Portable/Workspace/Solution/Project.cs
@@ -14,6 +14,7 @@
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Host;
+using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Collections.Immutable;
using Roslyn.Utilities;
@@ -599,9 +600,28 @@ public TextDocument AddAnalyzerConfigDocument(string name, SourceText text, IEnu
///
public Project RemoveDocument(DocumentId documentId)
{
+ // NOTE: the method isn't checking if documentId belongs to the project. This probably should be done, but may be a compat change.
+ // https://github.com/dotnet/roslyn/issues/41211 tracks this investigation.
return this.Solution.RemoveDocument(documentId).GetProject(this.Id)!;
}
+ ///
+ /// Creates a new instance of this project updated to no longer include the specified documents.
+ ///
+ public Project RemoveDocuments(ImmutableArray documentIds)
+ {
+ foreach (var documentId in documentIds)
+ {
+ // Handling of null entries is handled by Solution.RemoveDocuments.
+ if (documentId?.ProjectId != this.Id)
+ {
+ throw new ArgumentException(string.Format(WorkspacesResources._0_is_in_a_different_project, documentId));
+ }
+ }
+
+ return this.Solution.RemoveDocuments(documentIds).GetRequiredProject(this.Id);
+ }
+
///
/// Creates a new instance of this project updated to no longer include the specified additional document.
///
diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState.cs
index 962eb68f1a9859a2578627b7b21686b4d24dfdd9..2ea5742a7ad0d363148d50ab2d389f7616b3c514 100644
--- a/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState.cs
+++ b/src/Workspaces/Core/Portable/Workspace/Solution/ProjectState.cs
@@ -784,31 +784,25 @@ private ProjectState CreateNewStateForChangedAnalyzerConfigDocuments(ImmutableSo
analyzerConfigSet: newAnalyzerConfigSet);
}
- public ProjectState RemoveDocument(DocumentId documentId)
+ public ProjectState RemoveDocuments(ImmutableArray documentIds)
{
- Debug.Assert(this.DocumentStates.ContainsKey(documentId));
-
return this.With(
projectInfo: this.ProjectInfo.WithVersion(this.Version.GetNewerVersion()),
- documentIds: _documentIds.Remove(documentId),
- documentStates: _documentStates.Remove(documentId));
+ documentIds: _documentIds.RemoveRange(documentIds),
+ documentStates: _documentStates.RemoveRange(documentIds));
}
- public ProjectState RemoveAdditionalDocument(DocumentId documentId)
+ public ProjectState RemoveAdditionalDocuments(ImmutableArray documentIds)
{
- Debug.Assert(this.AdditionalDocumentStates.ContainsKey(documentId));
-
return this.With(
projectInfo: this.ProjectInfo.WithVersion(this.Version.GetNewerVersion()),
- additionalDocumentIds: _additionalDocumentIds.Remove(documentId),
- additionalDocumentStates: _additionalDocumentStates.Remove(documentId));
+ additionalDocumentIds: _additionalDocumentIds.RemoveRange(documentIds),
+ additionalDocumentStates: _additionalDocumentStates.RemoveRange(documentIds));
}
- public ProjectState RemoveAnalyzerConfigDocument(DocumentId documentId)
+ public ProjectState RemoveAnalyzerConfigDocuments(ImmutableArray documentIds)
{
- Debug.Assert(_analyzerConfigDocumentStates.ContainsKey(documentId));
-
- var newAnalyzerConfigDocumentStates = _analyzerConfigDocumentStates.Remove(documentId);
+ var newAnalyzerConfigDocumentStates = _analyzerConfigDocumentStates.RemoveRange(documentIds);
return CreateNewStateForChangedAnalyzerConfigDocuments(newAnalyzerConfigDocumentStates);
}
diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs
index 1d1ee85e737880f2b07140676da2802fadec7641..7316afd69e0c8a1ce332432ac860ab016178f04b 100644
--- a/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs
+++ b/src/Workspaces/Core/Portable/Workspace/Solution/Solution.cs
@@ -891,7 +891,20 @@ public Solution AddAnalyzerConfigDocuments(ImmutableArray document
///
public Solution RemoveDocument(DocumentId documentId)
{
- var newState = _state.RemoveDocument(documentId);
+ if (documentId == null)
+ {
+ throw new ArgumentNullException(nameof(documentId));
+ }
+
+ return RemoveDocuments(ImmutableArray.Create(documentId));
+ }
+
+ ///
+ /// Creates a new solution instance that no longer includes the specified documents.
+ ///
+ public Solution RemoveDocuments(ImmutableArray documentIds)
+ {
+ var newState = _state.RemoveDocuments(documentIds);
if (newState == _state)
{
return this;
@@ -905,7 +918,20 @@ public Solution RemoveDocument(DocumentId documentId)
///
public Solution RemoveAdditionalDocument(DocumentId documentId)
{
- var newState = _state.RemoveAdditionalDocument(documentId);
+ if (documentId == null)
+ {
+ throw new ArgumentNullException(nameof(documentId));
+ }
+
+ return RemoveAdditionalDocuments(ImmutableArray.Create(documentId));
+ }
+
+ ///
+ /// Creates a new solution instance that no longer includes the specified additional documents.
+ ///
+ public Solution RemoveAdditionalDocuments(ImmutableArray documentIds)
+ {
+ var newState = _state.RemoveAdditionalDocuments(documentIds);
if (newState == _state)
{
return this;
@@ -914,9 +940,25 @@ public Solution RemoveAdditionalDocument(DocumentId documentId)
return new Solution(newState);
}
+ ///
+ /// Creates a new solution instance that no longer includes the specified .
+ ///
public Solution RemoveAnalyzerConfigDocument(DocumentId documentId)
{
- var newState = _state.RemoveAnalyzerConfigDocument(documentId);
+ if (documentId == null)
+ {
+ throw new ArgumentNullException(nameof(documentId));
+ }
+
+ return RemoveAnalyzerConfigDocuments(ImmutableArray.Create(documentId));
+ }
+
+ ///
+ /// Creates a new solution instance that no longer includes the specified s.
+ ///
+ public Solution RemoveAnalyzerConfigDocuments(ImmutableArray documentIds)
+ {
+ var newState = _state.RemoveAnalyzerConfigDocuments(documentIds);
if (newState == _state)
{
return this;
diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTranslationAction.Actions.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTranslationAction.Actions.cs
index 11de84429eddafe819dc6cb3541002d3e090219a..fb921f24fec03011a13d9d210ab82c536676ff61 100644
--- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTranslationAction.Actions.cs
+++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.CompilationTranslationAction.Actions.cs
@@ -33,14 +33,25 @@ public override Task InvokeAsync(Compilation oldCompilation, Cancel
public DocumentId DocumentId => _newState.Attributes.Id;
}
- internal sealed class RemoveDocumentAction : SimpleCompilationTranslationAction
+ internal sealed class RemoveDocumentsAction : CompilationTranslationAction
{
- private static readonly Func> s_action =
- async (o, d, c) => o.RemoveSyntaxTrees(await d.GetSyntaxTreeAsync(c).ConfigureAwait(false));
+ private readonly ImmutableArray _documents;
- public RemoveDocumentAction(DocumentState document)
- : base(document, s_action)
+ public RemoveDocumentsAction(ImmutableArray documents)
{
+ _documents = documents;
+ }
+
+ public override async Task InvokeAsync(Compilation oldCompilation, CancellationToken cancellationToken)
+ {
+ var syntaxTrees = new List(_documents.Length);
+ foreach (var document in _documents)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+ syntaxTrees.Add(await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false));
+ }
+
+ return oldCompilation.RemoveSyntaxTrees(syntaxTrees);
}
}
@@ -55,9 +66,10 @@ public AddDocumentsAction(ImmutableArray documents)
public override async Task InvokeAsync(Compilation oldCompilation, CancellationToken cancellationToken)
{
- var syntaxTrees = new List();
+ var syntaxTrees = new List(capacity: _documents.Length);
foreach (var document in _documents)
{
+ cancellationToken.ThrowIfCancellationRequested();
syntaxTrees.Add(await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false));
}
@@ -88,42 +100,33 @@ public override async Task InvokeAsync(Compilation oldCompilation,
}
}
- internal sealed class ProjectCompilationOptionsAction : SimpleCompilationTranslationAction
+ internal sealed class ProjectCompilationOptionsAction : CompilationTranslationAction
{
- private static readonly Func> s_action =
- (o, d, c) => Task.FromResult(o.WithOptions(d));
+ private readonly CompilationOptions _options;
- public ProjectCompilationOptionsAction(CompilationOptions option)
- : base(option, s_action)
+ public ProjectCompilationOptionsAction(CompilationOptions options)
{
+ _options = options;
}
- }
- internal sealed class ProjectAssemblyNameAction : SimpleCompilationTranslationAction
- {
- private static readonly Func> s_action =
- (o, d, c) => Task.FromResult(o.WithAssemblyName(d));
-
- public ProjectAssemblyNameAction(string assemblyName)
- : base(assemblyName, s_action)
+ public override Task InvokeAsync(Compilation oldCompilation, CancellationToken cancellationToken)
{
+ return Task.FromResult(oldCompilation.WithOptions(_options));
}
}
- internal class SimpleCompilationTranslationAction : CompilationTranslationAction
+ internal sealed class ProjectAssemblyNameAction : CompilationTranslationAction
{
- private readonly T _data;
- private readonly Func> _action;
+ private readonly string _assemblyName;
- public SimpleCompilationTranslationAction(T data, Func> action)
+ public ProjectAssemblyNameAction(string assemblyName)
{
- _data = data;
- _action = action;
+ _assemblyName = assemblyName;
}
public override Task InvokeAsync(Compilation oldCompilation, CancellationToken cancellationToken)
{
- return _action(oldCompilation, _data, cancellationToken);
+ return Task.FromResult(oldCompilation.WithAssemblyName(_assemblyName));
}
}
}
diff --git a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs
index d65c4707dcc0ba59a29fff4adc2e4ef4cf8a35bf..87b14dc7112e794519a24f6989a35dfbc88640d3 100644
--- a/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs
+++ b/src/Workspaces/Core/Portable/Workspace/Solution/SolutionState.cs
@@ -1217,23 +1217,18 @@ public SolutionState AddDocuments(ImmutableArray documentInfos)
foreach (var documentInfosInProject in documentInfosByProjectId)
{
CheckContainsProject(documentInfosInProject.Key);
- var oldProject = this.GetProjectState(documentInfosInProject.Key);
-
- if (oldProject == null)
- {
- throw new InvalidOperationException(string.Format(WorkspacesResources._0_is_not_part_of_the_workspace, documentInfosInProject.Key));
- }
+ var oldProjectState = this.GetProjectState(documentInfosInProject.Key)!;
var newDocumentStatesForProjectBuilder = ArrayBuilder.GetInstance();
foreach (var documentInfo in documentInfosInProject)
{
- newDocumentStatesForProjectBuilder.Add(createDocumentState(documentInfo, oldProject));
+ newDocumentStatesForProjectBuilder.Add(createDocumentState(documentInfo, oldProjectState));
}
var newDocumentStatesForProject = newDocumentStatesForProjectBuilder.ToImmutableAndFree();
- var (newProjectState, compilationTranslationAction) = addDocumentsToProjectState(oldProject, newDocumentStatesForProject);
+ var (newProjectState, compilationTranslationAction) = addDocumentsToProjectState(oldProjectState, newDocumentStatesForProject);
newSolutionState = newSolutionState.ForkProject(newProjectState,
compilationTranslationAction,
@@ -1263,51 +1258,85 @@ public SolutionState AddAnalyzerConfigDocuments(ImmutableArray doc
});
}
- public SolutionState RemoveAnalyzerConfigDocument(DocumentId documentId)
+ public SolutionState RemoveAnalyzerConfigDocuments(ImmutableArray documentIds)
{
- CheckContainsAnalyzerConfigDocument(documentId);
-
- var oldProject = this.GetProjectState(documentId.ProjectId)!;
- var newProject = oldProject.RemoveAnalyzerConfigDocument(documentId);
- var removedDocumentStates = SpecializedCollections.SingletonEnumerable(oldProject.GetAnalyzerConfigDocumentState(documentId)!);
-
- return this.ForkProject(
- newProject,
- new CompilationTranslationAction.ReplaceAllSyntaxTreesAction(newProject),
- newFilePathToDocumentIdsMap: CreateFilePathToDocumentIdsMapWithRemovedDocuments(removedDocumentStates));
+ return RemoveDocumentsFromMultipleProjects(documentIds,
+ (projectState, documentId) => { CheckContainsAnalyzerConfigDocument(documentId); return projectState.GetAnalyzerConfigDocumentState(documentId)!; },
+ (oldProject, documentIds, _) =>
+ {
+ var newProject = oldProject.RemoveAnalyzerConfigDocuments(documentIds);
+ return (newProject, new CompilationTranslationAction.ReplaceAllSyntaxTreesAction(newProject));
+ });
}
///
/// Creates a new solution instance that no longer includes the specified document.
///
- public SolutionState RemoveDocument(DocumentId documentId)
+ public SolutionState RemoveDocuments(ImmutableArray documentIds)
{
- CheckContainsDocument(documentId);
+ return RemoveDocumentsFromMultipleProjects(documentIds,
+ (projectState, documentId) => { CheckContainsDocument(documentId); return projectState.GetDocumentState(documentId)!; },
+ (projectState, documentIds, documentStates) => (projectState.RemoveDocuments(documentIds), new CompilationTranslationAction.RemoveDocumentsAction(documentStates)));
+ }
- var oldProject = this.GetProjectState(documentId.ProjectId)!;
- var oldDocument = oldProject.GetDocumentState(documentId);
- var newProject = oldProject.RemoveDocument(documentId);
- var removedDocumentStates = SpecializedCollections.SingletonEnumerable(oldProject.GetDocumentState(documentId)!);
+ private SolutionState RemoveDocumentsFromMultipleProjects(
+ ImmutableArray documentIds,
+ Func getExistingTextDocumentState,
+ Func, ImmutableArray, (ProjectState newState, CompilationTranslationAction? translationAction)> removeDocumentsFromProjectState)
+ where T : TextDocumentState
+ {
+ if (documentIds.IsDefault)
+ {
+ throw new ArgumentNullException(nameof(documentIds));
+ }
- return this.ForkProject(
- newProject,
- new CompilationTranslationAction.RemoveDocumentAction(oldDocument),
- newFilePathToDocumentIdsMap: CreateFilePathToDocumentIdsMapWithRemovedDocuments(removedDocumentStates));
+ if (documentIds.IsEmpty)
+ {
+ return this;
+ }
+
+ // The documents might be contributing to multiple different projects; split them by project and then we'll process
+ // project-at-a-time.
+ var documentIdsByProjectId = documentIds.ToLookup(id => id.ProjectId);
+
+ var newSolutionState = this;
+
+ foreach (var documentIdsInProject in documentIdsByProjectId)
+ {
+ var oldProjectState = this.GetProjectState(documentIdsInProject.Key);
+
+ if (oldProjectState == null)
+ {
+ throw new InvalidOperationException(string.Format(WorkspacesResources._0_is_not_part_of_the_workspace, documentIdsInProject.Key));
+ }
+
+ var removedDocumentStatesBuilder = ArrayBuilder.GetInstance();
+
+ foreach (var documentId in documentIdsInProject)
+ {
+ removedDocumentStatesBuilder.Add(getExistingTextDocumentState(oldProjectState, documentId));
+ }
+
+ var removedDocumentStatesForProject = removedDocumentStatesBuilder.ToImmutableAndFree();
+
+ var (newProjectState, compilationTranslationAction) = removeDocumentsFromProjectState(oldProjectState, documentIdsInProject.ToImmutableArray(), removedDocumentStatesForProject);
+
+ newSolutionState = newSolutionState.ForkProject(newProjectState,
+ compilationTranslationAction,
+ newFilePathToDocumentIdsMap: CreateFilePathToDocumentIdsMapWithRemovedDocuments(removedDocumentStatesForProject));
+ }
+
+ return newSolutionState;
}
///
- /// Creates a new solution instance that no longer includes the specified additional document.
+ /// Creates a new solution instance that no longer includes the specified additional documents.
///
- public SolutionState RemoveAdditionalDocument(DocumentId documentId)
+ public SolutionState RemoveAdditionalDocuments(ImmutableArray documentIds)
{
- CheckContainsAdditionalDocument(documentId);
-
- var oldProject = this.GetProjectState(documentId.ProjectId)!;
- var newProject = oldProject.RemoveAdditionalDocument(documentId);
- var documentStates = SpecializedCollections.SingletonEnumerable(GetAdditionalDocumentState(documentId)!);
-
- return this.ForkProject(newProject,
- newFilePathToDocumentIdsMap: CreateFilePathToDocumentIdsMapWithRemovedDocuments(documentStates));
+ return RemoveDocumentsFromMultipleProjects(documentIds,
+ (projectState, documentId) => { CheckContainsAdditionalDocument(documentId); return projectState.GetAdditionalDocumentState(documentId)!; },
+ (projectState, documentIds, documentStates) => (projectState.RemoveAdditionalDocuments(documentIds), translationAction: null));
}
///
diff --git a/src/Workspaces/Core/Portable/WorkspacesResources.resx b/src/Workspaces/Core/Portable/WorkspacesResources.resx
index ad6e613cb2f1412fc772abe663417462740e6e71..3e4e842b9567638871da0e091dfa1028fa4963f4 100644
--- a/src/Workspaces/Core/Portable/WorkspacesResources.resx
+++ b/src/Workspaces/Core/Portable/WorkspacesResources.resx
@@ -1440,4 +1440,7 @@ Zero-width positive lookbehind assertions are typically used at the beginning of
Document does not support syntax trees
+
+ {0} is in a different project.
+
\ No newline at end of file
diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.cs.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.cs.xlf
index 146360cf4ebbd6235c2c833e783439b29584d46d..a68e4436df4d5211f1a1ebcadc1d6ddc009a9ce8 100644
--- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.cs.xlf
+++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.cs.xlf
@@ -1317,6 +1317,11 @@ Pozitivní kontrolní výrazy zpětného vyhledávání s nulovou délkou se obv
Pracovní prostor není platný.
+
+
+ {0} is in a different project.
+
+ '{0} není součástí pracovního prostoru.
diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.de.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.de.xlf
index efb386dc5d18821f7c14d7c73bd5ed43c806f6e7..819124831c936255199563bc299478c8d1775533 100644
--- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.de.xlf
+++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.de.xlf
@@ -1317,6 +1317,11 @@ Positive Lookbehindassertionen mit Nullbreite werden normalerweise am Anfang reg
Arbeitsbereich ist nicht leer.
+
+
+ {0} is in a different project.
+
+ '"{0}" ist nicht Teil des Arbeitsbereichs.
diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.es.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.es.xlf
index 9466a1b6ce311e54b8f81b5466d05f732c6cd12c..ab2fcb5faaa79250a816c41a5aa16f38e4686e64 100644
--- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.es.xlf
+++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.es.xlf
@@ -1317,6 +1317,11 @@ Las aserciones de búsqueda retrasada (lookbehind) positivas de ancho cero se us
El área de trabajo no está vacía.
+
+
+ {0} is in a different project.
+
+ '{0}' no es parte del área de trabajo.
diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.fr.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.fr.xlf
index 55c52acf13516fc65c0b55b28f082d465193b039..7590c9cead4dbd38b52dc2cbd60b302c2dc7264a 100644
--- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.fr.xlf
+++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.fr.xlf
@@ -1317,6 +1317,11 @@ Les assertions arrière positives de largeur nulle sont généralement utilisée
L'espace de travail n'est pas vide.
+
+
+ {0} is in a different project.
+
+ '{0}' ne fait pas partie de l'espace de travail.
diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.it.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.it.xlf
index a9ddff31ae528ea236ff45770d5f089915337456..2b966a9cd331b0019e10d0bded995a5a82f3dae7 100644
--- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.it.xlf
+++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.it.xlf
@@ -1317,6 +1317,11 @@ Le asserzioni lookbehind positive di larghezza zero vengono usate in genere all'
L'area di lavoro non è vuota.
+
+
+ {0} is in a different project.
+
+ '{0}' non fa parte dell'area di lavoro.
diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ja.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ja.xlf
index f4cb399917fee052b4fdbe937c7ab05b52be2aed..2ff80ddea60fa257d2d2ea143eb898e31ae026b9 100644
--- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ja.xlf
+++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ja.xlf
@@ -1317,6 +1317,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of
ワークスペースが空ではありません。
+
+
+ {0} is in a different project.
+
+ '{0}' はワークスペースの一部ではありません。
diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ko.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ko.xlf
index 38d531b6a3a347c6d99b89be9a1eeb2ed51f7311..38f0b09f76ffe97cd9245fc84bf18d50f159695d 100644
--- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ko.xlf
+++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ko.xlf
@@ -1317,6 +1317,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of
작업 영역이 비어 있지 않습니다.
+
+
+ {0} is in a different project.
+
+ '{0}'은(는) 작업 영역의 일부가 아닙니다.
diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pl.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pl.xlf
index 4ecb1ab8a590150270f6d6bd579fa62cdb234960..f758fde8f43efcd2b6a31649aead1bd28b4f4ef5 100644
--- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pl.xlf
+++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pl.xlf
@@ -1317,6 +1317,11 @@ Pozytywne asercje wsteczne o zerowej szerokości są zwykle używane na początk
Obszar roboczy nie jest pusty.
+
+
+ {0} is in a different project.
+
+ 'Element „{0}” nie jest częścią obszaru roboczego.
diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pt-BR.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pt-BR.xlf
index c8bacbfd8336a63c89c4e4ed7af7fc0ab422ace6..2b3acbafc8ba44f64b1fb09b512b788f5f273097 100644
--- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pt-BR.xlf
+++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.pt-BR.xlf
@@ -1317,6 +1317,11 @@ As declarações de lookbehind positivas de largura zero normalmente são usadas
Workspace não está vazio.
+
+
+ {0} is in a different project.
+
+ "{0}" não é parte do workspace.
diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ru.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ru.xlf
index 3c4608aea3c2d161c40032edd4dbcdfec6193650..8ea82d46c7282b1ee05003b75f1fd0e1237bc31b 100644
--- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ru.xlf
+++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.ru.xlf
@@ -1317,6 +1317,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of
Рабочая область не пуста.
+
+
+ {0} is in a different project.
+
+ '"{0}" не является частью рабочей области.
diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.tr.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.tr.xlf
index 0ed61e67a1b623fd9e685a62d8a9a182019b4dff..c48cba68bc3ec270f46418d182ae74fbb118b792 100644
--- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.tr.xlf
+++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.tr.xlf
@@ -1317,6 +1317,11 @@ Sıfır genişlikli pozitif geri yönlü onaylamalar genellikle normal ifadeleri
Çalışma alanı boş değil.
+
+
+ {0} is in a different project.
+
+ '{0}' çalışma alanının parçası değildir.
diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hans.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hans.xlf
index 35870bf0a8fc4a03cfce57a6bd4655400d8a0619..9ccd320134ae628ddcc01b2a2c7a2e87d4aa6b61 100644
--- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hans.xlf
+++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hans.xlf
@@ -1317,6 +1317,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of
工作区不为空。
+
+
+ {0} is in a different project.
+
+ '“{0}”不是工作区的一部分。
diff --git a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hant.xlf b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hant.xlf
index 8fdaeb1929e2b92a6564d154068492319196124f..901ebc095268f5d0b6f6aa2bacb660aec7956a0e 100644
--- a/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hant.xlf
+++ b/src/Workspaces/Core/Portable/xlf/WorkspacesResources.zh-Hant.xlf
@@ -1317,6 +1317,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of
工作區不是空的。
+
+
+ {0} is in a different project.
+
+ '{0}' 不是工作區的一部分。
diff --git a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs
index f1aeb7f3ee5389a1519667a4e8c45033c17115ef..7339e2cbac926d78fa7b865c402fb209e8bab15b 100644
--- a/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs
+++ b/src/Workspaces/CoreTest/SolutionTests/SolutionTests.cs
@@ -193,6 +193,72 @@ public void AddTwoDocumentsWithMissingProject()
Assert.ThrowsAny(() => solution.AddDocuments(ImmutableArray.Create(documentInfo1, documentInfo2)));
}
+ [Fact, Trait(Traits.Feature, Traits.Features.Workspace)]
+ public void RemoveZeroDocuments()
+ {
+ var solution = CreateSolution();
+
+ Assert.Same(solution, solution.RemoveDocuments(ImmutableArray.Empty));
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.Workspace)]
+ public async Task RemoveTwoDocuments()
+ {
+ var projectId = ProjectId.CreateNewId();
+
+ var documentInfo1 = DocumentInfo.Create(DocumentId.CreateNewId(projectId), "file1.cs");
+ var documentInfo2 = DocumentInfo.Create(DocumentId.CreateNewId(projectId), "file2.cs");
+
+ var solution = CreateSolution()
+ .AddProject(projectId, "project1", "project1.dll", LanguageNames.CSharp)
+ .AddDocuments(ImmutableArray.Create(documentInfo1, documentInfo2));
+
+ solution = solution.RemoveDocuments(ImmutableArray.Create(documentInfo1.Id, documentInfo2.Id));
+
+ var finalProject = solution.Projects.Single();
+ Assert.Empty(finalProject.Documents);
+ Assert.Empty((await finalProject.GetCompilationAsync()).SyntaxTrees);
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.Workspace)]
+ public void RemoveTwoDocumentsFromDifferentProjects()
+ {
+ var projectId1 = ProjectId.CreateNewId();
+ var projectId2 = ProjectId.CreateNewId();
+
+ var documentInfo1 = DocumentInfo.Create(DocumentId.CreateNewId(projectId1), "file1.cs");
+ var documentInfo2 = DocumentInfo.Create(DocumentId.CreateNewId(projectId2), "file2.cs");
+
+ var solution = CreateSolution()
+ .AddProject(projectId1, "project1", "project1.dll", LanguageNames.CSharp)
+ .AddProject(projectId2, "project2", "project2.dll", LanguageNames.CSharp)
+ .AddDocuments(ImmutableArray.Create(documentInfo1, documentInfo2));
+
+ Assert.All(solution.Projects, p => Assert.Single(p.Documents));
+
+ solution = solution.RemoveDocuments(ImmutableArray.Create(documentInfo1.Id, documentInfo2.Id));
+
+ Assert.All(solution.Projects, p => Assert.Empty(p.Documents));
+ }
+
+ [Fact, Trait(Traits.Feature, Traits.Features.Workspace)]
+ public void RemoveDocumentFromUnrelatedProject()
+ {
+ var projectId1 = ProjectId.CreateNewId();
+ var projectId2 = ProjectId.CreateNewId();
+
+ var documentInfo1 = DocumentInfo.Create(DocumentId.CreateNewId(projectId1), "file1.cs");
+
+ var solution = CreateSolution()
+ .AddProject(projectId1, "project1", "project1.dll", LanguageNames.CSharp)
+ .AddProject(projectId2, "project2", "project2.dll", LanguageNames.CSharp)
+ .AddDocument(documentInfo1);
+
+ // This should throw if we're removing one document from the wrong project. Right now we don't test the RemoveDocument
+ // API due to https://github.com/dotnet/roslyn/issues/41211.
+ Assert.Throws(() => solution.GetProject(projectId2).RemoveDocuments(ImmutableArray.Create(documentInfo1.Id)));
+ }
+
[Fact, Trait(Traits.Feature, Traits.Features.Workspace)]
public async Task TestOneCSharpProjectAsync()
{