99using System . IO ;
1010using System . Linq ;
1111using System . Reflection ;
12+ using System . Reflection . Metadata ;
1213using System . Threading ;
1314using System . Threading . Tasks ;
1415using Microsoft . CodeAnalysis . CodeActions ;
1516using Microsoft . CodeAnalysis . CodeStyle ;
1617using Microsoft . CodeAnalysis . Host ;
1718using Microsoft . CodeAnalysis . Host . Mef ;
1819using Microsoft . CodeAnalysis . Options ;
20+ using Microsoft . CodeAnalysis . PooledObjects ;
1921using Microsoft . CodeAnalysis . Remote ;
2022using Microsoft . CodeAnalysis . Shared . TestHooks ;
2123using Microsoft . CodeAnalysis . SolutionCrawler ;
@@ -176,22 +178,47 @@ public async Task<ImmutableArray<DiagnosticData>> GetProjectDiagnosticsForIdsAsy
176178 cancellationToken ) . ConfigureAwait ( false ) ;
177179 }
178180
179- private Task < ImmutableArray < DiagnosticData > > ProduceProjectDiagnosticsAsync (
181+ internal async Task < ImmutableArray < DiagnosticData > > ProduceProjectDiagnosticsAsync (
180182 Project project ,
181183 ImmutableArray < DiagnosticAnalyzer > analyzers ,
182184 ImmutableHashSet < string > ? diagnosticIds ,
183- IReadOnlyList < DocumentId > documentIds ,
185+ ImmutableArray < DocumentId > documentIds ,
184186 bool includeLocalDocumentDiagnostics ,
185187 bool includeNonLocalDocumentDiagnostics ,
186188 bool includeProjectNonLocalResult ,
187189 CancellationToken cancellationToken )
188190 {
189- return _incrementalAnalyzer . ProduceProjectDiagnosticsAsync (
191+ var client = await RemoteHostClient . TryGetClientAsync ( project , cancellationToken ) . ConfigureAwait ( false ) ;
192+ if ( client is not null )
193+ {
194+ var analyzerIds = analyzers . Select ( a => a . GetAnalyzerId ( ) ) . ToImmutableHashSet ( ) ;
195+ var result = await client . TryInvokeAsync < IRemoteDiagnosticAnalyzerService , ImmutableArray < DiagnosticData > > (
196+ project ,
197+ ( service , solution , cancellationToken ) => service . ProduceProjectDiagnosticsAsync (
198+ solution , project . Id , analyzerIds , diagnosticIds , documentIds ,
199+ includeLocalDocumentDiagnostics , includeNonLocalDocumentDiagnostics , includeProjectNonLocalResult ,
200+ cancellationToken ) ,
201+ cancellationToken ) . ConfigureAwait ( false ) ;
202+ if ( ! result . HasValue )
203+ return [ ] ;
204+
205+ return result . Value ;
206+ }
207+
208+ // Fallback to proccessing in proc.
209+ return await _incrementalAnalyzer . ProduceProjectDiagnosticsAsync (
190210 project , analyzers , diagnosticIds , documentIds ,
191211 includeLocalDocumentDiagnostics ,
192212 includeNonLocalDocumentDiagnostics ,
193213 includeProjectNonLocalResult ,
194- cancellationToken ) ;
214+ cancellationToken ) . ConfigureAwait ( false ) ;
215+ }
216+
217+ internal Task < ImmutableArray < DiagnosticAnalyzer > > GetProjectAnalyzersAsync (
218+ Project project , CancellationToken cancellationToken )
219+ {
220+ return _stateManager . GetOrCreateAnalyzersAsync (
221+ project . Solution . SolutionState , project . State , cancellationToken ) ;
195222 }
196223
197224 private async Task < ImmutableArray < DiagnosticAnalyzer > > GetDiagnosticAnalyzersAsync (
@@ -200,9 +227,7 @@ private async Task<ImmutableArray<DiagnosticAnalyzer>> GetDiagnosticAnalyzersAsy
200227 Func < DiagnosticAnalyzer , bool > ? shouldIncludeAnalyzer ,
201228 CancellationToken cancellationToken )
202229 {
203- var analyzersForProject = await _stateManager . GetOrCreateAnalyzersAsync (
204- project . Solution . SolutionState , project . State , cancellationToken ) . ConfigureAwait ( false ) ;
205-
230+ var analyzersForProject = await GetProjectAnalyzersAsync ( project , cancellationToken ) . ConfigureAwait ( false ) ;
206231 var analyzers = analyzersForProject . WhereAsArray ( a => ShouldIncludeAnalyzer ( project , a ) ) ;
207232
208233 return analyzers ;
@@ -355,6 +380,59 @@ public async Task<ImmutableDictionary<string, ImmutableArray<DiagnosticDescripto
355380 return project . Solution . SolutionState . Analyzers . GetDiagnosticDescriptorsPerReference ( this . _analyzerInfoCache , project ) ;
356381 }
357382
383+ public async Task < ImmutableArray < DiagnosticAnalyzer > > GetDeprioritizationCandidatesAsync (
384+ Project project , ImmutableArray < DiagnosticAnalyzer > analyzers , CancellationToken cancellationToken )
385+ {
386+ var client = await RemoteHostClient . TryGetClientAsync ( project , cancellationToken ) . ConfigureAwait ( false ) ;
387+ if ( client is not null )
388+ {
389+ var analyzerIds = analyzers . Select ( a => a . GetAnalyzerId ( ) ) . ToImmutableHashSet ( ) ;
390+ var result = await client . TryInvokeAsync < IRemoteDiagnosticAnalyzerService , ImmutableHashSet < string > > (
391+ project ,
392+ ( service , solution , cancellationToken ) => service . GetDeprioritizationCandidatesAsync (
393+ solution , project . Id , analyzerIds , cancellationToken ) ,
394+ cancellationToken ) . ConfigureAwait ( false ) ;
395+ if ( ! result . HasValue )
396+ return [ ] ;
397+
398+ return analyzers . FilterAnalyzers ( result . Value ) ;
399+ }
400+
401+ using var _ = ArrayBuilder < DiagnosticAnalyzer > . GetInstance ( out var builder ) ;
402+
403+ var hostAnalyzerInfo = await _stateManager . GetOrCreateHostAnalyzerInfoAsync (
404+ project . Solution . SolutionState , project . State , cancellationToken ) . ConfigureAwait ( false ) ;
405+ var compilationWithAnalyzers = await GetOrCreateCompilationWithAnalyzersAsync (
406+ project , analyzers , hostAnalyzerInfo , this . CrashOnAnalyzerException , cancellationToken ) . ConfigureAwait ( false ) ;
407+
408+ foreach ( var analyzer in analyzers )
409+ {
410+ if ( await IsCandidateForDeprioritizationBasedOnRegisteredActionsAsync ( analyzer ) . ConfigureAwait ( false ) )
411+ builder . Add ( analyzer ) ;
412+ }
413+
414+ return builder . ToImmutableAndClear ( ) ;
415+
416+ async Task < bool > IsCandidateForDeprioritizationBasedOnRegisteredActionsAsync ( DiagnosticAnalyzer analyzer )
417+ {
418+ // We deprioritize SymbolStart/End and SemanticModel analyzers from 'Normal' to 'Low' priority bucket,
419+ // as these are computationally more expensive.
420+ // Note that we never de-prioritize compiler analyzer, even though it registers a SemanticModel action.
421+ if ( compilationWithAnalyzers == null ||
422+ analyzer . IsWorkspaceDiagnosticAnalyzer ( ) ||
423+ analyzer . IsCompilerAnalyzer ( ) )
424+ {
425+ return false ;
426+ }
427+
428+ var telemetryInfo = await compilationWithAnalyzers . GetAnalyzerTelemetryInfoAsync ( analyzer , cancellationToken ) . ConfigureAwait ( false ) ;
429+ if ( telemetryInfo == null )
430+ return false ;
431+
432+ return telemetryInfo . SymbolStartActionsCount > 0 || telemetryInfo . SemanticModelActionsCount > 0 ;
433+ }
434+ }
435+
358436 private sealed class DiagnosticAnalyzerComparer : IEqualityComparer < DiagnosticAnalyzer >
359437 {
360438 public static readonly DiagnosticAnalyzerComparer Instance = new ( ) ;
0 commit comments