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)); } } } }
/// <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; }
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); }
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); } }
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);
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);