Skip to content

Commit 1c701f5

Browse files
committed
Merge pull request #1983 from sharwell/fixall-performance
Improve DocumentBasedFixAllProvider performance for multiple documents
2 parents b2686ff + 2fbc3a2 commit 1c701f5

43 files changed

Lines changed: 638 additions & 307 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/DocumentationRules/SA1626CodeFixProvider.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,8 @@ private class FixAll : DocumentBasedFixAllProvider
6868
protected override string CodeActionTitle =>
6969
DocumentationResources.SA1626CodeFix;
7070

71-
protected override async Task<SyntaxNode> FixAllInDocumentAsync(FixAllContext fixAllContext, Document document)
71+
protected override async Task<SyntaxNode> FixAllInDocumentAsync(FixAllContext fixAllContext, Document document, ImmutableArray<Diagnostic> diagnostics)
7272
{
73-
var diagnostics = await fixAllContext.GetDocumentDiagnosticsAsync(document).ConfigureAwait(false);
7473
if (diagnostics.IsEmpty)
7574
{
7675
return null;

StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/Helpers/CustomBatchFixAllProvider.cs

Lines changed: 4 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -210,93 +210,14 @@ public virtual string GetFixAllTitle(FixAllContext fixAllContext)
210210
}
211211
}
212212

213-
public virtual async Task<ImmutableDictionary<Document, ImmutableArray<Diagnostic>>> GetDocumentDiagnosticsToFixAsync(FixAllContext fixAllContext)
213+
public virtual Task<ImmutableDictionary<Document, ImmutableArray<Diagnostic>>> GetDocumentDiagnosticsToFixAsync(FixAllContext fixAllContext)
214214
{
215-
var allDiagnostics = ImmutableArray<Diagnostic>.Empty;
216-
var projectsToFix = ImmutableArray<Project>.Empty;
217-
218-
var document = fixAllContext.Document;
219-
var project = fixAllContext.Project;
220-
221-
switch (fixAllContext.Scope)
222-
{
223-
case FixAllScope.Document:
224-
if (document != null)
225-
{
226-
var documentDiagnostics = await fixAllContext.GetDocumentDiagnosticsAsync(document).ConfigureAwait(false);
227-
return ImmutableDictionary<Document, ImmutableArray<Diagnostic>>.Empty.SetItem(document, documentDiagnostics);
228-
}
229-
230-
break;
231-
232-
case FixAllScope.Project:
233-
projectsToFix = ImmutableArray.Create(project);
234-
allDiagnostics = await fixAllContext.GetAllDiagnosticsAsync(project).ConfigureAwait(false);
235-
break;
236-
237-
case FixAllScope.Solution:
238-
projectsToFix = project.Solution.Projects
239-
.Where(p => p.Language == project.Language)
240-
.ToImmutableArray();
241-
242-
var diagnostics = new ConcurrentDictionary<ProjectId, ImmutableArray<Diagnostic>>();
243-
var tasks = new Task[projectsToFix.Length];
244-
for (int i = 0; i < projectsToFix.Length; i++)
245-
{
246-
fixAllContext.CancellationToken.ThrowIfCancellationRequested();
247-
var projectToFix = projectsToFix[i];
248-
tasks[i] = Task.Run(
249-
async () =>
250-
{
251-
var projectDiagnostics = await fixAllContext.GetAllDiagnosticsAsync(projectToFix).ConfigureAwait(false);
252-
diagnostics.TryAdd(projectToFix.Id, projectDiagnostics);
253-
}, fixAllContext.CancellationToken);
254-
}
255-
256-
await Task.WhenAll(tasks).ConfigureAwait(false);
257-
allDiagnostics = allDiagnostics.AddRange(diagnostics.SelectMany(i => i.Value));
258-
break;
259-
}
260-
261-
if (allDiagnostics.IsEmpty)
262-
{
263-
return ImmutableDictionary<Document, ImmutableArray<Diagnostic>>.Empty;
264-
}
265-
266-
return await GetDocumentDiagnosticsToFixAsync(allDiagnostics, projectsToFix, fixAllContext.CancellationToken).ConfigureAwait(false);
215+
return FixAllContextHelper.GetDocumentDiagnosticsToFixAsync(fixAllContext);
267216
}
268217

269-
public virtual async Task<ImmutableDictionary<Project, ImmutableArray<Diagnostic>>> GetProjectDiagnosticsToFixAsync(FixAllContext fixAllContext)
218+
public virtual Task<ImmutableDictionary<Project, ImmutableArray<Diagnostic>>> GetProjectDiagnosticsToFixAsync(FixAllContext fixAllContext)
270219
{
271-
var project = fixAllContext.Project;
272-
if (project != null)
273-
{
274-
switch (fixAllContext.Scope)
275-
{
276-
case FixAllScope.Project:
277-
var diagnostics = await fixAllContext.GetProjectDiagnosticsAsync(project).ConfigureAwait(false);
278-
return ImmutableDictionary<Project, ImmutableArray<Diagnostic>>.Empty.SetItem(project, diagnostics);
279-
280-
case FixAllScope.Solution:
281-
var projectsAndDiagnostics = new ConcurrentDictionary<Project, ImmutableArray<Diagnostic>>();
282-
var options = new ParallelOptions() { CancellationToken = fixAllContext.CancellationToken };
283-
Parallel.ForEach(project.Solution.Projects, options, proj =>
284-
{
285-
fixAllContext.CancellationToken.ThrowIfCancellationRequested();
286-
var projectDiagnosticsTask = fixAllContext.GetProjectDiagnosticsAsync(proj);
287-
projectDiagnosticsTask.Wait(fixAllContext.CancellationToken);
288-
var projectDiagnostics = projectDiagnosticsTask.Result;
289-
if (projectDiagnostics.Any())
290-
{
291-
projectsAndDiagnostics.TryAdd(proj, projectDiagnostics);
292-
}
293-
});
294-
295-
return projectsAndDiagnostics.ToImmutableDictionary();
296-
}
297-
}
298-
299-
return ImmutableDictionary<Project, ImmutableArray<Diagnostic>>.Empty;
220+
return FixAllContextHelper.GetProjectDiagnosticsToFixAsync(fixAllContext);
300221
}
301222

302223
public virtual async Task<Solution> TryMergeFixesAsync(Solution oldSolution, IEnumerable<CodeAction> codeActions, CancellationToken cancellationToken)
@@ -429,57 +350,6 @@ public virtual async Task<Solution> TryMergeFixesAsync(Solution oldSolution, IEn
429350
return currentSolution;
430351
}
431352

432-
private static async Task<ImmutableDictionary<Document, ImmutableArray<Diagnostic>>> GetDocumentDiagnosticsToFixAsync(
433-
ImmutableArray<Diagnostic> diagnostics,
434-
ImmutableArray<Project> projects,
435-
CancellationToken cancellationToken)
436-
{
437-
var treeToDocumentMap = await GetTreeToDocumentMapAsync(projects, cancellationToken).ConfigureAwait(false);
438-
439-
var builder = ImmutableDictionary.CreateBuilder<Document, ImmutableArray<Diagnostic>>();
440-
foreach (var documentAndDiagnostics in diagnostics.GroupBy(d => GetReportedDocument(d, treeToDocumentMap)))
441-
{
442-
cancellationToken.ThrowIfCancellationRequested();
443-
var document = documentAndDiagnostics.Key;
444-
var diagnosticsForDocument = documentAndDiagnostics.ToImmutableArray();
445-
builder.Add(document, diagnosticsForDocument);
446-
}
447-
448-
return builder.ToImmutable();
449-
}
450-
451-
private static async Task<ImmutableDictionary<SyntaxTree, Document>> GetTreeToDocumentMapAsync(ImmutableArray<Project> projects, CancellationToken cancellationToken)
452-
{
453-
var builder = ImmutableDictionary.CreateBuilder<SyntaxTree, Document>();
454-
foreach (var project in projects)
455-
{
456-
cancellationToken.ThrowIfCancellationRequested();
457-
foreach (var document in project.Documents)
458-
{
459-
cancellationToken.ThrowIfCancellationRequested();
460-
var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
461-
builder.Add(tree, document);
462-
}
463-
}
464-
465-
return builder.ToImmutable();
466-
}
467-
468-
private static Document GetReportedDocument(Diagnostic diagnostic, ImmutableDictionary<SyntaxTree, Document> treeToDocumentsMap)
469-
{
470-
var tree = diagnostic.Location.SourceTree;
471-
if (tree != null)
472-
{
473-
Document document;
474-
if (treeToDocumentsMap.TryGetValue(tree, out document))
475-
{
476-
return document;
477-
}
478-
}
479-
480-
return null;
481-
}
482-
483353
/// <summary>
484354
/// Try to merge the changes between <paramref name="newDocument"/> and <paramref name="oldDocument"/> into <paramref name="cumulativeChanges"/>.
485355
/// If there is any conflicting change in <paramref name="newDocument"/> with existing <paramref name="cumulativeChanges"/>, then the original <paramref name="cumulativeChanges"/> are returned.

StyleCop.Analyzers/StyleCop.Analyzers.CodeFixes/Helpers/DocumentBasedFixAllProvider.cs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,16 +58,24 @@ public override Task<CodeAction> GetFixAsync(FixAllContext fixAllContext)
5858
/// </summary>
5959
/// <param name="fixAllContext">The context for the Fix All operation.</param>
6060
/// <param name="document">The document to fix.</param>
61+
/// <param name="diagnostics">The diagnostics to fix in the document.</param>
6162
/// <returns>
6263
/// <para>The new <see cref="SyntaxNode"/> representing the root of the fixed document.</para>
6364
/// <para>-or-</para>
6465
/// <para><see langword="null"/>, if no changes were made to the document.</para>
6566
/// </returns>
66-
protected abstract Task<SyntaxNode> FixAllInDocumentAsync(FixAllContext fixAllContext, Document document);
67+
protected abstract Task<SyntaxNode> FixAllInDocumentAsync(FixAllContext fixAllContext, Document document, ImmutableArray<Diagnostic> diagnostics);
6768

6869
private async Task<Document> GetDocumentFixesAsync(FixAllContext fixAllContext)
6970
{
70-
var newRoot = await this.FixAllInDocumentAsync(fixAllContext, fixAllContext.Document).ConfigureAwait(false);
71+
var documentDiagnosticsToFix = await FixAllContextHelper.GetDocumentDiagnosticsToFixAsync(fixAllContext).ConfigureAwait(false);
72+
ImmutableArray<Diagnostic> diagnostics;
73+
if (!documentDiagnosticsToFix.TryGetValue(fixAllContext.Document, out diagnostics))
74+
{
75+
return fixAllContext.Document;
76+
}
77+
78+
var newRoot = await this.FixAllInDocumentAsync(fixAllContext, fixAllContext.Document, diagnostics).ConfigureAwait(false);
7179
if (newRoot == null)
7280
{
7381
return fixAllContext.Document;
@@ -78,11 +86,20 @@ private async Task<Document> GetDocumentFixesAsync(FixAllContext fixAllContext)
7886

7987
private async Task<Solution> GetSolutionFixesAsync(FixAllContext fixAllContext, ImmutableArray<Document> documents)
8088
{
89+
var documentDiagnosticsToFix = await FixAllContextHelper.GetDocumentDiagnosticsToFixAsync(fixAllContext).ConfigureAwait(false);
90+
8191
Solution solution = fixAllContext.Solution;
8292
List<Task<SyntaxNode>> newDocuments = new List<Task<SyntaxNode>>(documents.Length);
8393
foreach (var document in documents)
8494
{
85-
newDocuments.Add(this.FixAllInDocumentAsync(fixAllContext, document));
95+
ImmutableArray<Diagnostic> diagnostics;
96+
if (!documentDiagnosticsToFix.TryGetValue(document, out diagnostics))
97+
{
98+
newDocuments.Add(document.GetSyntaxRootAsync(fixAllContext.CancellationToken));
99+
continue;
100+
}
101+
102+
newDocuments.Add(this.FixAllInDocumentAsync(fixAllContext, document, diagnostics));
86103
}
87104

88105
for (int i = 0; i < documents.Length; i++)

0 commit comments

Comments
 (0)