// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Diagnostics.Log;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.SolutionCrawler;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.Diagnostics
{
internal abstract class BaseDiagnosticIncrementalAnalyzer : IIncrementalAnalyzer
{
protected BaseDiagnosticIncrementalAnalyzer(DiagnosticAnalyzerService owner, Workspace workspace, HostAnalyzerManager hostAnalyzerManager, AbstractHostDiagnosticUpdateSource hostDiagnosticUpdateSource)
{
Contract.ThrowIfNull(owner);
this.Owner = owner;
this.Workspace = workspace;
this.HostAnalyzerManager = hostAnalyzerManager;
this.HostDiagnosticUpdateSource = hostDiagnosticUpdateSource;
this.DiagnosticLogAggregator = new DiagnosticLogAggregator(owner);
}
#region IIncrementalAnalyzer
///
/// Analyze a single document such that local diagnostics for that document become available,
/// prioritizing analyzing this document over analyzing the rest of the project.
/// Calls for each
/// unique group of diagnostics, where a group is identified by analysis classification (syntax/semantics), document, and analyzer.
///
/// The document to analyze.
/// If present, indicates a portion (e.g. a method body) of the document to analyze.
/// The reason(s) this analysis was triggered.
///
///
public abstract Task AnalyzeDocumentAsync(Document document, SyntaxNode bodyOpt, InvocationReasons reasons, CancellationToken cancellationToken);
///
/// Analyze a single project such that diagnostics for the entire project become available.
/// Calls for each
/// unique group of diagnostics, where a group is identified by analysis classification (project), project, and analyzer.
///
/// The project to analyze.
/// Indicates a change to the declarative semantics of the project.
/// The reason(s) this analysis was triggered.
///
///
public abstract Task AnalyzeProjectAsync(Project project, bool semanticsChanged, InvocationReasons reasons, CancellationToken cancellationToken);
///
/// Apply syntax tree actions (that have not already been applied) to a document.
/// Calls for each
/// unique group of diagnostics, where a group is identified by analysis classification (syntax), document, and analyzer.
///
/// The document to analyze.
/// The reason(s) this analysis was triggered.
///
///
public abstract Task AnalyzeSyntaxAsync(Document document, InvocationReasons reasons, CancellationToken cancellationToken);
///
/// Respond to a document being opened for editing in the host.
///
/// The opened document.
///
///
public abstract Task DocumentOpenAsync(Document document, CancellationToken cancellationToken);
///
/// Respond to a document being closed in the host.
///
/// The closed document.
///
///
public abstract Task DocumentCloseAsync(Document document, CancellationToken cancellationToken);
///
/// Flush cached diagnostics produced by a prior analysis of a document.
/// This will be called in a case where we need to re-analyze a document without textual, syntactic or semantic change of a solution.
/// For example, engine or analyzer's option changes.
///
/// The document whose diagnostics are to be flushed.
///
///
public abstract Task DocumentResetAsync(Document document, CancellationToken cancellationToken);
///
/// ???
///
/// The solution.
///
///
public abstract Task NewSolutionSnapshotAsync(Solution solution, CancellationToken cancellationToken);
///
/// Flush diagnostics produced by a prior analysis of a document,
/// and suppress future analysis of the document.
/// Calls with an empty collection.
///
///
public abstract void RemoveDocument(DocumentId documentId);
///
/// Flush diagnostics produced by a prior analysis of a project,
/// and suppress future analysis of the project.
/// Calls with an empty collection.
///
///
public abstract void RemoveProject(ProjectId projectId);
#endregion
#region delegating methods from diagnostic analyzer service to each implementation of the engine
///
/// Get previously-computed (and potentially stale) diagnostics associated with a particular combination of
/// analysis classification (syntax/semantics/project), document/project, and analyzer.
///
/// The solution.
/// Matched against values supplied in a to .
/// Flag indicating whether diagnostics with source suppressions (pragma/SuppressMessageAttribute) should be included.
///
///
public abstract Task> GetSpecificCachedDiagnosticsAsync(Solution solution, object id, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken));
///
/// Get previously-computed (and potentially stale) diagnostics associated with a particular document, project, or solution.
///
/// The solution. If projectId and documentId are both null, returned diagnostics are for the entire solution.
/// If projectId is non null and documentId is null, returned diagnostics are for that project only.
/// If documentId is non null, returned diagnostics are for that document only.
/// Flag indicating whether diagnostics with source suppressions (pragma/SuppressMessageAttribute) should be included.
///
///
public abstract Task> GetCachedDiagnosticsAsync(Solution solution, ProjectId projectId = null, DocumentId documentId = null, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken));
///
/// Get diagnostics associated with a particular combination of
/// analysis classification (syntax/semantics/project), document/project, and analyzer.
///
/// The solution.
/// Matched against values supplied in a to .
/// Flag indicating whether diagnostics with source suppressions (pragma/SuppressMessageAttribute) should be included.
///
///
public abstract Task> GetSpecificDiagnosticsAsync(Solution solution, object id, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken));
///
/// Get diagnostics associated with a particular document, project, or solution.
///
/// The solution. If projectId and documentId are both null, returned diagnostics are for the entire solution.
/// If projectId is non null and documentId is null, returned diagnostics are for that project only.
/// If documentId is non null, returned diagnostics are for that document only.
/// Flag indicating whether diagnostics with source suppressions (pragma/SuppressMessageAttribute) should be included.
///
///
public abstract Task> GetDiagnosticsAsync(Solution solution, ProjectId projectId = null, DocumentId documentId = null, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken));
///
/// Get diagnostics matching one of a set of diagnostic IDs associated with a particular document, project, or solution.
///
/// The solution. If projectId and documentId are both null, returned diagnostics are for the entire solution.
/// If projectId is non null and documentId is null, returned diagnostics are for that project only.
/// If documentId is non null, returned diagnostics are for that document only.
/// The diagnostic IDs to match.
/// Flag indicating whether diagnostics with source suppressions (pragma/SuppressMessageAttribute) should be included.
///
///
public abstract Task> GetDiagnosticsForIdsAsync(Solution solution, ProjectId projectId = null, DocumentId documentId = null, ImmutableHashSet diagnosticIds = null, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken));
///
/// Get diagnostics matching one of a set of diagnostic IDs that are not associated with a particular document.
///
/// The solution. If projectId is null, returned diagnostics are for the entire solution.
/// If projectId is non null, returned diagnostics are for that project only.
/// The diagnostic IDs to match.
/// Flag indicating whether diagnostics with source suppressions (pragma/SuppressMessageAttribute) should be included.
///
///
public abstract Task> GetProjectDiagnosticsForIdsAsync(Solution solution, ProjectId projectId = null, ImmutableHashSet diagnosticIds = null, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken));
///
/// Add diagnostics local to a span to a list of diagnostics.
///
/// The document containing the span.
/// The span for which to produce diagnostics.
/// The list of diagnostics to be augmented.
/// Flag indicating whether diagnostics with source suppressions (pragma/SuppressMessageAttribute) should be included.
///
/// True if the set of results is complete, false if getting a complete set requires running per-document actions.
public abstract Task TryAppendDiagnosticsForSpanAsync(Document document, TextSpan range, List diagnostics, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken));
///
/// Get diagnostics local to a span.
///
/// The document containing the span.
/// The span for which to produce diagnostics.
/// Flag indicating whether diagnostics with source suppressions (pragma/SuppressMessageAttribute) should be included.
///
///
public abstract Task> GetDiagnosticsForSpanAsync(Document document, TextSpan range, bool includeSuppressedDiagnostics = false, CancellationToken cancellationToken = default(CancellationToken));
///
/// True if given project has any diagnostics
///
public abstract bool ContainsDiagnostics(Workspace workspace, ProjectId projectId);
#endregion
#region build error synchronization
///
/// Callback from build listener.
///
/// Given diagnostics are errors host got from explicit build.
/// It is up to each incremental analyzer how they will merge this information with live diagnostic info.
///
/// this API doesn't have cancellationToken since it can't be cancelled.
///
public abstract Task SynchronizeWithBuildAsync(Workspace workspace, ImmutableDictionary> diagnostics);
#endregion
internal DiagnosticAnalyzerService Owner { get; }
internal Workspace Workspace { get; }
internal AbstractHostDiagnosticUpdateSource HostDiagnosticUpdateSource { get; }
internal HostAnalyzerManager HostAnalyzerManager { get; }
internal DiagnosticLogAggregator DiagnosticLogAggregator { get; private set; }
protected void ResetDiagnosticLogAggregator()
{
DiagnosticLogAggregator = new DiagnosticLogAggregator(Owner);
}
public virtual bool NeedsReanalysisOnOptionChanged(object sender, OptionChangedEventArgs e)
{
return false;
}
public virtual void LogAnalyzerCountSummary()
{
}
// internal for testing purposes.
internal Action GetOnAnalyzerException(ProjectId projectId)
{
return Owner.GetOnAnalyzerException(projectId, DiagnosticLogAggregator);
}
protected static ReportDiagnostic GetEffectiveSeverity(DiagnosticDescriptor descriptor, CompilationOptions options)
{
return options == null
? MapSeverityToReport(descriptor.DefaultSeverity)
: descriptor.GetEffectiveSeverity(options);
}
protected static ReportDiagnostic MapSeverityToReport(DiagnosticSeverity severity)
{
switch (severity)
{
case DiagnosticSeverity.Hidden:
return ReportDiagnostic.Hidden;
case DiagnosticSeverity.Info:
return ReportDiagnostic.Info;
case DiagnosticSeverity.Warning:
return ReportDiagnostic.Warn;
case DiagnosticSeverity.Error:
return ReportDiagnostic.Error;
default:
throw ExceptionUtilities.Unreachable;
}
}
}
}