private async Task <ImmutableArray <DiagnosticData> > GetSemanticDiagnosticsAsync(DiagnosticAnalyzer analyzer, bool isCompilerAnalyzer, CancellationToken cancellationToken) { // PERF: // 1. Compute diagnostics for all analyzers with a single invocation into CompilationWithAnalyzers. // This is critical for better analyzer execution performance through re-use of bound node cache. // 2. Ensure that the compiler analyzer is treated specially and does not block on diagnostic computation // for rest of the analyzers. This is needed to ensure faster refresh for compiler diagnostics while typing. RoslynDebug.Assert(_compilationWithAnalyzers != null); var span = AnalysisScope.Span; var document = (Document)AnalysisScope.TextDocument; if (isCompilerAnalyzer) { #if DEBUG await VerifySpanBasedCompilerDiagnosticsAsync().ConfigureAwait(false); #endif var adjustedSpan = await GetAdjustedSpanForCompilerAnalyzerAsync().ConfigureAwait(false); return(await GetCompilerAnalyzerDiagnosticsAsync(analyzer, adjustedSpan, cancellationToken).ConfigureAwait(false)); } if (_lazySemanticDiagnostics == null) { var analysisScope = AnalysisScope.WithAnalyzers(_compilationBasedAnalyzersInAnalysisScope); var semanticDiagnostics = await GetAnalysisResultAsync(analysisScope, cancellationToken).ConfigureAwait(false); Interlocked.CompareExchange(ref _lazySemanticDiagnostics, semanticDiagnostics, null); } return(_lazySemanticDiagnostics.TryGetValue(analyzer, out var diagnosticAnalysisResult) ? diagnosticAnalysisResult.GetDocumentDiagnostics(AnalysisScope.TextDocument.Id, AnalysisScope.Kind) : ImmutableArray <DiagnosticData> .Empty); async Task <TextSpan?> GetAdjustedSpanForCompilerAnalyzerAsync() { // This method is to workaround a bug (https://github.com/dotnet/roslyn/issues/1557) // once that bug is fixed, we should be able to use given span as it is. Debug.Assert(isCompilerAnalyzer); if (!span.HasValue) { return(null); } var service = document.GetRequiredLanguageService <ISyntaxFactsService>(); var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var startNode = service.GetContainingMemberDeclaration(root, span.Value.Start); var endNode = service.GetContainingMemberDeclaration(root, span.Value.End); if (startNode == endNode) { // use full member span if (service.IsMethodLevelMember(startNode)) { return(startNode.FullSpan); } // use span as it is return(span); } var startSpan = service.IsMethodLevelMember(startNode) ? startNode.FullSpan : span.Value; var endSpan = service.IsMethodLevelMember(endNode) ? endNode.FullSpan : span.Value; return(TextSpan.FromBounds(Math.Min(startSpan.Start, endSpan.Start), Math.Max(startSpan.End, endSpan.End))); } #if DEBUG async Task VerifySpanBasedCompilerDiagnosticsAsync() { if (!span.HasValue) { return; } // make sure what we got from range is same as what we got from whole diagnostics var model = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false); var rangeDeclaractionDiagnostics = model.GetDeclarationDiagnostics(span.Value).ToArray(); var rangeMethodBodyDiagnostics = model.GetMethodBodyDiagnostics(span.Value).ToArray(); var rangeDiagnostics = rangeDeclaractionDiagnostics.Concat(rangeMethodBodyDiagnostics).Where(shouldInclude).ToArray(); var wholeDeclarationDiagnostics = model.GetDeclarationDiagnostics().ToArray(); var wholeMethodBodyDiagnostics = model.GetMethodBodyDiagnostics().ToArray(); var wholeDiagnostics = wholeDeclarationDiagnostics.Concat(wholeMethodBodyDiagnostics).Where(shouldInclude).ToArray(); if (!AnalyzerHelper.AreEquivalent(rangeDiagnostics, wholeDiagnostics)) { // otherwise, report non-fatal watson so that we can fix those cases FatalError.ReportWithoutCrash(new Exception("Bug in GetDiagnostics")); // make sure we hold onto these for debugging. GC.KeepAlive(rangeDeclaractionDiagnostics); GC.KeepAlive(rangeMethodBodyDiagnostics); GC.KeepAlive(rangeDiagnostics); GC.KeepAlive(wholeDeclarationDiagnostics); GC.KeepAlive(wholeMethodBodyDiagnostics); GC.KeepAlive(wholeDiagnostics); } return;
private async Task <ImmutableArray <Diagnostic> > GetSemanticDiagnosticsAsync(SemanticModel model, DiagnosticAnalyzer analyzer, bool isCompilerAnalyzer, CancellationToken cancellationToken) { // PERF: // 1. Compute diagnostics for all analyzers with a single invocation into CompilationWithAnalyzers. // This is critical for better analyzer execution performance through re-use of bound node cache. // 2. Ensure that the compiler analyzer is treated specially and does not block on diagnostic computation // for rest of the analyzers. This is needed to ensure faster refresh for compiler diagnostics while typing. RoslynDebug.Assert(_compilationWithAnalyzers != null); var span = AnalysisScope.Span; if (isCompilerAnalyzer) { #if DEBUG VerifySpanBasedCompilerDiagnostics(model); #endif var adjustedSpan = await GetAdjustedSpanForCompilerAnalyzerAsync().ConfigureAwait(false); // TODO: Move this invocation to OOP return(await _compilationWithAnalyzers.GetAnalyzerSemanticDiagnosticsAsync(model, adjustedSpan, ImmutableArray.Create(analyzer), cancellationToken).ConfigureAwait(false)); } // We specially handle IPragmaSuppressionsAnalyzer by passing in the 'CompilationWithAnalyzers' // context to compute unnecessary pragma suppression diagnostics. // This is required because this analyzer relies on reported compiler + analyzer diagnostics // for unnecessary pragma analysis. if (analyzer is IPragmaSuppressionsAnalyzer suppressionsAnalyzer && !AnalysisScope.Span.HasValue) { using var _ = ArrayBuilder <Diagnostic> .GetInstance(out var builder); await suppressionsAnalyzer.AnalyzeAsync(model, span, _compilationWithAnalyzers, _analyzerInfoCache.GetDiagnosticDescriptors, IsCompilationEndAnalyzer, builder.Add, cancellationToken).ConfigureAwait(false); return(builder.ToImmutable()); } if (_lazySemanticDiagnostics == null) { // TODO: Move this invocation to OOP var analysisResult = await _compilationWithAnalyzers.GetAnalysisResultAsync(model, span, _compilationBasedAnalyzersInAnalysisScope, cancellationToken).ConfigureAwait(false); var treeDiagnostics = analysisResult.SemanticDiagnostics.TryGetValue(model.SyntaxTree, out var value) ? value : ImmutableDictionary <DiagnosticAnalyzer, ImmutableArray <Diagnostic> > .Empty; Interlocked.CompareExchange(ref _lazySemanticDiagnostics, treeDiagnostics, null); } return(_lazySemanticDiagnostics.TryGetValue(analyzer, out var diagnostics) ? diagnostics : ImmutableArray <Diagnostic> .Empty); bool IsCompilationEndAnalyzer(DiagnosticAnalyzer analyzer) { RoslynDebug.AssertNotNull(_compilationWithAnalyzers); return(_analyzerInfoCache.IsCompilationEndAnalyzer(analyzer, AnalysisScope.Document.Project, _compilationWithAnalyzers.Compilation) ?? true); } async Task <TextSpan?> GetAdjustedSpanForCompilerAnalyzerAsync() { // This method is to workaround a bug (https://github.com/dotnet/roslyn/issues/1557) // once that bug is fixed, we should be able to use given span as it is. Debug.Assert(isCompilerAnalyzer); if (!span.HasValue) { return(null); } var service = AnalysisScope.Document.GetRequiredLanguageService <ISyntaxFactsService>(); var root = await model.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false); var startNode = service.GetContainingMemberDeclaration(root, span.Value.Start); var endNode = service.GetContainingMemberDeclaration(root, span.Value.End); if (startNode == endNode) { // use full member span if (service.IsMethodLevelMember(startNode)) { return(startNode.FullSpan); } // use span as it is return(span); } var startSpan = service.IsMethodLevelMember(startNode) ? startNode.FullSpan : span.Value; var endSpan = service.IsMethodLevelMember(endNode) ? endNode.FullSpan : span.Value; return(TextSpan.FromBounds(Math.Min(startSpan.Start, endSpan.Start), Math.Max(startSpan.End, endSpan.End))); } #if DEBUG void VerifySpanBasedCompilerDiagnostics(SemanticModel model) { if (!span.HasValue) { return; } // make sure what we got from range is same as what we got from whole diagnostics var rangeDeclaractionDiagnostics = model.GetDeclarationDiagnostics(span.Value).ToArray(); var rangeMethodBodyDiagnostics = model.GetMethodBodyDiagnostics(span.Value).ToArray(); var rangeDiagnostics = rangeDeclaractionDiagnostics.Concat(rangeMethodBodyDiagnostics).Where(shouldInclude).ToArray(); var wholeDeclarationDiagnostics = model.GetDeclarationDiagnostics().ToArray(); var wholeMethodBodyDiagnostics = model.GetMethodBodyDiagnostics().ToArray(); var wholeDiagnostics = wholeDeclarationDiagnostics.Concat(wholeMethodBodyDiagnostics).Where(shouldInclude).ToArray(); if (!AnalyzerHelper.AreEquivalent(rangeDiagnostics, wholeDiagnostics)) { // otherwise, report non-fatal watson so that we can fix those cases FatalError.ReportWithoutCrash(new Exception("Bug in GetDiagnostics")); // make sure we hold onto these for debugging. GC.KeepAlive(rangeDeclaractionDiagnostics); GC.KeepAlive(rangeMethodBodyDiagnostics); GC.KeepAlive(rangeDiagnostics); GC.KeepAlive(wholeDeclarationDiagnostics); GC.KeepAlive(wholeMethodBodyDiagnostics); GC.KeepAlive(wholeDiagnostics); } return;