private async Task ComputeDocumentDiagnosticsAsync(
                ImmutableArray <DiagnosticAnalyzer> analyzers,
                AnalysisKind kind,
                TextSpan?span,
                ArrayBuilder <DiagnosticData> list,
                CancellationToken cancellationToken)
            {
                if (analyzers.IsEmpty)
                {
                    return;
                }

                var analysisScope = new DocumentAnalysisScope(_document, span, analyzers, kind);
                var executor      = new DocumentAnalysisExecutor(analysisScope, _compilationWithAnalyzers, _owner._diagnosticAnalyzerRunner, logPerformanceInfo: false);

                foreach (var analyzer in analyzers)
                {
                    cancellationToken.ThrowIfCancellationRequested();

                    var analyzerTypeName = analyzer.GetType().Name;
                    using (_addOperationScope?.Invoke(analyzerTypeName))
                        using (_addOperationScope is object?RoslynEventSource.LogInformationalBlock(FunctionId.DiagnosticAnalyzerService_GetDiagnosticsForSpanAsync, analyzerTypeName, cancellationToken) : default)
                        {
                            var dx = await executor.ComputeDiagnosticsAsync(analyzer, cancellationToken).ConfigureAwait(false);

                            if (dx != null)
                            {
                                // no state yet
                                list.AddRange(dx.Where(ShouldInclude));
                            }
                        }
                }
            }
Exemple #2
0
        /// <summary>
        /// Return all cached local diagnostics (syntax, semantic) that belong to given document for the given StateSet (analyzer).
        /// Also returns empty diagnostics for suppressed analyzer.
        /// Returns null if the diagnostics need to be computed.
        /// </summary>
        private DocumentAnalysisData?TryGetCachedDocumentAnalysisData(
            TextDocument document, StateSet stateSet,
            AnalysisKind kind, VersionStamp version,
            BackgroundAnalysisScope analysisScope, bool isActiveDocument,
            bool isOpenDocument, bool isGeneratedRazorDocument,
            CancellationToken cancellationToken)
        {
            Debug.Assert(isActiveDocument || isOpenDocument || isGeneratedRazorDocument);

            try
            {
                var state        = stateSet.GetOrCreateActiveFileState(document.Id);
                var existingData = state.GetAnalysisData(kind);

                if (existingData.Version == version)
                {
                    return(existingData);
                }

                // Perf optimization: Check whether analyzer is suppressed for project or document and avoid getting diagnostics if suppressed.
                if (!DocumentAnalysisExecutor.IsAnalyzerEnabledForProject(stateSet.Analyzer, document.Project, GlobalOptions) ||
                    !IsAnalyzerEnabledForDocument(stateSet.Analyzer, analysisScope, isActiveDocument, isOpenDocument, isGeneratedRazorDocument))
                {
                    return(new DocumentAnalysisData(version, existingData.Items, ImmutableArray <DiagnosticData> .Empty));
                }

                return(null);
            }
            catch (Exception e) when(FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken))
            {
                throw ExceptionUtilities.Unreachable;
            }
Exemple #3
0
        private static async Task <DocumentAnalysisData> ComputeDocumentAnalysisDataAsync(
            DocumentAnalysisExecutor executor, StateSet stateSet, CancellationToken cancellationToken)
        {
            var kind     = executor.AnalysisScope.Kind;
            var document = executor.AnalysisScope.TextDocument;

            // get log title and functionId
            GetLogFunctionIdAndTitle(kind, out var functionId, out var title);

            using (Logger.LogBlock(functionId, GetDocumentLogMessage, title, document, stateSet.Analyzer, cancellationToken))
            {
                try
                {
                    var diagnostics = await executor.ComputeDiagnosticsAsync(stateSet.Analyzer, cancellationToken).ConfigureAwait(false);

                    // this is no-op in product. only run in test environment
                    Logger.Log(functionId, (t, d, a, ds) => $"{GetDocumentLogMessage(t, d, a)}, {string.Join(Environment.NewLine, ds)}",
                               title, document, stateSet.Analyzer, diagnostics);

                    var version = await GetDiagnosticVersionAsync(document.Project, cancellationToken).ConfigureAwait(false);

                    var state        = stateSet.GetOrCreateActiveFileState(document.Id);
                    var existingData = state.GetAnalysisData(kind);

                    // we only care about local diagnostics
                    return(new DocumentAnalysisData(version, existingData.Items, diagnostics.ToImmutableArrayOrEmpty()));
                }
                catch (Exception e) when(FatalError.ReportUnlessCanceled(e))
                {
                    throw ExceptionUtilities.Unreachable;
                }
            }
        }
        public void CanCreateDiagnosticForAnalyzerLoadFailure(
            AnalyzerLoadFailureEventArgs.FailureErrorCode errorCode,
            [CombinatorialValues(LanguageNames.CSharp, LanguageNames.VisualBasic, null)] string?languageName)
        {
            // One potential value is None, which isn't actually a valid enum value to test.
            if (errorCode == AnalyzerLoadFailureEventArgs.FailureErrorCode.None)
            {
                return;
            }

            var expectsTypeName = errorCode is AnalyzerLoadFailureEventArgs.FailureErrorCode.UnableToCreateAnalyzer or
                                  AnalyzerLoadFailureEventArgs.FailureErrorCode.ReferencesFramework;

            const string analyzerTypeName = "AnalyzerTypeName";
            var          eventArgs        = new AnalyzerLoadFailureEventArgs(
                errorCode,
                message: errorCode.ToString(),
                typeNameOpt: expectsTypeName ? analyzerTypeName : null);

            // Ensure CreateAnalyzerLoadFailureDiagnostic doesn't fail when called. We don't assert much about the resulting
            // diagnostic -- this is primarly to ensure we don't forget to update it if a new error code is added.
            var diagnostic = DocumentAnalysisExecutor.CreateAnalyzerLoadFailureDiagnostic(eventArgs, "Analyzer.dll", null, languageName);

            Assert.Equal(languageName, diagnostic.Language);

            if (expectsTypeName)
            {
                Assert.Contains(analyzerTypeName, diagnostic.Message);
            }
            else
            {
                Assert.DoesNotContain(analyzerTypeName, diagnostic.Message);
            }
        }
            private async Task <ImmutableArray <DiagnosticData> > GetDiagnosticsAsync(
                TextDocument document, AnalysisKind kind, CancellationToken cancellationToken)
            {
                var loadDiagnostic = await document.State.GetLoadDiagnosticAsync(cancellationToken).ConfigureAwait(false);

                if (loadDiagnostic != null)
                {
                    return(ImmutableArray.Create(DiagnosticData.Create(loadDiagnostic, document)));
                }

                var project   = document.Project;
                var analyzers = GetAnalyzers(project.Solution.State.Analyzers, project);

                if (analyzers.IsEmpty)
                {
                    return(ImmutableArray <DiagnosticData> .Empty);
                }

                var ideOptions = _service._globalOptions.GetIdeAnalyzerOptions(project);

                var compilationWithAnalyzers = await DocumentAnalysisExecutor.CreateCompilationWithAnalyzersAsync(
                    project, ideOptions, analyzers, includeSuppressedDiagnostics : false, cancellationToken).ConfigureAwait(false);

                var analysisScope = new DocumentAnalysisScope(document, span: null, analyzers, kind);
                var executor      = new DocumentAnalysisExecutor(analysisScope, compilationWithAnalyzers, _diagnosticAnalyzerRunner, logPerformanceInfo: true);

                using var _ = ArrayBuilder <DiagnosticData> .GetInstance(out var builder);

                foreach (var analyzer in analyzers)
                {
                    builder.AddRange(await executor.ComputeDiagnosticsAsync(analyzer, cancellationToken).ConfigureAwait(false));
                }

                return(builder.ToImmutable());
            }
            private bool ShouldIncludeStateSet(Project project, StateSet stateSet)
            {
                if (!DocumentAnalysisExecutor.IsAnalyzerEnabledForProject(stateSet.Analyzer, project, Owner.GlobalOptions))
                {
                    return(false);
                }

                if (_diagnosticIds != null && Owner.DiagnosticAnalyzerInfoCache.GetDiagnosticDescriptors(stateSet.Analyzer).All(d => !_diagnosticIds.Contains(d.Id)))
                {
                    return(false);
                }

                return(true);
            }
Exemple #7
0
        private async Task AnalyzeProjectAsync(Project project, bool forceAnalyzerRun, CancellationToken cancellationToken)
        {
            try
            {
                var stateSets = GetStateSetsForFullSolutionAnalysis(_stateManager.GetOrUpdateStateSets(project), project);

                // get driver only with active analyzers.
                var ideOptions = AnalyzerService.GlobalOptions.GetIdeAnalyzerOptions(project);

                // PERF: get analyzers that are not suppressed and marked as open file only
                // this is perf optimization. we cache these result since we know the result. (no diagnostics)
                var activeAnalyzers = stateSets
                                      .Select(s => s.Analyzer)
                                      .Where(a => DocumentAnalysisExecutor.IsAnalyzerEnabledForProject(a, project, GlobalOptions) && !a.IsOpenFileOnly(ideOptions.CleanupOptions?.SimplifierOptions));

                CompilationWithAnalyzers?compilationWithAnalyzers = null;

                if (forceAnalyzerRun || GlobalOptions.IsFullSolutionAnalysisEnabled(project.Language))
                {
                    compilationWithAnalyzers = await DocumentAnalysisExecutor.CreateCompilationWithAnalyzersAsync(project, ideOptions, activeAnalyzers, includeSuppressedDiagnostics : true, cancellationToken).ConfigureAwait(false);
                }

                var result = await GetProjectAnalysisDataAsync(compilationWithAnalyzers, project, ideOptions, stateSets, forceAnalyzerRun, cancellationToken).ConfigureAwait(false);

                if (result.OldResult == null)
                {
                    RaiseProjectDiagnosticsIfNeeded(project, stateSets, result.Result);
                    return;
                }

                // no cancellation after this point.
                // any analyzer that doesn't have result will be treated as returned empty set
                // which means we will remove those from error list
                foreach (var stateSet in stateSets)
                {
                    var state = stateSet.GetOrCreateProjectState(project.Id);

                    await state.SaveToInMemoryStorageAsync(project, result.GetResult(stateSet.Analyzer)).ConfigureAwait(false);
                }

                RaiseProjectDiagnosticsIfNeeded(project, stateSets, result.OldResult, result.Result);
            }
            catch (Exception e) when(FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken))
            {
                throw ExceptionUtilities.Unreachable;
            }
        }
            public static async Task <LatestDiagnosticsForSpanGetter> CreateAsync(
                DiagnosticIncrementalAnalyzer owner,
                Document document,
                TextSpan?range,
                bool blockForData,
                Func <string, IDisposable?>?addOperationScope,
                bool includeSuppressedDiagnostics,
                CodeActionRequestPriority priority,
                Func <string, bool>?shouldIncludeDiagnostic,
                CancellationToken cancellationToken)
            {
                var stateSets = owner._stateManager
                                .GetOrCreateStateSets(document.Project).Where(s => DocumentAnalysisExecutor.IsAnalyzerEnabledForProject(s.Analyzer, document.Project, owner.GlobalOptions));

                var crashOnAnalyzerException = owner.GlobalOptions.GetOption(InternalDiagnosticsOptions.CrashOnAnalyzerException);
                var compilationWithAnalyzers = await GetOrCreateCompilationWithAnalyzersAsync(document.Project, stateSets, includeSuppressedDiagnostics, crashOnAnalyzerException, cancellationToken).ConfigureAwait(false);

                return(new LatestDiagnosticsForSpanGetter(
                           owner, compilationWithAnalyzers, document, stateSets, shouldIncludeDiagnostic, range,
                           blockForData, addOperationScope, includeSuppressedDiagnostics, priority));
            }
        private async Task AnalyzeDocumentForKindAsync(TextDocument document, AnalysisKind kind, CancellationToken cancellationToken)
        {
            try
            {
                if (!document.SupportsDiagnostics())
                {
                    return;
                }

                var isActiveDocument         = _documentTrackingService.TryGetActiveDocument() == document.Id;
                var isOpenDocument           = document.IsOpen();
                var isGeneratedRazorDocument = document.Services.GetService <DocumentPropertiesService>()?.DiagnosticsLspClientName != null;

                // Only analyze open/active documents, unless it is a generated Razor document.
                if (!isActiveDocument && !isOpenDocument && !isGeneratedRazorDocument)
                {
                    return;
                }

                var stateSets = _stateManager.GetOrUpdateStateSets(document.Project);
                var compilationWithAnalyzers = await GetOrCreateCompilationWithAnalyzersAsync(document.Project, stateSets, cancellationToken).ConfigureAwait(false);

                var version = await GetDiagnosticVersionAsync(document.Project, cancellationToken).ConfigureAwait(false);

                var backgroundAnalysisScope = GlobalOptions.GetBackgroundAnalysisScope(document.Project.Language);

                // We split the diagnostic computation for document into following steps:
                //  1. Try to get cached diagnostics for each analyzer, while computing the set of analyzers that do not have cached diagnostics.
                //  2. Execute all the non-cached analyzers with a single invocation into CompilationWithAnalyzers.
                //  3. Fetch computed diagnostics per-analyzer from the above invocation, and cache and raise diagnostic reported events.
                // In near future, the diagnostic computation invocation into CompilationWithAnalyzers will be moved to OOP.
                // This should help simplify and/or remove the IDE layer diagnostic caching in devenv process.

                // First attempt to fetch diagnostics from the cache, while computing the state sets for analyzers that are not cached.
                using var _ = ArrayBuilder <StateSet> .GetInstance(out var nonCachedStateSets);

                foreach (var stateSet in stateSets)
                {
                    var data = TryGetCachedDocumentAnalysisData(document, stateSet, kind, version,
                                                                backgroundAnalysisScope, isActiveDocument, isOpenDocument, isGeneratedRazorDocument, cancellationToken);
                    if (data.HasValue)
                    {
                        // We need to persist and raise diagnostics for suppressed analyzer.
                        PersistAndRaiseDiagnosticsIfNeeded(data.Value, stateSet);
                    }
                    else
                    {
                        nonCachedStateSets.Add(stateSet);
                    }
                }

                // Then, compute the diagnostics for non-cached state sets, and cache and raise diagnostic reported events for these diagnostics.
                if (nonCachedStateSets.Count > 0)
                {
                    var analysisScope = new DocumentAnalysisScope(document, span: null, nonCachedStateSets.SelectAsArray(s => s.Analyzer), kind);
                    var executor      = new DocumentAnalysisExecutor(analysisScope, compilationWithAnalyzers, _diagnosticAnalyzerRunner, logPerformanceInfo: true, onAnalysisException: OnAnalysisException);
                    var logTelemetry  = document.Project.Solution.Options.GetOption(DiagnosticOptions.LogTelemetryForBackgroundAnalyzerExecution);
                    foreach (var stateSet in nonCachedStateSets)
                    {
                        var computedData = await ComputeDocumentAnalysisDataAsync(executor, stateSet, logTelemetry, cancellationToken).ConfigureAwait(false);

                        PersistAndRaiseDiagnosticsIfNeeded(computedData, stateSet);
                    }
                }
            }
            catch (Exception e) when(FatalError.ReportAndPropagateUnlessCanceled(e, cancellationToken))
            {
                throw ExceptionUtilities.Unreachable;
            }

            void PersistAndRaiseDiagnosticsIfNeeded(DocumentAnalysisData result, StateSet stateSet)
            {
                if (result.FromCache == true)
                {
                    RaiseDocumentDiagnosticsIfNeeded(document, stateSet, kind, result.Items);
                    return;
                }

                // no cancellation after this point.
                var state = stateSet.GetOrCreateActiveFileState(document.Id);

                state.Save(kind, result.ToPersistData());

                RaiseDocumentDiagnosticsIfNeeded(document, stateSet, kind, result.OldItems, result.Items);
            }

            void OnAnalysisException()
            {
                // Do not re-use cached CompilationWithAnalyzers instance in presence of an exception, as the underlying analysis state might be corrupt.
                ClearCompilationsWithAnalyzersCache(document.Project);
            }
        }
Exemple #10
0
        private async Task AnalyzeDocumentForKindAsync(TextDocument document, AnalysisKind kind, CancellationToken cancellationToken)
        {
            try
            {
                if (!AnalysisEnabled(document))
                {
                    // to reduce allocations, here, we don't clear existing diagnostics since it is dealt by other entry point such as
                    // DocumentReset or DocumentClosed.
                    return;
                }

                var stateSets = _stateManager.GetOrUpdateStateSets(document.Project);
                var compilationWithAnalyzers = await GetOrCreateCompilationWithAnalyzersAsync(document.Project, stateSets, cancellationToken).ConfigureAwait(false);

                // We split the diagnostic computation for document into following steps:
                //  1. Try to get cached diagnostics for each analyzer, while computing the set of analyzers that do not have cached diagnostics.
                //  2. Execute all the non-cached analyzers with a single invocation into CompilationWithAnalyzers.
                //  3. Fetch computed diagnostics per-analyzer from the above invocation, and cache and raise diagnostic reported events.
                // In near future, the diagnostic computation invocation into CompilationWithAnalyzers will be moved to OOP.
                // This should help simplify and/or remove the IDE layer diagnostic caching in devenv process.

                // First attempt to fetch diagnostics from the cache, while computing the state sets for analyzers that are not cached.
                using var _ = ArrayBuilder <StateSet> .GetInstance(out var nonCachedStateSets);

                foreach (var stateSet in stateSets)
                {
                    var data = await TryGetCachedDocumentAnalysisDataAsync(document, stateSet, kind, cancellationToken).ConfigureAwait(false);

                    if (data.HasValue)
                    {
                        // We need to persist and raise diagnostics for suppressed analyzer.
                        PersistAndRaiseDiagnosticsIfNeeded(data.Value, stateSet);
                    }
                    else
                    {
                        nonCachedStateSets.Add(stateSet);
                    }
                }

                // Then, compute the diagnostics for non-cached state sets, and cache and raise diagnostic reported events for these diagnostics.
                if (nonCachedStateSets.Count > 0)
                {
                    var analysisScope = new DocumentAnalysisScope(document, span: null, nonCachedStateSets.SelectAsArray(s => s.Analyzer), kind);
                    var executor      = new DocumentAnalysisExecutor(analysisScope, compilationWithAnalyzers, DiagnosticAnalyzerInfoCache);
                    foreach (var stateSet in nonCachedStateSets)
                    {
                        var computedData = await ComputeDocumentAnalysisDataAsync(executor, stateSet, cancellationToken).ConfigureAwait(false);

                        PersistAndRaiseDiagnosticsIfNeeded(computedData, stateSet);
                    }

                    var asyncToken = AnalyzerService.Listener.BeginAsyncOperation(nameof(AnalyzeDocumentForKindAsync));
                    var _2         = ReportAnalyzerPerformanceAsync(document, compilationWithAnalyzers, cancellationToken).CompletesAsyncOperation(asyncToken);
                }
            }
            catch (Exception e) when(FatalError.ReportUnlessCanceled(e))
            {
                throw ExceptionUtilities.Unreachable;
            }

            void PersistAndRaiseDiagnosticsIfNeeded(DocumentAnalysisData result, StateSet stateSet)
            {
                if (result.FromCache == true)
                {
                    RaiseDocumentDiagnosticsIfNeeded(document, stateSet, kind, result.Items);
                    return;
                }

                // no cancellation after this point.
                var state = stateSet.GetOrCreateActiveFileState(document.Id);

                state.Save(kind, result.ToPersistData());

                RaiseDocumentDiagnosticsIfNeeded(document, stateSet, kind, result.OldItems, result.Items);
            }
        }
 public static DiagnosticData CreateAnalyzerLoadFailureDiagnostic(AnalyzerLoadFailureEventArgs e, string fullPath, ProjectId?projectId, string?language)
 => DocumentAnalysisExecutor.CreateAnalyzerLoadFailureDiagnostic(e, fullPath, projectId, language);
Exemple #12
0
 private static Task <CompilationWithAnalyzers?> CreateCompilationWithAnalyzersAsync(Project project, IdeAnalyzerOptions ideOptions, IEnumerable <StateSet> stateSets, bool includeSuppressedDiagnostics, CancellationToken cancellationToken)
 => DocumentAnalysisExecutor.CreateCompilationWithAnalyzersAsync(project, ideOptions, stateSets.Select(s => s.Analyzer), includeSuppressedDiagnostics, cancellationToken);