Skip to content

Commit

Permalink
Moved parallelism to the AnalyzerRunner
Browse files Browse the repository at this point in the history
  • Loading branch information
JoeRobich committed Jun 5, 2020
1 parent 93a6396 commit 7b8edc8
Show file tree
Hide file tree
Showing 7 changed files with 75 additions and 67 deletions.
11 changes: 0 additions & 11 deletions eng/Signing.props

This file was deleted.

75 changes: 51 additions & 24 deletions src/Analyzers/AnalyzerFormatter.cs
Expand Up @@ -7,6 +7,8 @@
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Tools.Formatters;
using Microsoft.Extensions.Logging;

Expand Down Expand Up @@ -41,37 +43,52 @@ internal class AnalyzerFormatter : ICodeFormatter
var analysisStopwatch = Stopwatch.StartNew();
logger.LogTrace($"Analyzing code style.");

if (!options.SaveFormattedFiles)
{
await LogDiagnosticsAsync(solution, formattableDocuments, options, logger, formattedFiles, cancellationToken);
}
else
var analyzersAndFixers = _finder.GetAnalyzersAndFixers();
var formattablePaths = formattableDocuments.Select(id => solution.GetDocument(id)?.FilePath)
.OfType<string>().ToImmutableArray();

logger.LogTrace("Determining diagnostics.");

var projectDiagnostics = await GetProjectDiagnosticsAsync(solution, analyzersAndFixers, formattablePaths, options, logger, formattedFiles, cancellationToken).ConfigureAwait(false);

var projectDiagnosticsMS = analysisStopwatch.ElapsedMilliseconds;
logger.LogTrace(Resources.Complete_in_0_ms, projectDiagnosticsMS);

if (options.SaveFormattedFiles)
{
solution = await FixDiagnosticsAsync(solution, formattableDocuments, logger, cancellationToken);
logger.LogTrace("Fixing diagnostics.");

solution = await FixDiagnosticsAsync(solution, analyzersAndFixers, projectDiagnostics, formattablePaths, logger, cancellationToken).ConfigureAwait(false);

var fixDiagnosticsMS = analysisStopwatch.ElapsedMilliseconds - projectDiagnosticsMS;
logger.LogTrace(Resources.Complete_in_0_ms, fixDiagnosticsMS);
}

logger.LogTrace("Analysis complete in {0}ms.", analysisStopwatch.ElapsedMilliseconds);

return solution;
}

private async Task LogDiagnosticsAsync(Solution solution, ImmutableArray<DocumentId> formattableDocuments, FormatOptions options, ILogger logger, List<FormattedFile> formattedFiles, CancellationToken cancellationToken)
private async Task<ImmutableDictionary<ProjectId, ImmutableHashSet<string>>> GetProjectDiagnosticsAsync(
Solution solution,
ImmutableArray<(DiagnosticAnalyzer Analyzer, CodeFixProvider? Fixer)> analyzersAndFixers,
ImmutableArray<string> formattablePaths,
FormatOptions options,
ILogger logger,
List<FormattedFile> formattedFiles,
CancellationToken cancellationToken)
{
var pairs = _finder.GetAnalyzersAndFixers();
var paths = formattableDocuments.Select(id => solution.GetDocument(id)?.FilePath)
.OfType<string>().ToImmutableArray();
var analyzers = analyzersAndFixers.Select(pair => pair.Analyzer).ToImmutableArray();

// no need to run codefixes as we won't persist the changes
var analyzers = pairs.Select(x => x.Analyzer).ToImmutableArray();
var result = new CodeAnalysisResult();
await solution.Projects.ForEachAsync(async (project, token) =>
foreach (var project in solution.Projects)
{
await _runner.RunCodeAnalysisAsync(result, analyzers, project, paths, logger, token).ConfigureAwait(false);
}, cancellationToken).ConfigureAwait(false);
await _runner.RunCodeAnalysisAsync(result, analyzers, project, formattablePaths, logger, cancellationToken).ConfigureAwait(false);
}

LogDiagnosticLocations(result.Diagnostics.SelectMany(kvp => kvp.Value), options.WorkspaceFilePath, options.ChangesAreErrors, logger, formattedFiles);

return;
return result.Diagnostics.ToImmutableDictionary(kvp => kvp.Key.Id, kvp => kvp.Value.Select(diagnostic => diagnostic.Id).ToImmutableHashSet());

static void LogDiagnosticLocations(IEnumerable<Diagnostic> diagnostics, string workspacePath, bool changesAreErrors, ILogger logger, List<FormattedFile> formattedFiles)
{
Expand Down Expand Up @@ -99,20 +116,30 @@ static void LogDiagnosticLocations(IEnumerable<Diagnostic> diagnostics, string w
}
}

private async Task<Solution> FixDiagnosticsAsync(Solution solution, ImmutableArray<DocumentId> formattableDocuments, ILogger logger, CancellationToken cancellationToken)
private async Task<Solution> FixDiagnosticsAsync(
Solution solution,
ImmutableArray<(DiagnosticAnalyzer Analyzer, CodeFixProvider? Fixer)> analyzersAndFixers,
ImmutableDictionary<ProjectId, ImmutableHashSet<string>> projectDiagnostics,
ImmutableArray<string> formattablePaths,
ILogger logger,
CancellationToken cancellationToken)
{
var pairs = _finder.GetAnalyzersAndFixers();
var paths = formattableDocuments.Select(id => solution.GetDocument(id)?.FilePath)
.OfType<string>().ToImmutableArray();
var analyzers = analyzersAndFixers.Select(pair => pair.Analyzer).ToImmutableArray();

// we need to run each codefix iteratively so ensure that all diagnostics are found and fixed
foreach (var (analyzer, codefix) in pairs)
foreach (var (analyzer, codefix) in analyzersAndFixers)
{
var result = new CodeAnalysisResult();
await solution.Projects.ForEachAsync(async (project, token) =>
foreach (var project in solution.Projects)
{
await _runner.RunCodeAnalysisAsync(result, analyzer, project, paths, logger, token).ConfigureAwait(false);
}, cancellationToken).ConfigureAwait(false);
if (!projectDiagnostics.TryGetValue(project.Id, out var diagnosticIds) ||
!analyzer.SupportedDiagnostics.Any(diagnostic => diagnosticIds.Contains(diagnostic.Id)))
{
continue;
}

await _runner.RunCodeAnalysisAsync(result, analyzer, project, formattablePaths, logger, cancellationToken).ConfigureAwait(false);
}

var hasDiagnostics = result.Diagnostics.Any(kvp => kvp.Value.Count > 0);
if (hasDiagnostics && codefix is object)
Expand Down
16 changes: 10 additions & 6 deletions src/Analyzers/AnalyzerRunner.cs
Expand Up @@ -29,17 +29,21 @@ internal partial class AnalyzerRunner : IAnalyzerRunner
ILogger logger,
CancellationToken cancellationToken)
{
var compilation = await project.GetCompilationAsync(cancellationToken);
var compilation = await project.GetCompilationAsync(cancellationToken).ConfigureAwait(false);
if (compilation is null)
{
return;
}

var analyzerCompilation = compilation.WithAnalyzers(
analyzers,
options: project.AnalyzerOptions,
cancellationToken);
var diagnostics = await analyzerCompilation.GetAnalyzerDiagnosticsAsync(cancellationToken);
var analyzerOptions = new CompilationWithAnalyzersOptions(
project.AnalyzerOptions,
onAnalyzerException: null,
concurrentAnalysis: true,
logAnalyzerExecutionTime: false,
reportSuppressedDiagnostics: false);
var analyzerCompilation = compilation.WithAnalyzers(analyzers, analyzerOptions);
var diagnostics = await analyzerCompilation.GetAnalyzerDiagnosticsAsync(cancellationToken).ConfigureAwait(false);

// filter diagnostics
foreach (var diagnostic in diagnostics)
{
Expand Down
21 changes: 10 additions & 11 deletions src/Analyzers/CodeAnalysisResult.cs
Expand Up @@ -2,24 +2,23 @@

using System.Collections.Generic;

using NonBlocking;

namespace Microsoft.CodeAnalysis.Tools.Analyzers
{
internal class CodeAnalysisResult
{
private readonly ConcurrentDictionary<Project, List<Diagnostic>> _dictionary
= new ConcurrentDictionary<Project, List<Diagnostic>>();
private readonly Dictionary<Project, List<Diagnostic>> _dictionary
= new Dictionary<Project, List<Diagnostic>>();

internal void AddDiagnostic(Project project, Diagnostic diagnostic)
{
_ = _dictionary.AddOrUpdate(project,
addValueFactory: (key) => new List<Diagnostic>() { diagnostic },
updateValueFactory: (key, list) =>
{
list.Add(diagnostic);
return list;
});
if (!_dictionary.ContainsKey(project))
{
_dictionary.Add(project, new List<Diagnostic>() { diagnostic });
}
else
{
_dictionary[project].Add(diagnostic);
}
}

public IReadOnlyDictionary<Project, List<Diagnostic>> Diagnostics
Expand Down
12 changes: 0 additions & 12 deletions src/Analyzers/Extensions.cs
@@ -1,26 +1,14 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;

namespace Microsoft.CodeAnalysis.Tools.Analyzers
{
public static class Extensions
{
public static Task ForEachAsync<T>(this IEnumerable<T> enumerable,
Func<T, CancellationToken, Task> action,
CancellationToken cancellationToken = default)
=> Task.WhenAll(enumerable
.AsParallel()
.WithDegreeOfParallelism(Environment.ProcessorCount)
.WithCancellation(cancellationToken)
.Select(x => action(x, cancellationToken)));

public static bool Any(this SolutionChanges solutionChanges)
=> solutionChanges.GetProjectChanges().Any(x => x.GetChangedDocuments().Any());

Expand Down
6 changes: 4 additions & 2 deletions src/Analyzers/SolutionCodeFixApplier.cs
Expand Up @@ -42,8 +42,10 @@ internal class SolutionCodeFixApplier : ICodeFixApplier
fixAllDiagnosticProvider: new DiagnosticProvider(result),
cancellationToken: cancellationToken);

var action = await fixAllProvider.GetFixAsync(fixAllContext);
var operations = await (action?.GetOperationsAsync(cancellationToken) ?? Task.FromResult(ImmutableArray<CodeActionOperation>.Empty));
var action = await fixAllProvider.GetFixAsync(fixAllContext).ConfigureAwait(false);
var operations = action is object
? await action.GetOperationsAsync(cancellationToken).ConfigureAwait(false)
: ImmutableArray<CodeActionOperation>.Empty;
var applyChangesOperation = operations.OfType<ApplyChangesOperation>().SingleOrDefault();
return applyChangesOperation?.ChangedSolution ?? solution;
}
Expand Down
1 change: 0 additions & 1 deletion src/dotnet-format.csproj
Expand Up @@ -57,7 +57,6 @@
https://github.com/microsoft/MSBuildLocator/issues/88
-->
<PackageReference Include="Newtonsoft.Json" Version="$(NewtonsoftJsonVersion)" />
<PackageReference Include="NonBlocking" Version="1.0.3" />
</ItemGroup>

<ItemGroup>
Expand Down

0 comments on commit 7b8edc8

Please sign in to comment.