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