Esempio n. 1
0
        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;
Esempio n. 2
0
        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;