@@ -96,7 +96,7 @@ public ImmutableArray<DiagnosticDescriptor> AllSupportedDiagnostics
9696 . SelectMany ( a => a . SupportedDiagnostics )
9797 . OrderBy ( a => a . Id )
9898 . ToImmutableArray ( ) ;
99-
99+
100100 // Use the Roslyn ErrorLogger type to log diagnostics per analyzer to SARIF format
101101 public void LogDiagnostics ( string filePath , ImmutableArray < Diagnostic > diagnostics )
102102 {
@@ -241,6 +241,7 @@ public async Task FormatProjectWithAnalyzersAsync(Project project, CancellationT
241241 await FormatProjectWithSyntaxAnalyzersAsync ( workspace , project . Id , cancellationToken ) ;
242242 await FormatProjectWithLocalAnalyzersAsync ( workspace , project . Id , cancellationToken ) ;
243243 await FormatProjectWithGlobalAnalyzersAsync ( workspace , project . Id , cancellationToken ) ;
244+ await FormatProjectWithUnspecifiedAnalyzersAsync ( workspace , project . Id , cancellationToken ) ;
244245
245246 watch . Stop ( ) ;
246247 FormatLogger . WriteLine ( "Total time for formatting {0} - {1}" , project . Name , watch . Elapsed ) ;
@@ -260,12 +261,7 @@ private async Task FormatProjectWithLocalAnalyzersAsync(Workspace workspace, Pro
260261
261262 private async Task FormatProjectWithGlobalAnalyzersAsync ( Workspace workspace , ProjectId projectId , CancellationToken cancellationToken )
262263 {
263- // Remaining analyzers are either RuleType.GlobalSemantic, in which case we need to run them one by one lest they conflict
264- // with each other, or else they are of an unknown type in which case we should conservatively treat them as if they
265- // may conflict with one another.
266- var analyzers = _analyzers . Where ( a => {
267- return a . SupportedDiagnostics . All ( d => ! ( d . CustomTags . Contains ( RuleType . Syntactic ) || d . CustomTags . Contains ( RuleType . LocalSemantic ) ) ) ;
268- } ) ;
264+ var analyzers = _analyzers . Where ( a => a . SupportedDiagnostics . All ( d => d . CustomTags . Contains ( RuleType . GlobalSemantic ) ) ) ;
269265
270266 // Since global analyzers can potentially conflict with each other, run them one by one.
271267 foreach ( var analyzer in analyzers )
@@ -274,10 +270,22 @@ private async Task FormatProjectWithGlobalAnalyzersAsync(Workspace workspace, Pr
274270 }
275271 }
276272
277- private async Task < int > getProjectLinesOfCodeCount ( Project project )
273+ private async Task FormatProjectWithUnspecifiedAnalyzersAsync ( Workspace workspace , ProjectId projectId , CancellationToken cancellationToken )
278274 {
279- var allLines = project . Documents . Select ( async doc =>
275+ var analyzers = _analyzers . Where ( a => a . SupportedDiagnostics . All ( d => {
276+ return ! ( d . CustomTags . Contains ( RuleType . Syntactic ) || d . CustomTags . Contains ( RuleType . LocalSemantic ) || d . CustomTags . Contains ( RuleType . GlobalSemantic ) ) ;
277+ } ) ) ;
278+
279+ // Treat analyzers with unknown rule types as if they were global in case they might conflict with each other
280+ foreach ( var analyzer in analyzers )
280281 {
282+ await FormatWithAnalyzersCoreAsync ( workspace , projectId , new [ ] { analyzer } , cancellationToken ) ;
283+ }
284+ }
285+
286+ private async Task < int > getProjectLinesOfCodeCount ( Project project )
287+ {
288+ var allLines = project . Documents . Select ( async doc => {
281289 var text = await doc . GetTextAsync ( ) ;
282290 return text . Lines . Count ;
283291 } ) ;
@@ -294,28 +302,35 @@ private async Task FormatWithAnalyzersCoreAsync(Workspace workspace, ProjectId p
294302 // Ensure at least 1 analyzer supporting the current project's language ran
295303 if ( _compilationWithAnalyzers != null )
296304 {
297- var ext = project . Language == "C#" ? ".csproj" : ".vbproj" ;
298- var resultFile = project . FilePath . Substring ( project . FilePath . LastIndexOf ( Path . DirectorySeparatorChar ) ) . Replace ( ext , "_CodeFormatterResults.txt" ) ;
305+ var extension = StringComparer . OrdinalIgnoreCase . Equals ( project . Language , "C#" ) ? ".csproj" : ".vbproj" ;
306+ var resultFile = project . FilePath . Substring ( project . FilePath . LastIndexOf ( Path . DirectorySeparatorChar ) ) . Replace ( extension , "_CodeFormatterResults.txt" ) ;
299307
300308 var linesOfCodeInProject = - 1 ;
301309 foreach ( var analyzer in analyzers )
302310 {
303311 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 )
312+ if ( Verbose || LogOutputPath != null )
316313 {
317- LogDiagnostics ( ( LogOutputPath + resultFile ) . Replace ( ".txt" , ".json" ) , diags ) ;
318- File . AppendAllText ( LogOutputPath + resultFile , analyzerResultText ) ;
314+ linesOfCodeInProject = linesOfCodeInProject == - 1 ? await getProjectLinesOfCodeCount ( project ) : linesOfCodeInProject ;
315+ var analyzerTelemetryInfo = await _compilationWithAnalyzers . GetAnalyzerTelemetryInfoAsync ( analyzer , cancellationToken ) ;
316+ var analyzerResultText = string . Format ( "{0}\t {1}\t {2}\t {3}\t {4}\r \n " ,
317+ analyzer . ToString ( ) ,
318+ project . Documents . Count ( ) ,
319+ linesOfCodeInProject ,
320+ diagnostics . Count ( ) ,
321+ analyzerTelemetryInfo . ExecutionTime ) ;
322+
323+ if ( Verbose )
324+ {
325+ FormatLogger . Write ( analyzerResultText ) ;
326+ }
327+
328+ if ( LogOutputPath != null )
329+ {
330+ var resultPath = LogOutputPath + resultFile ;
331+ LogDiagnostics ( Path . ChangeExtension ( resultPath , "json" ) , diags ) ;
332+ File . AppendAllText ( resultPath , analyzerResultText ) ;
333+ }
319334 }
320335 }
321336 }
@@ -343,7 +358,7 @@ private async Task FormatWithAnalyzersCoreAsync(Workspace workspace, ProjectId p
343358 }
344359 }
345360 }
346-
361+
347362 public void ToggleRuleEnabled ( IRuleMetadata ruleMetaData , bool enabled )
348363 {
349364 _ruleEnabledMap [ ruleMetaData . Name ] = enabled ;
@@ -654,7 +669,30 @@ private async Task<Solution> RunGlobalSemanticPass(Solution solution, IReadOnlyL
654669
655670 public void AddAnalyzers ( ImmutableArray < DiagnosticAnalyzer > analyzers )
656671 {
657- _analyzers = _analyzers . Concat ( analyzers ) ;
672+ var toAdd = new List < DiagnosticAnalyzer > ( ) ;
673+ foreach ( var analyzer in analyzers )
674+ {
675+ IEqualityComparer < DiagnosticAnalyzer > comparer = new AnalyzerComparer ( ) ;
676+ if ( ! _analyzers . Contains ( analyzer , comparer ) )
677+ {
678+ toAdd . Add ( analyzer ) ;
679+ }
680+ }
681+ _analyzers = _analyzers . Concat ( toAdd . ToArray ( ) ) ;
682+ }
683+ }
684+
685+
686+ internal class AnalyzerComparer : IEqualityComparer < DiagnosticAnalyzer >
687+ {
688+ public bool Equals ( DiagnosticAnalyzer x , DiagnosticAnalyzer y )
689+ {
690+ return x . ToString ( ) == y . ToString ( ) ;
691+ }
692+
693+ public int GetHashCode ( DiagnosticAnalyzer obj )
694+ {
695+ return obj . GetHashCode ( ) ;
658696 }
659697 }
660- }
698+ }
0 commit comments