/// <summary> /// Return list of <see cref="StateSet"/> to be used for full solution analysis. /// </summary> private IReadOnlyList <StateSet> GetStateSetsForFullSolutionAnalysis(IEnumerable <StateSet> stateSets, Project project) { // If full analysis is off, remove state that is created from build. // this will make sure diagnostics from build (converted from build to live) will never be cleared // until next build. if (SolutionCrawlerOptions.GetBackgroundAnalysisScope(project) != BackgroundAnalysisScope.FullSolution) { stateSets = stateSets.Where(s => !s.FromBuild(project.Id)); } // Compute analyzer config options for computing effective severity. // Note that these options are not cached onto the project, so we compute it once upfront. var analyzerConfigOptions = project.GetAnalyzerConfigOptions(); // Include only analyzers we want to run for full solution analysis. // Analyzers not included here will never be saved because result is unknown. return(stateSets.Where(s => IsCandidateForFullSolutionAnalysis(s.Analyzer, project, analyzerConfigOptions)).ToList()); }
private Task BuildCompilationsAsync( Solution solution, ProjectId initialProject, ISet <ProjectId> projectsToBuild, CancellationToken cancellationToken) { var allProjectIds = new List <ProjectId>(); if (initialProject != null) { allProjectIds.Add(initialProject); } allProjectIds.AddRange(projectsToBuild.Where(p => p != initialProject)); var logger = Logger.LogBlock(FunctionId.BackgroundCompiler_BuildCompilationsAsync, cancellationToken); // Skip performing any background compilation for projects where user has explicitly // set the background analysis scope to only analyze active files. var compilationTasks = allProjectIds .Select(solution.GetProject) .Where(p => p != null && SolutionCrawlerOptions.GetBackgroundAnalysisScope(p) != BackgroundAnalysisScope.ActiveFile) .Select(p => p.GetCompilationAsync(cancellationToken)) .ToArray(); return(Task.WhenAll(compilationTasks).SafeContinueWith(t => { logger.Dispose(); if (t.Status == TaskStatus.RanToCompletion) { lock (_buildGate) { if (!cancellationToken.IsCancellationRequested) { _mostRecentCompilations = t.Result; } } } }, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.Default)); }
private bool SupportedLiveDiagnosticId(Project project, Compilation compilation, string id) { var projectId = project.Id; lock (_liveDiagnosticIdMap) { if (_liveDiagnosticIdMap.TryGetValue(projectId, out var ids)) { return(ids.Contains(id)); } var fullSolutionAnalysis = SolutionCrawlerOptions.GetBackgroundAnalysisScope(project) == BackgroundAnalysisScope.FullSolution; if (!project.SupportsCompilation || fullSolutionAnalysis) { return(IsSupportedDiagnosticId(project.Id, id)); } // set ids set var builder = ImmutableHashSet.CreateBuilder <string>(); var diagnosticService = _owner._diagnosticService; var infoCache = diagnosticService.AnalyzerInfoCache; foreach (var analyzersPerReference in infoCache.CreateDiagnosticAnalyzersPerReference(project)) { foreach (var analyzer in analyzersPerReference.Value) { if (diagnosticService.IsCompilationEndAnalyzer(analyzer, project, compilation)) { continue; } var diagnosticIds = infoCache.GetDiagnosticDescriptors(analyzer).Select(d => d.Id); builder.UnionWith(diagnosticIds); } } var set = builder.ToImmutable(); _liveDiagnosticIdMap.Add(projectId, set); return(set.Contains(id)); } }
private IEnumerable <StateSet> GetStateSetsForFullSolutionAnalysis(IEnumerable <StateSet> stateSets, Project project) { // If full analysis is off, remove state that is created from build. // this will make sure diagnostics from build (converted from build to live) will never be cleared // until next build. if (SolutionCrawlerOptions.GetBackgroundAnalysisScope(project) != BackgroundAnalysisScope.FullSolution) { stateSets = stateSets.Where(s => !s.FromBuild(project.Id)); } // include all analyzers if option is on if (project.Solution.Workspace.Options.GetOption(InternalDiagnosticsOptions.ProcessHiddenDiagnostics)) { return(stateSets); } // Include only analyzers we want to run for full solution analysis. // Analyzers not included here will never be saved because result is unknown. return(stateSets.Where(s => IsCandidateForFullSolutionAnalysis(s.Analyzer, project))); }
public async Task ActiveDocumentSwitchedAsync(TextDocument document, CancellationToken cancellationToken) { // When the analysis scope is set to 'ActiveFile' and the active document is switched, // we retrigger analysis of newly active document. // For the remaining analysis scopes, we always analyze all the open files, so switching active // documents between two open files doesn't require us to retrigger analysis of the newly active document. if (SolutionCrawlerOptions.GetBackgroundAnalysisScope(document.Project) != BackgroundAnalysisScope.ActiveFile) { return; } // First reset the document states. await TextDocumentResetAsync(document, cancellationToken).ConfigureAwait(false); // Trigger syntax analysis. await AnalyzeDocumentForKindAsync(document, AnalysisKind.Syntax, cancellationToken).ConfigureAwait(false); // Trigger semantic analysis for source documents. Non-source documents do not support semantic analysis. if (document is Document) { await AnalyzeDocumentForKindAsync(document, AnalysisKind.Semantic, cancellationToken).ConfigureAwait(false); } }
private static bool AnalysisEnabled(TextDocument document, IDocumentTrackingService documentTrackingService) { if (document.Services.GetService <DocumentPropertiesService>()?.DiagnosticsLspClientName != null) { // This is a generated Razor document, and they want diagnostics, so let's report it return(true); } if (!document.SupportsDiagnostics()) { return(false); } // change it to check active file (or visible files), not open files if active file tracking is enabled. // otherwise, use open file. if (SolutionCrawlerOptions.GetBackgroundAnalysisScope(document.Project) == BackgroundAnalysisScope.ActiveFile) { return(documentTrackingService.TryGetActiveDocument() == document.Id); } else { return(document.IsOpen()); } }
// Local functions static BackgroundAnalysisScope?GetBackgroundAnalysisScope(Workspace workspace) { var csharpAnalysisScope = SolutionCrawlerOptions.GetDefaultBackgroundAnalysisScopeFromOptions(workspace.CurrentSolution.Options, LanguageNames.CSharp); var visualBasicAnalysisScope = SolutionCrawlerOptions.GetDefaultBackgroundAnalysisScopeFromOptions(workspace.CurrentSolution.Options, LanguageNames.VisualBasic); var containsCSharpProject = workspace.CurrentSolution.Projects.Any(static project => project.Language == LanguageNames.CSharp);
internal static async ValueTask <ImmutableArray <Document> > GetWorkspacePullDocumentsAsync(RequestContext context, CancellationToken cancellationToken) { Contract.ThrowIfNull(context.Solution); // If we're being called from razor, we do not support WorkspaceDiagnostics at all. For razor, workspace // diagnostics will be handled by razor itself, which will operate by calling into Roslyn and asking for // document-diagnostics instead. if (context.ClientName != null) { return(ImmutableArray <Document> .Empty); } using var _ = ArrayBuilder <Document> .GetInstance(out var result); var solution = context.Solution; var documentTrackingService = solution.Workspace.Services.GetRequiredService <IDocumentTrackingService>(); // Collect all the documents from the solution in the order we'd like to get diagnostics for. This will // prioritize the files from currently active projects, but then also include all other docs in all projects // (depending on current FSA settings). var activeDocument = documentTrackingService.GetActiveDocument(solution); var visibleDocuments = documentTrackingService.GetVisibleDocuments(solution); // Now, prioritize the projects related to the active/visible files. await AddDocumentsFromProjectAsync(activeDocument?.Project, context.SupportedLanguages, isOpen : true, cancellationToken).ConfigureAwait(false); foreach (var doc in visibleDocuments) { await AddDocumentsFromProjectAsync(doc.Project, context.SupportedLanguages, isOpen : true, cancellationToken).ConfigureAwait(false); } // finally, add the remainder of all documents. foreach (var project in solution.Projects) { await AddDocumentsFromProjectAsync(project, context.SupportedLanguages, isOpen : false, cancellationToken).ConfigureAwait(false); } // Ensure that we only process documents once. result.RemoveDuplicates(); return(result.ToImmutable()); async Task AddDocumentsFromProjectAsync(Project?project, ImmutableArray <string> supportedLanguages, bool isOpen, CancellationToken cancellationToken) { if (project == null) { return; } if (!supportedLanguages.Contains(project.Language)) { // This project is for a language not supported by the LSP server making the request. // Do not report diagnostics for these projects. return; } // if the project doesn't necessarily have an open file in it, then only include it if the user has full // solution analysis on. if (!isOpen) { var analysisScope = SolutionCrawlerOptions.GetBackgroundAnalysisScope(solution.Workspace.Options, project.Language); if (analysisScope != BackgroundAnalysisScope.FullSolution) { context.TraceInformation($"Skipping project '{project.Name}' as it has no open document and Full Solution Analysis is off"); return; } } // Otherwise, if the user has an open file from this project, or FSA is on, then include all the // documents from it. If all features are enabled for source generated documents, make sure they are // included as well. var documents = project.Documents; if (solution.Workspace.Services.GetService <ISyntaxTreeConfigurationService>() is { EnableOpeningSourceGeneratedFilesInWorkspace : true }) { documents = documents.Concat(await project.GetSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false)); } foreach (var document in documents) { // Only consider closed documents here (and only open ones in the DocumentPullDiagnosticHandler). // Each handler treats those as separate worlds that they are responsible for. if (context.IsTracking(document.GetURI())) { context.TraceInformation($"Skipping tracked document: {document.GetURI()}"); continue; } result.Add(document); } }
public async ValueTask SynchronizeWithBuildAsync( ImmutableDictionary <ProjectId, ImmutableArray <DiagnosticData> > buildDiagnostics, TaskQueue postBuildAndErrorListRefreshTaskQueue, bool onBuildCompleted, CancellationToken cancellationToken) { var options = Workspace.Options; using (Logger.LogBlock(FunctionId.DiagnosticIncrementalAnalyzer_SynchronizeWithBuildAsync, LogSynchronizeWithBuild, options, buildDiagnostics, cancellationToken)) { DebugVerifyDiagnosticLocations(buildDiagnostics); if (!PreferBuildErrors(options)) { // Prefer live errors over build errors return; } var solution = Workspace.CurrentSolution; foreach (var(projectId, diagnostics) in buildDiagnostics) { cancellationToken.ThrowIfCancellationRequested(); var project = solution.GetProject(projectId); if (project == null) { continue; } var stateSets = _stateManager.CreateBuildOnlyProjectStateSet(project); var newResult = CreateAnalysisResults(project, stateSets, diagnostics); // PERF: Save the diagnostics into in-memory cache on the main thread. // Saving them into persistent storage is expensive, so we invoke that operation on a separate task queue // to ensure faster error list refresh. foreach (var stateSet in stateSets) { cancellationToken.ThrowIfCancellationRequested(); var state = stateSet.GetOrCreateProjectState(project.Id); var result = GetResultOrEmpty(newResult, stateSet.Analyzer, project.Id, VersionStamp.Default); await state.SaveToInMemoryStorageAsync(project, result).ConfigureAwait(false); } // Raise diagnostic updated events after the new diagnostics have been stored into the in-memory cache. if (diagnostics.IsEmpty) { ClearAllDiagnostics(stateSets, projectId); } else { RaiseProjectDiagnosticsIfNeeded(project, stateSets, newResult); } } // Refresh live diagnostics after solution build completes. if (onBuildCompleted && PreferLiveErrorsOnOpenedFiles(options)) { // Enqueue re-analysis of active document with high-priority right away. if (_documentTrackingService.GetActiveDocument(solution) is { } activeDocument) { AnalyzerService.Reanalyze(Workspace, documentIds: ImmutableArray.Create(activeDocument.Id), highPriority: true); } // Enqueue remaining re-analysis with normal priority on a separate task queue // that will execute at the end of all the post build and error list refresh tasks. _ = postBuildAndErrorListRefreshTaskQueue.ScheduleTask(nameof(SynchronizeWithBuildAsync), () => { // Enqueue re-analysis of open documents. AnalyzerService.Reanalyze(Workspace, documentIds: Workspace.GetOpenDocumentIds()); // Enqueue re-analysis of projects, if required. foreach (var projectsByLanguage in solution.Projects.GroupBy(p => p.Language)) { if (SolutionCrawlerOptions.GetBackgroundAnalysisScope(Workspace.Options, projectsByLanguage.Key) == BackgroundAnalysisScope.FullSolution) { AnalyzerService.Reanalyze(Workspace, projectsByLanguage.Select(p => p.Id)); } } }, cancellationToken); } } }
public async Task MergeAsync( IPersistentStorageService persistentService, ActiveFileState state, TextDocument document ) { Contract.ThrowIfFalse(state.DocumentId == document.Id); // merge active file state to project state var lastResult = _lastResult; var syntax = state.GetAnalysisData(AnalysisKind.Syntax); var semantic = state.GetAnalysisData(AnalysisKind.Semantic); var project = document.Project; // if project didn't successfully loaded, then it is same as FSA off var fullAnalysis = SolutionCrawlerOptions.GetBackgroundAnalysisScope(project) == BackgroundAnalysisScope.FullSolution && await project .HasSuccessfullyLoadedAsync(CancellationToken.None) .ConfigureAwait(false); // keep from build flag if full analysis is off var fromBuild = fullAnalysis ? false : lastResult.FromBuild; var openFileOnlyAnalyzer = _owner.Analyzer.IsOpenFileOnly( document.Project.Solution.Options ); // if it is allowed to keep project state, check versions and if they are same, bail out. // if full solution analysis is off or we are asked to reset document state, we always merge. if ( fullAnalysis && !openFileOnlyAnalyzer && syntax.Version != VersionStamp.Default && syntax.Version == semantic.Version && syntax.Version == lastResult.Version ) { // all data is in sync already. return; } // we have mixed versions or full analysis is off, set it to default so that it can be re-calculated next time so data can be in sync. var version = VersionStamp.Default; // serialization can't be cancelled. var serializer = new DiagnosticDataSerializer(_owner.AnalyzerVersion, version); // save active file diagnostics back to project state await SerializeAsync( persistentService, serializer, project, document, document.Id, _owner.SyntaxStateName, syntax.Items ) .ConfigureAwait(false); await SerializeAsync( persistentService, serializer, project, document, document.Id, _owner.SemanticStateName, semantic.Items ) .ConfigureAwait(false); // save last aggregated form of analysis result _lastResult = _lastResult.UpdateAggregatedResult( version, state.DocumentId, fromBuild ); }
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 = SolutionCrawlerOptions.GetBackgroundAnalysisScope(document.Project); // 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); } }