|
7 | 7 | using System.Collections.Immutable; |
8 | 8 | using System.Composition; |
9 | 9 | using System.Diagnostics; |
| 10 | +using System.IO; |
10 | 11 | using System.Linq; |
| 12 | +using System.Reflection; |
11 | 13 | using System.Threading; |
12 | 14 | using System.Threading.Tasks; |
13 | 15 |
|
@@ -40,6 +42,8 @@ internal sealed partial class FormattingEngineImplementation : IFormattingEngine |
40 | 42 | private readonly Dictionary<string, bool> _ruleEnabledMap = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase); |
41 | 43 | private readonly ImmutableDictionary<string, CodeFixProvider> _diagnosticIdToFixerMap; |
42 | 44 | private CompilationWithAnalyzers _compilationWithAnalyzers; |
| 45 | + private object _outputLogger; // Microsoft.CodeAnalysis.ErrorLogger |
| 46 | + private Assembly _loggerAssembly; |
43 | 47 |
|
44 | 48 | public ImmutableArray<string> CopyrightHeader |
45 | 49 | { |
@@ -92,6 +96,27 @@ public ImmutableArray<DiagnosticDescriptor> AllSupportedDiagnostics |
92 | 96 | .SelectMany(a => a.SupportedDiagnostics) |
93 | 97 | .OrderBy(a => a.Id) |
94 | 98 | .ToImmutableArray(); |
| 99 | + |
| 100 | + // Use the Roslyn ErrorLogger type to log diagnostics per analyzer to SARIF format |
| 101 | + public void LogDiagnostics(string filePath, ImmutableArray<Diagnostic> diagnostics) |
| 102 | + { |
| 103 | + if (_loggerAssembly == null) |
| 104 | + { |
| 105 | + _loggerAssembly = Assembly.Load(typeof(CommandLineParser).Assembly.FullName); |
| 106 | + } |
| 107 | + |
| 108 | + _outputLogger = Activator.CreateInstance( |
| 109 | + _loggerAssembly.GetType("Microsoft.CodeAnalysis.ErrorLogger"), |
| 110 | + new object[] { new FileStream(filePath, FileMode.Append, FileAccess.Write), "CodeFormatter", "0.1", _loggerAssembly.GetName().Version }); |
| 111 | + |
| 112 | + var logDiagMethodInfo = _outputLogger.GetType().GetMethod("LogDiagnostic", BindingFlags.NonPublic | BindingFlags.Instance); |
| 113 | + foreach (var diagnostic in diagnostics) |
| 114 | + { |
| 115 | + logDiagMethodInfo.Invoke(_outputLogger, new object[] { diagnostic, System.Globalization.CultureInfo.DefaultThreadCurrentCulture }); |
| 116 | + } |
| 117 | + |
| 118 | + ((IDisposable)_outputLogger).Dispose(); |
| 119 | + } |
95 | 120 |
|
96 | 121 | public FormattingEngineImplementation( |
97 | 122 | FormattingOptions options, |
@@ -260,35 +285,39 @@ private async Task<int> getProjectLinesOfCodeCount(Project project) |
260 | 285 | return totalLines.Sum(); |
261 | 286 | } |
262 | 287 |
|
263 | | - private string createAnalyzerResultText(DiagnosticAnalyzer analyzer, ImmutableArray<Diagnostic> diagnostics, int documentCount, int linesOfCodeInProject) |
264 | | - { |
265 | | - return String.Format("{0}\t{1}\t{2}\t{3}\n", analyzer.ToString(), documentCount, linesOfCodeInProject, diagnostics.Count()); |
266 | | - } |
267 | | - |
268 | 288 | private async Task FormatWithAnalyzersCoreAsync(Workspace workspace, ProjectId projectId, IEnumerable<DiagnosticAnalyzer> analyzers, CancellationToken cancellationToken) |
269 | 289 | { |
270 | 290 | if (analyzers != null && analyzers.Count() != 0) |
271 | 291 | { |
272 | 292 | var project = workspace.CurrentSolution.GetProject(projectId); |
273 | 293 | var diagnostics = await GetDiagnostics(project, analyzers, cancellationToken).ConfigureAwait(false); |
274 | | - |
275 | | - var projectResultText = ""; |
276 | | - var ext = project.Language == "C#" ? ".csproj" : ".vbproj"; |
277 | | - var resultFile = project.FilePath.Substring(project.FilePath.LastIndexOf(System.IO.Path.DirectorySeparatorChar)).Replace(ext, "_CodeFormatterResults.txt"); |
278 | | - |
279 | | - var linesOfCodeInProject = -1; |
280 | | - foreach (var analyzer in analyzers) |
281 | | - { |
282 | | - var diags = await _compilationWithAnalyzers.GetAnalyzerDiagnosticsAsync(ImmutableArray.Create(analyzer), cancellationToken); |
283 | | - linesOfCodeInProject = linesOfCodeInProject == -1 ? await getProjectLinesOfCodeCount(project) : linesOfCodeInProject; |
284 | | - var analyzerResultText = createAnalyzerResultText(analyzer, diags, project.Documents.Count(), linesOfCodeInProject); |
285 | | - FormatLogger.Write(analyzerResultText); |
286 | | - projectResultText += analyzerResultText; |
287 | | - } |
288 | | - |
289 | | - if (LogOutputPath != null) |
| 294 | + // Ensure at least 1 analyzer supporting the current project's language ran |
| 295 | + if (_compilationWithAnalyzers != null) |
290 | 296 | { |
291 | | - System.IO.File.WriteAllText(System.IO.Path.Combine(LogOutputPath, resultFile), projectResultText); |
| 297 | + var ext = project.Language == "C#" ? ".csproj" : ".vbproj"; |
| 298 | + var resultFile = project.FilePath.Substring(project.FilePath.LastIndexOf(Path.DirectorySeparatorChar)).Replace(ext, "_CodeFormatterResults.txt"); |
| 299 | + |
| 300 | + var linesOfCodeInProject = -1; |
| 301 | + foreach (var analyzer in analyzers) |
| 302 | + { |
| 303 | + var diags = await _compilationWithAnalyzers.GetAnalyzerDiagnosticsAsync(ImmutableArray.Create(analyzer), cancellationToken); |
| 304 | + linesOfCodeInProject = linesOfCodeInProject == -1 ? await getProjectLinesOfCodeCount(project) : linesOfCodeInProject; |
| 305 | + var analyzerTelemetryInfo = await _compilationWithAnalyzers.GetAnalyzerTelemetryInfoAsync(analyzer, cancellationToken); |
| 306 | + var analyzerResultText = String.Format("{0}\t{1}\t{2}\t{3}\t{4}\r\n", |
| 307 | + analyzer.ToString(), |
| 308 | + project.Documents.Count(), |
| 309 | + linesOfCodeInProject, |
| 310 | + diagnostics.Count(), |
| 311 | + analyzerTelemetryInfo.ExecutionTime); |
| 312 | + |
| 313 | + FormatLogger.Write(analyzerResultText); |
| 314 | + |
| 315 | + if (LogOutputPath != null) |
| 316 | + { |
| 317 | + LogDiagnostics((LogOutputPath + resultFile).Replace(".txt", ".json"), diags); |
| 318 | + File.AppendAllText(LogOutputPath + resultFile, analyzerResultText); |
| 319 | + } |
| 320 | + } |
292 | 321 | } |
293 | 322 |
|
294 | 323 | if (ApplyFixes) |
@@ -444,6 +473,7 @@ private async Task<ImmutableArray<Diagnostic>> GetDiagnostics(Project project, I |
444 | 473 | } |
445 | 474 | else |
446 | 475 | { |
| 476 | + _compilationWithAnalyzers = null; |
447 | 477 | return ImmutableArray<Diagnostic>.Empty; |
448 | 478 | } |
449 | 479 | } |
|
0 commit comments