-
Notifications
You must be signed in to change notification settings - Fork 172
/
AnalyzerFormatter.cs
133 lines (112 loc) · 5.47 KB
/
AnalyzerFormatter.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Tools.Formatters;
using Microsoft.Extensions.Logging;
namespace Microsoft.CodeAnalysis.Tools.Analyzers
{
internal class AnalyzerFormatter : ICodeFormatter
{
public FormatType FormatType => FormatType.CodeStyle;
private readonly IAnalyzerFinder _finder;
private readonly IAnalyzerRunner _runner;
private readonly ICodeFixApplier _applier;
public AnalyzerFormatter(
IAnalyzerFinder finder,
IAnalyzerRunner runner,
ICodeFixApplier applier)
{
_finder = finder;
_runner = runner;
_applier = applier;
}
public async Task<Solution> FormatAsync(
Solution solution,
ImmutableArray<DocumentId> formattableDocuments,
FormatOptions options,
ILogger logger,
List<FormattedFile> formattedFiles,
CancellationToken cancellationToken)
{
var analysisStopwatch = Stopwatch.StartNew();
logger.LogTrace($"Analyzing code style.");
if (!options.SaveFormattedFiles)
{
await LogDiagnosticsAsync(solution, formattableDocuments, options, logger, cancellationToken);
}
else
{
solution = await FixDiagnosticsAsync(solution, formattableDocuments, logger, cancellationToken);
}
logger.LogTrace("Analysis complete in {0}ms.", analysisStopwatch.ElapsedMilliseconds);
return solution;
}
private async Task LogDiagnosticsAsync(Solution solution, ImmutableArray<DocumentId> formattableDocuments, FormatOptions options, ILogger logger, CancellationToken cancellationToken)
{
var pairs = _finder.GetAnalyzersAndFixers();
var paths = formattableDocuments.Select(id => solution.GetDocument(id)?.FilePath)
.OfType<string>().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) =>
{
await _runner.RunCodeAnalysisAsync(result, analyzers, project, paths, logger, token);
}, cancellationToken);
LogDiagnosticLocations(result.Diagnostics.SelectMany(kvp => kvp.Value), options.WorkspaceFilePath, options.ChangesAreErrors, logger);
return;
static void LogDiagnosticLocations(IEnumerable<Diagnostic> diagnostics, string workspacePath, bool changesAreErrors, ILogger logger)
{
var workspaceFolder = Path.GetDirectoryName(workspacePath);
foreach (var diagnostic in diagnostics)
{
var message = diagnostic.GetMessage();
var filePath = diagnostic.Location.SourceTree?.FilePath;
var mappedLineSpan = diagnostic.Location.GetMappedLineSpan();
var changePosition = mappedLineSpan.StartLinePosition;
var formatMessage = $"{Path.GetRelativePath(workspaceFolder, filePath)}({changePosition.Line + 1},{changePosition.Character + 1}): {message}";
if (changesAreErrors)
{
logger.LogError(formatMessage);
}
else
{
logger.LogWarning(formatMessage);
}
}
}
}
private async Task<Solution> FixDiagnosticsAsync(Solution solution, ImmutableArray<DocumentId> formattableDocuments, ILogger logger, CancellationToken cancellationToken)
{
var pairs = _finder.GetAnalyzersAndFixers();
var paths = formattableDocuments.Select(id => solution.GetDocument(id)?.FilePath)
.OfType<string>().ToImmutableArray();
// we need to run each codefix iteratively so ensure that all diagnostics are found and fixed
foreach (var (analyzer, codefix) in pairs)
{
var result = new CodeAnalysisResult();
await solution.Projects.ForEachAsync(async (project, token) =>
{
await _runner.RunCodeAnalysisAsync(result, analyzer, project, paths, logger, token);
}, cancellationToken);
var hasDiagnostics = result.Diagnostics.Any(kvp => kvp.Value.Count > 0);
if (hasDiagnostics && codefix is object)
{
logger.LogTrace($"Applying fixes for {codefix.GetType().Name}");
solution = await _applier.ApplyCodeFixesAsync(solution, result, codefix, logger, cancellationToken);
var changedSolution = await _applier.ApplyCodeFixesAsync(solution, result, codefix, logger, cancellationToken);
if (changedSolution.GetChanges(solution).Any())
{
solution = changedSolution;
}
}
}
return solution;
}
}
}