Beispiel #1
0
        /// <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());
        }
Beispiel #2
0
        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));
        }
Beispiel #3
0
            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);
            }
        }
Beispiel #6
0
        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());
            }
        }
Beispiel #7
0
            // 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);
Beispiel #8
0
        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);
            }
        }