diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs
index 411e8ed26fd8f..54171becad401 100644
--- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs
+++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProject.cs
@@ -38,9 +38,10 @@ internal sealed partial class ProjectSystemProject
///
/// A semaphore taken for all mutation of any mutable field in this type.
///
- /// This is, for now, intentionally pessimistic. There are no doubt ways that we could allow more to run in parallel,
- /// but the current tradeoff is for simplicity of code and "obvious correctness" than something that is subtle, fast, and wrong.
- private readonly SemaphoreSlim _gate = new SemaphoreSlim(initialCount: 1);
+ /// This is, for now, intentionally pessimistic. There are no doubt ways that we could allow more to run in
+ /// parallel, but the current tradeoff is for simplicity of code and "obvious correctness" than something that is
+ /// subtle, fast, and wrong.
+ private readonly SemaphoreSlim _gate = new(initialCount: 1);
///
/// The number of active batch scopes. If this is zero, we are not batching, non-zero means we are batching.
@@ -52,13 +53,13 @@ internal sealed partial class ProjectSystemProject
private readonly List _projectReferencesAddedInBatch = [];
private readonly List _projectReferencesRemovedInBatch = [];
- private readonly Dictionary _analyzerPathsToAnalyzers = [];
- private readonly List _analyzersAddedInBatch = [];
+ private readonly Dictionary _analyzerPathsToAnalyzers = [];
+ private readonly List _analyzersAddedInBatch = [];
///
/// The list of s that will be removed in this batch.
///
- private readonly List _analyzersRemovedInBatch = [];
+ private readonly List _analyzersRemovedInBatch = [];
private readonly List> _projectPropertyModificationsInBatch = [];
@@ -653,12 +654,23 @@ await _projectSystemProjectFactory.ApplyBatchChangeToWorkspaceMaybeAsync(useAsyn
newSolution: solutionChanges.Solution.RemoveProjectReference(Id, projectReference));
}
+ // Analyzer reference removing...
+ if (_analyzersRemovedInBatch.Count > 0)
+ {
+ projectUpdateState = projectUpdateState.WithIncrementalAnalyzerReferencesRemoved(_analyzersRemovedInBatch);
+
+ foreach (var analyzerReference in _analyzersRemovedInBatch)
+ solutionChanges.UpdateSolutionForProjectAction(Id, solutionChanges.Solution.RemoveAnalyzerReference(Id, analyzerReference));
+ }
+
// Analyzer reference adding...
- solutionChanges.UpdateSolutionForProjectAction(Id, solutionChanges.Solution.AddAnalyzerReferences(Id, _analyzersAddedInBatch));
+ if (_analyzersAddedInBatch.Count > 0)
+ {
+ projectUpdateState = projectUpdateState.WithIncrementalAnalyzerReferencesAdded(_analyzersAddedInBatch);
- // Analyzer reference removing...
- foreach (var analyzerReference in _analyzersRemovedInBatch)
- solutionChanges.UpdateSolutionForProjectAction(Id, solutionChanges.Solution.RemoveAnalyzerReference(Id, analyzerReference));
+ solutionChanges.UpdateSolutionForProjectAction(
+ Id, solutionChanges.Solution.AddAnalyzerReferences(Id, _analyzersAddedInBatch));
+ }
// Other property modifications...
foreach (var propertyModification in _projectPropertyModificationsInBatch)
@@ -918,47 +930,54 @@ public void AddAnalyzerReference(string fullPath)
foreach (var mappedFullPath in mappedPaths)
{
if (_analyzerPathsToAnalyzers.ContainsKey(mappedFullPath))
- {
throw new ArgumentException($"'{fullPath}' has already been added to this project.", nameof(fullPath));
- }
}
- foreach (var mappedFullPath in mappedPaths)
+ if (_activeBatchScopes > 0)
{
- // Are we adding one we just recently removed? If so, we can just keep using that one, and avoid removing
- // it once we apply the batch
- var analyzerPendingRemoval = _analyzersRemovedInBatch.FirstOrDefault(a => a.FullPath == mappedFullPath);
- if (analyzerPendingRemoval != null)
+ foreach (var mappedFullPath in mappedPaths)
{
- _analyzersRemovedInBatch.Remove(analyzerPendingRemoval);
- _analyzerPathsToAnalyzers.Add(mappedFullPath, analyzerPendingRemoval);
- }
- else
- {
- // Nope, we actually need to make a new one.
- var analyzerReference = new AnalyzerFileReference(mappedFullPath, _analyzerAssemblyLoader);
-
- _analyzerPathsToAnalyzers.Add(mappedFullPath, analyzerReference);
-
- if (_activeBatchScopes > 0)
+ // Are we adding one we just recently removed? If so, we can just keep using that one, and avoid removing
+ // it once we apply the batch
+ var analyzerPendingRemoval = _analyzersRemovedInBatch.FirstOrDefault(a => a.FullPath == mappedFullPath);
+ if (analyzerPendingRemoval != null)
{
- _analyzersAddedInBatch.Add(analyzerReference);
+ _analyzersRemovedInBatch.Remove(analyzerPendingRemoval);
+ _analyzerPathsToAnalyzers.Add(mappedFullPath, analyzerPendingRemoval);
}
else
{
- _projectSystemProjectFactory.ApplyChangeToWorkspace(w => w.OnAnalyzerReferenceAdded(Id, analyzerReference));
+ // Nope, we actually need to make a new one.
+ var analyzerReference = new AnalyzerFileReference(mappedFullPath, _analyzerAssemblyLoader);
+
+ _analyzersAddedInBatch.Add(analyzerReference);
+ _analyzerPathsToAnalyzers.Add(mappedFullPath, analyzerReference);
}
}
}
+ else
+ {
+ _projectSystemProjectFactory.ApplyChangeToWorkspaceWithProjectUpdateState((w, projectUpdateState) =>
+ {
+ foreach (var mappedFullPath in mappedPaths)
+ {
+ var analyzerReference = new AnalyzerFileReference(mappedFullPath, _analyzerAssemblyLoader);
+ _analyzerPathsToAnalyzers.Add(mappedFullPath, analyzerReference);
+ w.OnAnalyzerReferenceAdded(Id, analyzerReference);
+
+ projectUpdateState = projectUpdateState.WithIncrementalAnalyzerReferenceAdded(analyzerReference);
+ }
+
+ return projectUpdateState;
+ });
+ }
}
}
public void RemoveAnalyzerReference(string fullPath)
{
if (string.IsNullOrEmpty(fullPath))
- {
throw new ArgumentException("message", nameof(fullPath));
- }
var mappedPaths = GetMappedAnalyzerPaths(fullPath);
@@ -968,28 +987,38 @@ public void RemoveAnalyzerReference(string fullPath)
foreach (var mappedFullPath in mappedPaths)
{
if (!_analyzerPathsToAnalyzers.ContainsKey(mappedFullPath))
- {
throw new ArgumentException($"'{fullPath}' is not an analyzer of this project.", nameof(fullPath));
- }
}
- foreach (var mappedFullPath in mappedPaths)
+ if (_activeBatchScopes > 0)
{
- var analyzerReference = _analyzerPathsToAnalyzers[mappedFullPath];
+ foreach (var mappedFullPath in mappedPaths)
+ {
+ var analyzerReference = _analyzerPathsToAnalyzers[mappedFullPath];
- _analyzerPathsToAnalyzers.Remove(mappedFullPath);
+ _analyzerPathsToAnalyzers.Remove(mappedFullPath);
- if (_activeBatchScopes > 0)
- {
// This analyzer may be one we've just added in the same batch; in that case, just don't add it in
// the first place.
if (!_analyzersAddedInBatch.Remove(analyzerReference))
_analyzersRemovedInBatch.Add(analyzerReference);
}
- else
+ }
+ else
+ {
+ _projectSystemProjectFactory.ApplyChangeToWorkspaceWithProjectUpdateState((w, projectUpdateState) =>
{
- _projectSystemProjectFactory.ApplyChangeToWorkspace(w => w.OnAnalyzerReferenceRemoved(Id, analyzerReference));
- }
+ foreach (var mappedFullPath in mappedPaths)
+ {
+ var analyzerReference = _analyzerPathsToAnalyzers[mappedFullPath];
+ _analyzerPathsToAnalyzers.Remove(mappedFullPath);
+
+ w.OnAnalyzerReferenceRemoved(Id, analyzerReference);
+ projectUpdateState = projectUpdateState.WithIncrementalAnalyzerReferenceRemoved(analyzerReference);
+ }
+
+ return projectUpdateState;
+ });
}
}
}
diff --git a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectFactory.ProjectUpdateState.cs b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectFactory.ProjectUpdateState.cs
index f5f72dad55794..f39c09b89c689 100644
--- a/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectFactory.ProjectUpdateState.cs
+++ b/src/Workspaces/Core/Portable/Workspace/ProjectSystem/ProjectSystemProjectFactory.ProjectUpdateState.cs
@@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.
using System;
+using System.Collections.Generic;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.Diagnostics;
@@ -123,9 +124,15 @@ public ProjectUpdateState WithIncrementalMetadataReferenceAdded(PortableExecutab
public ProjectUpdateState WithIncrementalAnalyzerReferenceRemoved(AnalyzerFileReference reference)
=> this with { RemovedAnalyzerReferences = RemovedAnalyzerReferences.Add(reference) };
+ public ProjectUpdateState WithIncrementalAnalyzerReferencesRemoved(List references)
+ => this with { RemovedAnalyzerReferences = RemovedAnalyzerReferences.AddRange(references) };
+
public ProjectUpdateState WithIncrementalAnalyzerReferenceAdded(AnalyzerFileReference reference)
=> this with { AddedAnalyzerReferences = AddedAnalyzerReferences.Add(reference) };
+ public ProjectUpdateState WithIncrementalAnalyzerReferencesAdded(List references)
+ => this with { AddedAnalyzerReferences = AddedAnalyzerReferences.AddRange(references) };
+
///
/// Returns a new instance with any incremental state that should not be saved between updates cleared.
///