|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.SolutionCrawler;
using Roslyn.LanguageServer.Protocol;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics;
[Export(typeof(IDiagnosticSourceProvider)), Shared]
[method: ImportingConstructor]
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
internal sealed class WorkspaceDocumentsAndProjectDiagnosticSourceProvider(
[Import] IDiagnosticAnalyzerService diagnosticAnalyzerService,
[Import] IGlobalOptionService globalOptions)
: IDiagnosticSourceProvider
{
public bool IsDocument => false;
public string Name => PullDiagnosticCategories.WorkspaceDocumentsAndProject;
public bool IsEnabled(ClientCapabilities clientCapabilities) => true;
/// <summary>
/// There are three potential sources for reporting workspace diagnostics:
///
/// 1. Full solution analysis: If the user has enabled Full solution analysis, we always run analysis on the latest
/// project snapshot and return up-to-date diagnostics computed from this analysis.
///
/// 2. Code analysis service: Otherwise, if full solution analysis is disabled, and if we have diagnostics from an explicitly
/// triggered code analysis execution on either the current or a prior project snapshot, we return
/// diagnostics from this execution. These diagnostics may be stale with respect to the current
/// project snapshot, but they match user's intent of not enabling continuous background analysis
/// for always having up-to-date workspace diagnostics, but instead computing them explicitly on
/// specific project snapshots by manually running the "Run Code Analysis" command on a project or solution.
///
/// 3. EnC analysis: Emit and debugger diagnostics associated with a closed document or not associated with any document.
///
/// If full solution analysis is disabled AND code analysis was never executed for the given project,
/// we have no workspace diagnostics to report and bail out.
/// </summary>
public async ValueTask<ImmutableArray<IDiagnosticSource>> CreateDiagnosticSourcesAsync(RequestContext context, CancellationToken cancellationToken)
{
Contract.ThrowIfNull(context.Solution);
using var _ = ArrayBuilder<IDiagnosticSource>.GetInstance(out var result);
var solution = context.Solution;
var enableDiagnosticsInSourceGeneratedFiles = solution.Services.GetService<ISolutionCrawlerOptionsService>()?.EnableDiagnosticsInSourceGeneratedFiles == true;
var codeAnalysisService = solution.Services.GetRequiredService<ICodeAnalysisDiagnosticAnalyzerService>();
foreach (var project in WorkspaceDiagnosticSourceHelpers.GetProjectsInPriorityOrder(solution, context.SupportedLanguages))
await AddDocumentsAndProjectAsync(project, diagnosticAnalyzerService, cancellationToken).ConfigureAwait(false);
return result.ToImmutableAndClear();
async Task AddDocumentsAndProjectAsync(Project project, IDiagnosticAnalyzerService diagnosticAnalyzerService, CancellationToken cancellationToken)
{
var fullSolutionAnalysisEnabled = globalOptions.IsFullSolutionAnalysisEnabled(project.Language, out var compilerFullSolutionAnalysisEnabled, out var analyzersFullSolutionAnalysisEnabled);
if (!fullSolutionAnalysisEnabled && !codeAnalysisService.HasProjectBeenAnalyzed(project.Id))
return;
Func<DiagnosticAnalyzer, bool>? shouldIncludeAnalyzer = !compilerFullSolutionAnalysisEnabled || !analyzersFullSolutionAnalysisEnabled
? ShouldIncludeAnalyzer : null;
AddDocumentSources(project.Documents);
AddDocumentSources(project.AdditionalDocuments);
// If all features are enabled for source generated documents, then compute todo-comments/diagnostics for them.
if (enableDiagnosticsInSourceGeneratedFiles)
{
var sourceGeneratedDocuments = await project.GetSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false);
AddDocumentSources(sourceGeneratedDocuments);
}
// Finally, add the appropriate FSA or CodeAnalysis project source to get project specific diagnostics, not associated with any document.
AddProjectSource();
return;
void AddDocumentSources(IEnumerable<TextDocument> documents)
{
foreach (var document in documents)
{
if (!WorkspaceDiagnosticSourceHelpers.ShouldSkipDocument(context, document))
{
// Add the appropriate FSA or CodeAnalysis document source to get document diagnostics.
var documentDiagnosticSource = fullSolutionAnalysisEnabled
? AbstractWorkspaceDocumentDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(document, diagnosticAnalyzerService, shouldIncludeAnalyzer)
: AbstractWorkspaceDocumentDiagnosticSource.CreateForCodeAnalysisDiagnostics(document, codeAnalysisService);
result.Add(documentDiagnosticSource);
}
}
}
void AddProjectSource()
{
var projectDiagnosticSource = fullSolutionAnalysisEnabled
? AbstractProjectDiagnosticSource.CreateForFullSolutionAnalysisDiagnostics(project, diagnosticAnalyzerService, shouldIncludeAnalyzer)
: AbstractProjectDiagnosticSource.CreateForCodeAnalysisDiagnostics(project, codeAnalysisService);
result.Add(projectDiagnosticSource);
}
bool ShouldIncludeAnalyzer(DiagnosticAnalyzer analyzer)
=> analyzer.IsCompilerAnalyzer() ? compilerFullSolutionAnalysisEnabled : analyzersFullSolutionAnalysisEnabled;
}
}
}
|