未验证 提交 7daa213b 编写于 作者: J Jason Malinowski 提交者: GitHub

Merge pull request #41217 from jasonmalinowski/allow-batched-document-removals

Allow batched document removals and update some consuming code accordingly
......@@ -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
/// <see cref="WorkspaceChangeKind.SolutionChanged"/> if we can't give a more precise type.
/// </summary>
private WorkspaceChangeKind? _workspaceChangeKind;
private readonly List<DocumentId> _documentIdsRemoved = new List<DocumentId>();
public SolutionChangeAccumulator(Solution startingSolution)
{
......@@ -26,12 +30,13 @@ public SolutionChangeAccumulator(Solution startingSolution)
}
public Solution Solution { get; private set; }
public IEnumerable<DocumentId> 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<DocumentId> documentIds)
{
......@@ -75,6 +80,17 @@ public void UpdateSolutionForDocumentAction(Solution newSolution, WorkspaceChang
}
}
/// <summary>
/// The same as <see cref="UpdateSolutionForDocumentAction(Solution, WorkspaceChangeKind, IEnumerable{DocumentId})" /> but also records
/// the removed documents into <see cref="DocumentIdsRemoved"/>.
/// </summary>
public void UpdateSolutionForRemovedDocumentAction(Solution solution, WorkspaceChangeKind removeDocumentChangeKind, IEnumerable<DocumentId> documentIdsRemoved)
{
UpdateSolutionForDocumentAction(solution, removeDocumentChangeKind, documentIdsRemoved);
_documentIdsRemoved.AddRange(documentIdsRemoved);
}
/// <summary>
/// Should be called to update the solution if there isn't a specific document change kind that should be
/// given to <see cref="UpdateSolutionForDocumentAction"/>
......
......@@ -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<string> filePaths)
List<(DocumentId documentId, SourceTextContainer textContainer)> documentsToOpen,
Func<Solution, ImmutableArray<DocumentInfo>, Solution> addDocuments,
WorkspaceChangeKind addDocumentChangeKind,
Func<Solution, DocumentId, Solution> removeDocument,
Func<Solution, ImmutableArray<DocumentId>, Solution> removeDocuments,
WorkspaceChangeKind removeDocumentChangeKind)
{
// Document adding...
......@@ -1592,12 +1574,9 @@ public void ReorderFiles(ImmutableArray<string> 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);
......
......@@ -1468,6 +1468,8 @@ internal override bool CanAddProjectReference(ProjectId referencingProject, Proj
return result != (uint)__VSREFERENCEQUERYRESULT.REFERENCE_DENY;
}
#nullable enable
/// <summary>
/// Applies a single operation to the workspace. <paramref name="action"/> should be a call to one of the protected Workspace.On* methods.
/// </summary>
......@@ -1496,6 +1498,11 @@ public void ApplyBatchChangeToWorkspace(Func<CodeAnalysis.Solution, SolutionChan
return;
}
foreach (var documentId in solutionChangeAccumulator.DocumentIdsRemoved)
{
this.ClearDocumentData(documentId);
}
SetCurrentSolution(solutionChangeAccumulator.Solution);
RaiseWorkspaceChangedEventAsync(
solutionChangeAccumulator.WorkspaceChangeKind,
......@@ -1506,6 +1513,8 @@ public void ApplyBatchChangeToWorkspace(Func<CodeAnalysis.Solution, SolutionChan
}
}
#nullable restore
private readonly Dictionary<ProjectId, ProjectReferenceInformation> _projectReferenceInfoMap = new Dictionary<ProjectId, ProjectReferenceInformation>();
private ProjectReferenceInformation GetReferenceInfo_NoLock(ProjectId projectId)
......
*REMOVED*Microsoft.CodeAnalysis.TextDocument.Project.set -> void
*REMOVED*Microsoft.CodeAnalysis.TextDocument.TextDocument() -> void
Microsoft.CodeAnalysis.Options.DocumentOptionSet.WithChangedOption<T>(Microsoft.CodeAnalysis.Options.PerLanguageOption<T> option, T value) -> Microsoft.CodeAnalysis.Options.DocumentOptionSet
Microsoft.CodeAnalysis.Project.RemoveDocuments(System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.DocumentId> documentIds) -> Microsoft.CodeAnalysis.Project
Microsoft.CodeAnalysis.Solution.RemoveAdditionalDocuments(System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.DocumentId> documentIds) -> Microsoft.CodeAnalysis.Solution
Microsoft.CodeAnalysis.Solution.RemoveAnalyzerConfigDocuments(System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.DocumentId> documentIds) -> Microsoft.CodeAnalysis.Solution
Microsoft.CodeAnalysis.Solution.RemoveDocuments(System.Collections.Immutable.ImmutableArray<Microsoft.CodeAnalysis.DocumentId> 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<Microsoft.CodeAnalysis.Document>
static Microsoft.CodeAnalysis.Simplification.Simplifier.AddImportsAnnotation.get -> Microsoft.CodeAnalysis.SyntaxAnnotation
......@@ -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
/// </summary>
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)!;
}
/// <summary>
/// Creates a new instance of this project updated to no longer include the specified documents.
/// </summary>
public Project RemoveDocuments(ImmutableArray<DocumentId> 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);
}
/// <summary>
/// Creates a new instance of this project updated to no longer include the specified additional document.
/// </summary>
......
......@@ -784,31 +784,25 @@ private ProjectState CreateNewStateForChangedAnalyzerConfigDocuments(ImmutableSo
analyzerConfigSet: newAnalyzerConfigSet);
}
public ProjectState RemoveDocument(DocumentId documentId)
public ProjectState RemoveDocuments(ImmutableArray<DocumentId> 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<DocumentId> 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<DocumentId> documentIds)
{
Debug.Assert(_analyzerConfigDocumentStates.ContainsKey(documentId));
var newAnalyzerConfigDocumentStates = _analyzerConfigDocumentStates.Remove(documentId);
var newAnalyzerConfigDocumentStates = _analyzerConfigDocumentStates.RemoveRange(documentIds);
return CreateNewStateForChangedAnalyzerConfigDocuments(newAnalyzerConfigDocumentStates);
}
......
......@@ -891,7 +891,20 @@ public Solution AddAnalyzerConfigDocuments(ImmutableArray<DocumentInfo> document
/// </summary>
public Solution RemoveDocument(DocumentId documentId)
{
var newState = _state.RemoveDocument(documentId);
if (documentId == null)
{
throw new ArgumentNullException(nameof(documentId));
}
return RemoveDocuments(ImmutableArray.Create(documentId));
}
/// <summary>
/// Creates a new solution instance that no longer includes the specified documents.
/// </summary>
public Solution RemoveDocuments(ImmutableArray<DocumentId> documentIds)
{
var newState = _state.RemoveDocuments(documentIds);
if (newState == _state)
{
return this;
......@@ -905,7 +918,20 @@ public Solution RemoveDocument(DocumentId documentId)
/// </summary>
public Solution RemoveAdditionalDocument(DocumentId documentId)
{
var newState = _state.RemoveAdditionalDocument(documentId);
if (documentId == null)
{
throw new ArgumentNullException(nameof(documentId));
}
return RemoveAdditionalDocuments(ImmutableArray.Create(documentId));
}
/// <summary>
/// Creates a new solution instance that no longer includes the specified additional documents.
/// </summary>
public Solution RemoveAdditionalDocuments(ImmutableArray<DocumentId> documentIds)
{
var newState = _state.RemoveAdditionalDocuments(documentIds);
if (newState == _state)
{
return this;
......@@ -914,9 +940,25 @@ public Solution RemoveAdditionalDocument(DocumentId documentId)
return new Solution(newState);
}
/// <summary>
/// Creates a new solution instance that no longer includes the specified <see cref="AnalyzerConfigDocument"/>.
/// </summary>
public Solution RemoveAnalyzerConfigDocument(DocumentId documentId)
{
var newState = _state.RemoveAnalyzerConfigDocument(documentId);
if (documentId == null)
{
throw new ArgumentNullException(nameof(documentId));
}
return RemoveAnalyzerConfigDocuments(ImmutableArray.Create(documentId));
}
/// <summary>
/// Creates a new solution instance that no longer includes the specified <see cref="AnalyzerConfigDocument"/>s.
/// </summary>
public Solution RemoveAnalyzerConfigDocuments(ImmutableArray<DocumentId> documentIds)
{
var newState = _state.RemoveAnalyzerConfigDocuments(documentIds);
if (newState == _state)
{
return this;
......
......@@ -33,14 +33,25 @@ public override Task<Compilation> InvokeAsync(Compilation oldCompilation, Cancel
public DocumentId DocumentId => _newState.Attributes.Id;
}
internal sealed class RemoveDocumentAction : SimpleCompilationTranslationAction<DocumentState>
internal sealed class RemoveDocumentsAction : CompilationTranslationAction
{
private static readonly Func<Compilation, DocumentState, CancellationToken, Task<Compilation>> s_action =
async (o, d, c) => o.RemoveSyntaxTrees(await d.GetSyntaxTreeAsync(c).ConfigureAwait(false));
private readonly ImmutableArray<DocumentState> _documents;
public RemoveDocumentAction(DocumentState document)
: base(document, s_action)
public RemoveDocumentsAction(ImmutableArray<DocumentState> documents)
{
_documents = documents;
}
public override async Task<Compilation> InvokeAsync(Compilation oldCompilation, CancellationToken cancellationToken)
{
var syntaxTrees = new List<SyntaxTree>(_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<DocumentState> documents)
public override async Task<Compilation> InvokeAsync(Compilation oldCompilation, CancellationToken cancellationToken)
{
var syntaxTrees = new List<SyntaxTree>();
var syntaxTrees = new List<SyntaxTree>(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<Compilation> InvokeAsync(Compilation oldCompilation,
}
}
internal sealed class ProjectCompilationOptionsAction : SimpleCompilationTranslationAction<CompilationOptions>
internal sealed class ProjectCompilationOptionsAction : CompilationTranslationAction
{
private static readonly Func<Compilation, CompilationOptions, CancellationToken, Task<Compilation>> 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<string>
{
private static readonly Func<Compilation, string, CancellationToken, Task<Compilation>> s_action =
(o, d, c) => Task.FromResult(o.WithAssemblyName(d));
public ProjectAssemblyNameAction(string assemblyName)
: base(assemblyName, s_action)
public override Task<Compilation> InvokeAsync(Compilation oldCompilation, CancellationToken cancellationToken)
{
return Task.FromResult(oldCompilation.WithOptions(_options));
}
}
internal class SimpleCompilationTranslationAction<T> : CompilationTranslationAction
internal sealed class ProjectAssemblyNameAction : CompilationTranslationAction
{
private readonly T _data;
private readonly Func<Compilation, T, CancellationToken, Task<Compilation>> _action;
private readonly string _assemblyName;
public SimpleCompilationTranslationAction(T data, Func<Compilation, T, CancellationToken, Task<Compilation>> action)
public ProjectAssemblyNameAction(string assemblyName)
{
_data = data;
_action = action;
_assemblyName = assemblyName;
}
public override Task<Compilation> InvokeAsync(Compilation oldCompilation, CancellationToken cancellationToken)
{
return _action(oldCompilation, _data, cancellationToken);
return Task.FromResult(oldCompilation.WithAssemblyName(_assemblyName));
}
}
}
......
......@@ -1217,23 +1217,18 @@ public SolutionState AddDocuments(ImmutableArray<DocumentInfo> 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<T>.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<DocumentInfo> doc
});
}
public SolutionState RemoveAnalyzerConfigDocument(DocumentId documentId)
public SolutionState RemoveAnalyzerConfigDocuments(ImmutableArray<DocumentId> 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));
});
}
/// <summary>
/// Creates a new solution instance that no longer includes the specified document.
/// </summary>
public SolutionState RemoveDocument(DocumentId documentId)
public SolutionState RemoveDocuments(ImmutableArray<DocumentId> 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<T>(
ImmutableArray<DocumentId> documentIds,
Func<ProjectState, DocumentId, T> getExistingTextDocumentState,
Func<ProjectState, ImmutableArray<DocumentId>, ImmutableArray<T>, (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<T>.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;
}
/// <summary>
/// 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.
/// </summary>
public SolutionState RemoveAdditionalDocument(DocumentId documentId)
public SolutionState RemoveAdditionalDocuments(ImmutableArray<DocumentId> 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));
}
/// <summary>
......
......@@ -1440,4 +1440,7 @@ Zero-width positive lookbehind assertions are typically used at the beginning of
<data name="Document_does_not_support_syntax_trees" xml:space="preserve">
<value>Document does not support syntax trees</value>
</data>
<data name="_0_is_in_a_different_project" xml:space="preserve">
<value>{0} is in a different project.</value>
</data>
</root>
\ No newline at end of file
......@@ -1317,6 +1317,11 @@ Pozitivní kontrolní výrazy zpětného vyhledávání s nulovou délkou se obv
<target state="translated">Pracovní prostor není platný.</target>
<note />
</trans-unit>
<trans-unit id="_0_is_in_a_different_project">
<source>{0} is in a different project.</source>
<target state="new">{0} is in a different project.</target>
<note />
</trans-unit>
<trans-unit id="_0_is_not_part_of_the_workspace">
<source>'{0}' is not part of the workspace.</source>
<target state="translated">'{0} není součástí pracovního prostoru.</target>
......
......@@ -1317,6 +1317,11 @@ Positive Lookbehindassertionen mit Nullbreite werden normalerweise am Anfang reg
<target state="translated">Arbeitsbereich ist nicht leer.</target>
<note />
</trans-unit>
<trans-unit id="_0_is_in_a_different_project">
<source>{0} is in a different project.</source>
<target state="new">{0} is in a different project.</target>
<note />
</trans-unit>
<trans-unit id="_0_is_not_part_of_the_workspace">
<source>'{0}' is not part of the workspace.</source>
<target state="translated">'"{0}" ist nicht Teil des Arbeitsbereichs.</target>
......
......@@ -1317,6 +1317,11 @@ Las aserciones de búsqueda retrasada (lookbehind) positivas de ancho cero se us
<target state="translated">El área de trabajo no está vacía.</target>
<note />
</trans-unit>
<trans-unit id="_0_is_in_a_different_project">
<source>{0} is in a different project.</source>
<target state="new">{0} is in a different project.</target>
<note />
</trans-unit>
<trans-unit id="_0_is_not_part_of_the_workspace">
<source>'{0}' is not part of the workspace.</source>
<target state="translated">'{0}' no es parte del área de trabajo.</target>
......
......@@ -1317,6 +1317,11 @@ Les assertions arrière positives de largeur nulle sont généralement utilisée
<target state="translated">L'espace de travail n'est pas vide.</target>
<note />
</trans-unit>
<trans-unit id="_0_is_in_a_different_project">
<source>{0} is in a different project.</source>
<target state="new">{0} is in a different project.</target>
<note />
</trans-unit>
<trans-unit id="_0_is_not_part_of_the_workspace">
<source>'{0}' is not part of the workspace.</source>
<target state="translated">'{0}' ne fait pas partie de l'espace de travail.</target>
......
......@@ -1317,6 +1317,11 @@ Le asserzioni lookbehind positive di larghezza zero vengono usate in genere all'
<target state="translated">L'area di lavoro non è vuota.</target>
<note />
</trans-unit>
<trans-unit id="_0_is_in_a_different_project">
<source>{0} is in a different project.</source>
<target state="new">{0} is in a different project.</target>
<note />
</trans-unit>
<trans-unit id="_0_is_not_part_of_the_workspace">
<source>'{0}' is not part of the workspace.</source>
<target state="translated">'{0}' non fa parte dell'area di lavoro.</target>
......
......@@ -1317,6 +1317,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of
<target state="translated">ワークスペースが空ではありません。</target>
<note />
</trans-unit>
<trans-unit id="_0_is_in_a_different_project">
<source>{0} is in a different project.</source>
<target state="new">{0} is in a different project.</target>
<note />
</trans-unit>
<trans-unit id="_0_is_not_part_of_the_workspace">
<source>'{0}' is not part of the workspace.</source>
<target state="translated">'{0}' はワークスペースの一部ではありません。</target>
......
......@@ -1317,6 +1317,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of
<target state="translated">작업 영역이 비어 있지 않습니다.</target>
<note />
</trans-unit>
<trans-unit id="_0_is_in_a_different_project">
<source>{0} is in a different project.</source>
<target state="new">{0} is in a different project.</target>
<note />
</trans-unit>
<trans-unit id="_0_is_not_part_of_the_workspace">
<source>'{0}' is not part of the workspace.</source>
<target state="translated">'{0}'은(는) 작업 영역의 일부가 아닙니다.</target>
......
......@@ -1317,6 +1317,11 @@ Pozytywne asercje wsteczne o zerowej szerokości są zwykle używane na początk
<target state="translated">Obszar roboczy nie jest pusty.</target>
<note />
</trans-unit>
<trans-unit id="_0_is_in_a_different_project">
<source>{0} is in a different project.</source>
<target state="new">{0} is in a different project.</target>
<note />
</trans-unit>
<trans-unit id="_0_is_not_part_of_the_workspace">
<source>'{0}' is not part of the workspace.</source>
<target state="translated">'Element „{0}” nie jest częścią obszaru roboczego.</target>
......
......@@ -1317,6 +1317,11 @@ As declarações de lookbehind positivas de largura zero normalmente são usadas
<target state="translated">Workspace não está vazio.</target>
<note />
</trans-unit>
<trans-unit id="_0_is_in_a_different_project">
<source>{0} is in a different project.</source>
<target state="new">{0} is in a different project.</target>
<note />
</trans-unit>
<trans-unit id="_0_is_not_part_of_the_workspace">
<source>'{0}' is not part of the workspace.</source>
<target state="translated">"{0}" não é parte do workspace.</target>
......
......@@ -1317,6 +1317,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of
<target state="translated">Рабочая область не пуста.</target>
<note />
</trans-unit>
<trans-unit id="_0_is_in_a_different_project">
<source>{0} is in a different project.</source>
<target state="new">{0} is in a different project.</target>
<note />
</trans-unit>
<trans-unit id="_0_is_not_part_of_the_workspace">
<source>'{0}' is not part of the workspace.</source>
<target state="translated">'"{0}" не является частью рабочей области.</target>
......
......@@ -1317,6 +1317,11 @@ Sıfır genişlikli pozitif geri yönlü onaylamalar genellikle normal ifadeleri
<target state="translated">Çalışma alanı boş değil.</target>
<note />
</trans-unit>
<trans-unit id="_0_is_in_a_different_project">
<source>{0} is in a different project.</source>
<target state="new">{0} is in a different project.</target>
<note />
</trans-unit>
<trans-unit id="_0_is_not_part_of_the_workspace">
<source>'{0}' is not part of the workspace.</source>
<target state="translated">'{0}' çalışma alanının parçası değildir.</target>
......
......@@ -1317,6 +1317,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of
<target state="translated">工作区不为空。</target>
<note />
</trans-unit>
<trans-unit id="_0_is_in_a_different_project">
<source>{0} is in a different project.</source>
<target state="new">{0} is in a different project.</target>
<note />
</trans-unit>
<trans-unit id="_0_is_not_part_of_the_workspace">
<source>'{0}' is not part of the workspace.</source>
<target state="translated">'“{0}”不是工作区的一部分。</target>
......
......@@ -1317,6 +1317,11 @@ Zero-width positive lookbehind assertions are typically used at the beginning of
<target state="translated">工作區不是空的。</target>
<note />
</trans-unit>
<trans-unit id="_0_is_in_a_different_project">
<source>{0} is in a different project.</source>
<target state="new">{0} is in a different project.</target>
<note />
</trans-unit>
<trans-unit id="_0_is_not_part_of_the_workspace">
<source>'{0}' is not part of the workspace.</source>
<target state="translated">'{0}' 不是工作區的一部分。</target>
......
......@@ -193,6 +193,72 @@ public void AddTwoDocumentsWithMissingProject()
Assert.ThrowsAny<InvalidOperationException>(() => 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<DocumentId>.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<ArgumentException>(() => solution.GetProject(projectId2).RemoveDocuments(ImmutableArray.Create(documentInfo1.Id)));
}
[Fact, Trait(Traits.Feature, Traits.Features.Workspace)]
public async Task TestOneCSharpProjectAsync()
{
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册