From f6d80fd161fbfd82b4743085bcc911c07d9373da Mon Sep 17 00:00:00 2001 From: Manish Vasani Date: Thu, 14 Oct 2021 20:02:52 -0700 Subject: [PATCH] BackgroundAnalysisScope enhancements (Part 1) Addresses first two parts of #57146 --- .../DiagnosticAnalyzerServiceTests.cs | 40 +++++++++++------ .../SolutionCrawler/WorkCoordinatorTests.cs | 31 ++++++++++++- .../DiagnosticIncrementalAnalyzer.Executor.cs | 44 ++++++++++++++++--- ...osticIncrementalAnalyzer_GetDiagnostics.cs | 5 +-- ...crementalAnalyzer_GetDiagnosticsForSpan.cs | 2 +- ...IncrementalAnalyzer_IncrementalAnalyzer.cs | 39 ++++------------ .../Portable/Workspace/BackgroundCompiler.cs | 4 +- .../Portable/Workspace/BackgroundParser.cs | 4 +- .../Options/AdvancedOptionPageControl.xaml | 3 ++ .../Options/AdvancedOptionPageControl.xaml.cs | 1 + .../Impl/Options/AdvancedOptionPageStrings.cs | 3 ++ src/VisualStudio/Core/Def/Commands.vsct | 11 +++++ .../Core/Def/ID.RoslynCommands.cs | 1 + .../VisualStudioDiagnosticAnalyzerService.cs | 13 +++++- .../Core/Def/ServicesVSResources.resx | 4 ++ .../Core/Def/xlf/Commands.vsct.cs.xlf | 15 +++++++ .../Core/Def/xlf/Commands.vsct.de.xlf | 15 +++++++ .../Core/Def/xlf/Commands.vsct.es.xlf | 15 +++++++ .../Core/Def/xlf/Commands.vsct.fr.xlf | 15 +++++++ .../Core/Def/xlf/Commands.vsct.it.xlf | 15 +++++++ .../Core/Def/xlf/Commands.vsct.ja.xlf | 15 +++++++ .../Core/Def/xlf/Commands.vsct.ko.xlf | 15 +++++++ .../Core/Def/xlf/Commands.vsct.pl.xlf | 15 +++++++ .../Core/Def/xlf/Commands.vsct.pt-BR.xlf | 15 +++++++ .../Core/Def/xlf/Commands.vsct.ru.xlf | 15 +++++++ .../Core/Def/xlf/Commands.vsct.tr.xlf | 15 +++++++ .../Core/Def/xlf/Commands.vsct.zh-Hans.xlf | 15 +++++++ .../Core/Def/xlf/Commands.vsct.zh-Hant.xlf | 15 +++++++ .../Core/Def/xlf/ServicesVSResources.cs.xlf | 5 +++ .../Core/Def/xlf/ServicesVSResources.de.xlf | 5 +++ .../Core/Def/xlf/ServicesVSResources.es.xlf | 5 +++ .../Core/Def/xlf/ServicesVSResources.fr.xlf | 5 +++ .../Core/Def/xlf/ServicesVSResources.it.xlf | 5 +++ .../Core/Def/xlf/ServicesVSResources.ja.xlf | 5 +++ .../Core/Def/xlf/ServicesVSResources.ko.xlf | 5 +++ .../Core/Def/xlf/ServicesVSResources.pl.xlf | 5 +++ .../Def/xlf/ServicesVSResources.pt-BR.xlf | 5 +++ .../Core/Def/xlf/ServicesVSResources.ru.xlf | 5 +++ .../Core/Def/xlf/ServicesVSResources.tr.xlf | 5 +++ .../Def/xlf/ServicesVSResources.zh-Hans.xlf | 5 +++ .../Def/xlf/ServicesVSResources.zh-Hant.xlf | 5 +++ .../Options/AdvancedOptionPageControl.xaml | 3 ++ .../Options/AdvancedOptionPageControl.xaml.vb | 1 + .../Impl/Options/AdvancedOptionPageStrings.vb | 3 ++ .../DiagnosticAnalyzerInfoCache.cs | 9 ++-- .../BackgroundAnalysisScope.cs | 23 +++++++++- 46 files changed, 439 insertions(+), 65 deletions(-) diff --git a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs index b1a8a2ae99378..6c3b7dc4abbd0 100644 --- a/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs +++ b/src/EditorFeatures/Test/Diagnostics/DiagnosticAnalyzerServiceTests.cs @@ -652,6 +652,7 @@ internal async Task TestAdditionalFileAnalyzer(bool registerFromInitialize, bool switch (analysisScope) { + case BackgroundAnalysisScope.None: case BackgroundAnalysisScope.ActiveFile: case BackgroundAnalysisScope.OpenFilesAndProjects: workspace.OpenAdditionalDocument(firstAdditionalDocument.Id); @@ -670,7 +671,7 @@ internal async Task TestAdditionalFileAnalyzer(bool registerFromInitialize, bool var expectedCount = (analysisScope, testMultiple) switch { - (BackgroundAnalysisScope.ActiveFile, _) => 0, + (BackgroundAnalysisScope.ActiveFile or BackgroundAnalysisScope.None, _) => 0, (BackgroundAnalysisScope.OpenFilesAndProjects or BackgroundAnalysisScope.FullSolution, false) => 1, (BackgroundAnalysisScope.OpenFilesAndProjects, true) => 2, (BackgroundAnalysisScope.FullSolution, true) => 4, @@ -687,7 +688,7 @@ internal async Task TestAdditionalFileAnalyzer(bool registerFromInitialize, bool var applicableDiagnostics = diagnostics.Where( d => d.Id == analyzer.Descriptor.Id && d.DataLocation.OriginalFilePath == additionalDoc.FilePath); - if (analysisScope == BackgroundAnalysisScope.ActiveFile) + if (analysisScope is BackgroundAnalysisScope.ActiveFile or BackgroundAnalysisScope.None) { Assert.Empty(applicableDiagnostics); } @@ -761,6 +762,7 @@ internal async Task TestDiagnosticSuppressor(bool includeAnalyzer, bool includeS switch (analysisScope) { + case BackgroundAnalysisScope.None: case BackgroundAnalysisScope.ActiveFile: workspace.OpenDocument(document.Id); var documentTrackingService = (TestDocumentTrackingService)workspace.Services.GetService(); @@ -776,11 +778,14 @@ internal async Task TestDiagnosticSuppressor(bool includeAnalyzer, bool includeS case BackgroundAnalysisScope.FullSolution: await incrementalAnalyzer.AnalyzeProjectAsync(project, semanticsChanged: true, InvocationReasons.Reanalyze, CancellationToken.None); break; + + default: + throw ExceptionUtilities.UnexpectedValue(analysisScope); } await ((AsynchronousOperationListener)service.Listener).ExpeditedWaitAsync(); - if (includeAnalyzer) + if (includeAnalyzer && analysisScope != BackgroundAnalysisScope.None) { Assert.True(diagnostic != null); Assert.Equal(NamedTypeAnalyzer.DiagnosticId, diagnostic.Id); @@ -863,6 +868,7 @@ void M() switch (analysisScope) { + case BackgroundAnalysisScope.None: case BackgroundAnalysisScope.ActiveFile: workspace.OpenDocument(document.Id); var documentTrackingService = (TestDocumentTrackingService)workspace.Services.GetRequiredService(); @@ -882,21 +888,29 @@ void M() await ((AsynchronousOperationListener)service.Listener).ExpeditedWaitAsync(); - Assert.Equal(2, diagnostics.Count); var root = await document.GetSyntaxRootAsync(); - if (testPragma) + if (analysisScope == BackgroundAnalysisScope.None) { - var pragma1 = root.FindTrivia(diagnostics[0].GetTextSpan().Start).ToString(); - Assert.Equal($"#pragma warning disable {NamedTypeAnalyzer.DiagnosticId} // Unnecessary", pragma1); - var pragma2 = root.FindTrivia(diagnostics[1].GetTextSpan().Start).ToString(); - Assert.Equal($"#pragma warning disable CS0168 // Variable is declared but never used - Unnecessary", pragma2); + // Anayzers are disabled for BackgroundAnalysisScope.None. + Assert.Empty(diagnostics); } else { - var attribute1 = root.FindNode(diagnostics[0].GetTextSpan()).ToString(); - Assert.Equal($@"System.Diagnostics.CodeAnalysis.SuppressMessage(""Category2"", ""{NamedTypeAnalyzer.DiagnosticId}"")", attribute1); - var attribute2 = root.FindNode(diagnostics[1].GetTextSpan()).ToString(); - Assert.Equal($@"System.Diagnostics.CodeAnalysis.SuppressMessage(""Category3"", ""CS0168"")", attribute2); + Assert.Equal(2, diagnostics.Count); + if (testPragma) + { + var pragma1 = root.FindTrivia(diagnostics[0].GetTextSpan().Start).ToString(); + Assert.Equal($"#pragma warning disable {NamedTypeAnalyzer.DiagnosticId} // Unnecessary", pragma1); + var pragma2 = root.FindTrivia(diagnostics[1].GetTextSpan().Start).ToString(); + Assert.Equal($"#pragma warning disable CS0168 // Variable is declared but never used - Unnecessary", pragma2); + } + else + { + var attribute1 = root.FindNode(diagnostics[0].GetTextSpan()).ToString(); + Assert.Equal($@"System.Diagnostics.CodeAnalysis.SuppressMessage(""Category2"", ""{NamedTypeAnalyzer.DiagnosticId}"")", attribute1); + var attribute2 = root.FindNode(diagnostics[1].GetTextSpan()).ToString(); + Assert.Equal($@"System.Diagnostics.CodeAnalysis.SuppressMessage(""Category3"", ""CS0168"")", attribute2); + } } } diff --git a/src/EditorFeatures/Test/SolutionCrawler/WorkCoordinatorTests.cs b/src/EditorFeatures/Test/SolutionCrawler/WorkCoordinatorTests.cs index 2bb8424b5110d..77a76fd5a6a75 100644 --- a/src/EditorFeatures/Test/SolutionCrawler/WorkCoordinatorTests.cs +++ b/src/EditorFeatures/Test/SolutionCrawler/WorkCoordinatorTests.cs @@ -78,6 +78,7 @@ public async Task DynamicallyAddAnalyzer() Assert.Equal(10, worker.DocumentIds.Count); } + [InlineData(BackgroundAnalysisScope.None)] [InlineData(BackgroundAnalysisScope.ActiveFile)] [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects)] [InlineData(BackgroundAnalysisScope.FullSolution)] @@ -105,6 +106,7 @@ internal async Task SolutionAdded_Simple(BackgroundAnalysisScope analysisScope) Assert.Equal(expectedDocumentEvents, worker.SyntaxDocumentIds.Count); } + [InlineData(BackgroundAnalysisScope.None)] [InlineData(BackgroundAnalysisScope.ActiveFile)] [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects)] [InlineData(BackgroundAnalysisScope.FullSolution)] @@ -120,6 +122,7 @@ internal async Task SolutionAdded_Complex(BackgroundAnalysisScope analysisScope) Assert.Equal(expectedDocumentEvents, worker.SyntaxDocumentIds.Count); } + [InlineData(BackgroundAnalysisScope.None)] [InlineData(BackgroundAnalysisScope.ActiveFile)] [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects)] [InlineData(BackgroundAnalysisScope.FullSolution)] @@ -135,6 +138,7 @@ internal async Task Solution_Remove(BackgroundAnalysisScope analysisScope) Assert.Equal(10, worker.InvalidateDocumentIds.Count); } + [InlineData(BackgroundAnalysisScope.None)] [InlineData(BackgroundAnalysisScope.ActiveFile)] [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects)] [InlineData(BackgroundAnalysisScope.FullSolution)] @@ -150,6 +154,7 @@ internal async Task Solution_Clear(BackgroundAnalysisScope analysisScope) Assert.Equal(10, worker.InvalidateDocumentIds.Count); } + [InlineData(BackgroundAnalysisScope.None)] [InlineData(BackgroundAnalysisScope.ActiveFile)] [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects)] [InlineData(BackgroundAnalysisScope.FullSolution)] @@ -169,6 +174,7 @@ internal async Task Solution_Reload(BackgroundAnalysisScope analysisScope) Assert.Equal(expectedProjectEvents, worker.ProjectIds.Count); } + [InlineData(BackgroundAnalysisScope.None)] [InlineData(BackgroundAnalysisScope.ActiveFile)] [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects)] [InlineData(BackgroundAnalysisScope.FullSolution)] @@ -192,6 +198,7 @@ internal async Task Solution_Change(BackgroundAnalysisScope analysisScope) Assert.Equal(expectedDocumentEvents, worker.SyntaxDocumentIds.Count); } + [InlineData(BackgroundAnalysisScope.None)] [InlineData(BackgroundAnalysisScope.ActiveFile)] [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects)] [InlineData(BackgroundAnalysisScope.FullSolution)] @@ -218,6 +225,7 @@ internal async Task Project_Add(BackgroundAnalysisScope analysisScope) Assert.Equal(expectedDocumentEvents, worker.SyntaxDocumentIds.Count); } + [InlineData(BackgroundAnalysisScope.None)] [InlineData(BackgroundAnalysisScope.ActiveFile)] [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects)] [InlineData(BackgroundAnalysisScope.FullSolution)] @@ -236,6 +244,7 @@ internal async Task Project_Remove(BackgroundAnalysisScope analysisScope) Assert.Equal(5, worker.InvalidateDocumentIds.Count); } + [InlineData(BackgroundAnalysisScope.None)] [InlineData(BackgroundAnalysisScope.ActiveFile)] [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects)] [InlineData(BackgroundAnalysisScope.FullSolution)] @@ -256,6 +265,7 @@ internal async Task Project_Change(BackgroundAnalysisScope analysisScope) Assert.Equal(1, worker.InvalidateDocumentIds.Count); } + [InlineData(BackgroundAnalysisScope.None, false)] [InlineData(BackgroundAnalysisScope.ActiveFile, false)] [InlineData(BackgroundAnalysisScope.ActiveFile, true)] [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false)] @@ -283,6 +293,7 @@ internal async Task Project_AssemblyName_Change(BackgroundAnalysisScope analysis Assert.Equal(expectedDocumentEvents, worker.DocumentIds.Count); } + [InlineData(BackgroundAnalysisScope.None, false)] [InlineData(BackgroundAnalysisScope.ActiveFile, false)] [InlineData(BackgroundAnalysisScope.ActiveFile, true)] [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false)] @@ -310,6 +321,7 @@ internal async Task Project_DefaultNamespace_Change(BackgroundAnalysisScope anal Assert.Equal(expectedDocumentEvents, worker.DocumentIds.Count); } + [InlineData(BackgroundAnalysisScope.None, false)] [InlineData(BackgroundAnalysisScope.ActiveFile, false)] [InlineData(BackgroundAnalysisScope.ActiveFile, true)] [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false)] @@ -337,6 +349,7 @@ internal async Task Project_AnalyzerOptions_Change(BackgroundAnalysisScope analy Assert.Equal(expectedDocumentEvents, worker.DocumentIds.Count); } + [InlineData(BackgroundAnalysisScope.None, false)] [InlineData(BackgroundAnalysisScope.ActiveFile, false)] [InlineData(BackgroundAnalysisScope.ActiveFile, true)] [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false)] @@ -364,6 +377,7 @@ internal async Task Project_OutputFilePath_Change(BackgroundAnalysisScope analys Assert.Equal(expectedDocumentEvents, worker.DocumentIds.Count); } + [InlineData(BackgroundAnalysisScope.None, false)] [InlineData(BackgroundAnalysisScope.ActiveFile, false)] [InlineData(BackgroundAnalysisScope.ActiveFile, true)] [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false)] @@ -391,6 +405,7 @@ internal async Task Project_OutputRefFilePath_Change(BackgroundAnalysisScope ana Assert.Equal(expectedDocumentEvents, worker.DocumentIds.Count); } + [InlineData(BackgroundAnalysisScope.None, false)] [InlineData(BackgroundAnalysisScope.ActiveFile, false)] [InlineData(BackgroundAnalysisScope.ActiveFile, true)] [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false)] @@ -418,6 +433,7 @@ internal async Task Project_CompilationOutputInfo_Change(BackgroundAnalysisScope Assert.Equal(expectedDocumentEvents, worker.DocumentIds.Count); } + [InlineData(BackgroundAnalysisScope.None, false)] [InlineData(BackgroundAnalysisScope.ActiveFile, false)] [InlineData(BackgroundAnalysisScope.ActiveFile, true)] [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false)] @@ -504,6 +520,7 @@ public async Task Test_BackgroundAnalysisScopeOptionChanged_FullSolution() Assert.Equal(2, worker.ProjectIds.Count); } + [InlineData(BackgroundAnalysisScope.None)] [InlineData(BackgroundAnalysisScope.ActiveFile)] [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects)] [InlineData(BackgroundAnalysisScope.FullSolution)] @@ -524,6 +541,7 @@ internal async Task Project_Reload(BackgroundAnalysisScope analysisScope) Assert.Equal(expectedProjectEvents, worker.ProjectIds.Count); } + [InlineData(BackgroundAnalysisScope.None, false)] [InlineData(BackgroundAnalysisScope.ActiveFile, false)] [InlineData(BackgroundAnalysisScope.ActiveFile, true)] [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false)] @@ -557,6 +575,7 @@ internal async Task Document_Add(BackgroundAnalysisScope analysisScope, bool act Assert.Equal(expectedDocumentSemanticEvents, worker.DocumentIds.Count); } + [InlineData(BackgroundAnalysisScope.None, false)] [InlineData(BackgroundAnalysisScope.ActiveFile, false)] [InlineData(BackgroundAnalysisScope.ActiveFile, true)] [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false)] @@ -586,6 +605,7 @@ internal async Task Document_Remove(BackgroundAnalysisScope analysisScope, bool Assert.Equal(expectedDocumentInvalidatedEvents, worker.InvalidateDocumentIds.Count); } + [InlineData(BackgroundAnalysisScope.None, false)] [InlineData(BackgroundAnalysisScope.ActiveFile, false)] [InlineData(BackgroundAnalysisScope.ActiveFile, true)] [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false)] @@ -611,6 +631,7 @@ internal async Task Document_Reload(BackgroundAnalysisScope analysisScope, bool Assert.Equal(0, worker.InvalidateDocumentIds.Count); } + [InlineData(BackgroundAnalysisScope.None, false)] [InlineData(BackgroundAnalysisScope.ActiveFile, false)] [InlineData(BackgroundAnalysisScope.ActiveFile, true)] [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false)] @@ -656,6 +677,7 @@ internal async Task Document_Reanalyze(BackgroundAnalysisScope analysisScope, bo Assert.Equal(expectedReanalyzeDocumentCount, worker.DocumentIds.Count); } + [InlineData(BackgroundAnalysisScope.None, false)] [InlineData(BackgroundAnalysisScope.ActiveFile, false)] [InlineData(BackgroundAnalysisScope.ActiveFile, true)] [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false)] @@ -681,6 +703,7 @@ internal async Task Document_Change(BackgroundAnalysisScope analysisScope, bool Assert.Equal(expectedDocumentEvents, worker.SyntaxDocumentIds.Count); } + [InlineData(BackgroundAnalysisScope.None, false)] [InlineData(BackgroundAnalysisScope.ActiveFile, false)] [InlineData(BackgroundAnalysisScope.ActiveFile, true)] [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false)] @@ -719,6 +742,7 @@ internal async Task Document_AdditionalFileChange(BackgroundAnalysisScope analys Assert.Equal(expectedDocumentSemanticEvents, worker.DocumentIds.Count); } + [InlineData(BackgroundAnalysisScope.None, false)] [InlineData(BackgroundAnalysisScope.ActiveFile, false)] [InlineData(BackgroundAnalysisScope.ActiveFile, true)] [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false)] @@ -758,6 +782,7 @@ internal async Task Document_AnalyzerConfigFileChange(BackgroundAnalysisScope an Assert.Equal(expectedDocumentSemanticEvents, worker.DocumentIds.Count); } + [InlineData(BackgroundAnalysisScope.None, false)] [InlineData(BackgroundAnalysisScope.ActiveFile, false)] [InlineData(BackgroundAnalysisScope.ActiveFile, true)] [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false)] @@ -812,6 +837,7 @@ internal async Task Document_Cancellation(BackgroundAnalysisScope analysisScope, Assert.Equal(expectedDocumentSemanticEvents, analyzer.DocumentIds.Count); } + [InlineData(BackgroundAnalysisScope.None, false)] [InlineData(BackgroundAnalysisScope.ActiveFile, false)] [InlineData(BackgroundAnalysisScope.ActiveFile, true)] [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false)] @@ -919,6 +945,7 @@ public async Task Document_InvocationReasons() Assert.Equal(5, analyzer.DocumentIds.Count); } + [InlineData(BackgroundAnalysisScope.None, false)] [InlineData(BackgroundAnalysisScope.ActiveFile, false)] [InlineData(BackgroundAnalysisScope.ActiveFile, true)] [InlineData(BackgroundAnalysisScope.OpenFilesAndProjects, false)] @@ -943,14 +970,14 @@ internal async Task Document_ActiveDocumentChanged(BackgroundAnalysisScope analy var expectedSyntaxDocumentEvents = (analysisScope, hasActiveDocumentBefore) switch { (BackgroundAnalysisScope.ActiveFile, _) => 1, - (BackgroundAnalysisScope.OpenFilesAndProjects or BackgroundAnalysisScope.FullSolution, _) => 0, + (BackgroundAnalysisScope.OpenFilesAndProjects or BackgroundAnalysisScope.FullSolution or BackgroundAnalysisScope.None, _) => 0, _ => throw ExceptionUtilities.Unreachable, }; var expectedDocumentEvents = (analysisScope, hasActiveDocumentBefore) switch { (BackgroundAnalysisScope.ActiveFile, _) => 5, - (BackgroundAnalysisScope.OpenFilesAndProjects or BackgroundAnalysisScope.FullSolution, _) => 0, + (BackgroundAnalysisScope.OpenFilesAndProjects or BackgroundAnalysisScope.FullSolution or BackgroundAnalysisScope.None, _) => 0, _ => throw ExceptionUtilities.Unreachable, }; diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs index f12d8156595e4..95ce9c98a4db6 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer.Executor.cs @@ -25,12 +25,15 @@ internal partial class DiagnosticIncrementalAnalyzer /// Also returns empty diagnostics for suppressed analyzer. /// Returns null if the diagnostics need to be computed. /// - private async Task TryGetCachedDocumentAnalysisDataAsync( - TextDocument document, StateSet stateSet, AnalysisKind kind, CancellationToken cancellationToken) + private static DocumentAnalysisData? TryGetCachedDocumentAnalysisData( + TextDocument document, StateSet stateSet, + AnalysisKind kind, VersionStamp version, + BackgroundAnalysisScope analysisScope, bool isActiveDocument, + bool isOpenDocument, bool isGeneratedRazorDocument, + CancellationToken cancellationToken) { try { - var version = await GetDiagnosticVersionAsync(document.Project, cancellationToken).ConfigureAwait(false); var state = stateSet.GetOrCreateActiveFileState(document.Id); var existingData = state.GetAnalysisData(kind); @@ -39,8 +42,9 @@ internal partial class DiagnosticIncrementalAnalyzer return existingData; } - // Perf optimization: Check whether analyzer is suppressed and avoid getting diagnostics if suppressed. - if (DiagnosticAnalyzerInfoCache.IsAnalyzerSuppressed(stateSet.Analyzer, document.Project)) + // Perf optimization: Check whether analyzer is suppressed for project or document and avoid getting diagnostics if suppressed. + if (DiagnosticAnalyzerInfoCache.IsAnalyzerSuppressedForProject(stateSet.Analyzer, document.Project) || + IsAnalyzerSuppressedForDocument(stateSet.Analyzer, analysisScope, isActiveDocument, isOpenDocument, isGeneratedRazorDocument)) { return new DocumentAnalysisData(version, existingData.Items, ImmutableArray.Empty); } @@ -51,6 +55,36 @@ internal partial class DiagnosticIncrementalAnalyzer { throw ExceptionUtilities.Unreachable; } + + static bool IsAnalyzerSuppressedForDocument( + DiagnosticAnalyzer analyzer, + BackgroundAnalysisScope analysisScope, + bool isActiveDocument, + bool isOpenDocument, + bool isGeneratedRazorDocument) + { + if (isGeneratedRazorDocument) + { + // This is a generated Razor document, and they always want all analyzer diagnostics. + return false; + } + + var analyzerEnabled = analysisScope switch + { + // Analyzers are only enabled for active document. + // Compiler analyzer is special and enabled for all open documents. + BackgroundAnalysisScope.ActiveFile => analyzer.IsCompilerAnalyzer() ? isOpenDocument : isActiveDocument, + + // Analyzers are disabled for all documents. + // Compiler analyzer is special and enabled for all open documents. + BackgroundAnalysisScope.None => isOpenDocument && analyzer.IsCompilerAnalyzer(), + + // All analyzers, including compiler analyzer, are enabled for all open documents. + _ => isOpenDocument, + }; + + return !analyzerEnabled; + } } /// diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs index 7ede412128fb9..62d0a8cc16464 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnostics.cs @@ -281,13 +281,12 @@ protected override async Task AppendDiagnosticsAsync(Project project, IEnumerabl private bool ShouldIncludeStateSet(Project project, StateSet stateSet) { - var infoCache = Owner.DiagnosticAnalyzerInfoCache; - if (infoCache.IsAnalyzerSuppressed(stateSet.Analyzer, project)) + if (DiagnosticAnalyzerInfoCache.IsAnalyzerSuppressedForProject(stateSet.Analyzer, project)) { return false; } - if (_diagnosticIds != null && infoCache.GetDiagnosticDescriptors(stateSet.Analyzer).All(d => !_diagnosticIds.Contains(d.Id))) + if (_diagnosticIds != null && Owner.DiagnosticAnalyzerInfoCache.GetDiagnosticDescriptors(stateSet.Analyzer).All(d => !_diagnosticIds.Contains(d.Id))) { return false; } diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs index e08463c9f9d7c..83c6d83448bc0 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_GetDiagnosticsForSpan.cs @@ -81,7 +81,7 @@ public static async Task CreateAsync( CancellationToken cancellationToken) { var stateSets = owner._stateManager - .GetOrCreateStateSets(document.Project).Where(s => !owner.DiagnosticAnalyzerInfoCache.IsAnalyzerSuppressed(s.Analyzer, document.Project)); + .GetOrCreateStateSets(document.Project).Where(s => !DiagnosticAnalyzerInfoCache.IsAnalyzerSuppressedForProject(s.Analyzer, document.Project)); // filter to specific diagnostic it is looking for if (diagnosticId != null) diff --git a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs index f775b51e9b8ea..75368fcd4339d 100644 --- a/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs +++ b/src/Features/Core/Portable/Diagnostics/EngineV2/DiagnosticIncrementalAnalyzer_IncrementalAnalyzer.cs @@ -35,15 +35,18 @@ private async Task AnalyzeDocumentForKindAsync(TextDocument document, AnalysisKi { try { - if (!AnalysisEnabled(document, _documentTrackingService)) + if (!document.SupportsDiagnostics()) { - // to reduce allocations, here, we don't clear existing diagnostics since it is dealt by other entry point such as - // DocumentReset or DocumentClosed. 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); + var isActiveDocument = _documentTrackingService.TryGetActiveDocument() == document.Id; + var isOpenDocument = document.IsOpen(); + var isGeneratedRazorDocument = document.Services.GetService()?.DiagnosticsLspClientName != null; // 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. @@ -56,7 +59,8 @@ private async Task AnalyzeDocumentForKindAsync(TextDocument document, AnalysisKi using var _ = ArrayBuilder.GetInstance(out var nonCachedStateSets); foreach (var stateSet in stateSets) { - var data = await TryGetCachedDocumentAnalysisDataAsync(document, stateSet, kind, cancellationToken).ConfigureAwait(false); + 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. @@ -132,7 +136,7 @@ private async Task AnalyzeProjectAsync(Project project, bool forceAnalyzerRun, C // this is perf optimization. we cache these result since we know the result. (no diagnostics) var activeAnalyzers = stateSets .Select(s => s.Analyzer) - .Where(a => !DiagnosticAnalyzerInfoCache.IsAnalyzerSuppressed(a, project) && !a.IsOpenFileOnly(options)); + .Where(a => !DiagnosticAnalyzerInfoCache.IsAnalyzerSuppressedForProject(a, project) && !a.IsOpenFileOnly(options)); // get driver only with active analyzers. var compilationWithAnalyzers = await AnalyzerHelper.CreateCompilationWithAnalyzersAsync(project, activeAnalyzers, includeSuppressedDiagnostics: true, cancellationToken).ConfigureAwait(false); @@ -330,31 +334,6 @@ public Task NewSolutionSnapshotAsync(Solution solution, CancellationToken cancel return Task.CompletedTask; } - private static bool AnalysisEnabled(TextDocument document, IDocumentTrackingService documentTrackingService) - { - if (document.Services.GetService()?.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(); - } - } - /// /// Return list of to be used for full solution analysis. /// diff --git a/src/Features/Core/Portable/Workspace/BackgroundCompiler.cs b/src/Features/Core/Portable/Workspace/BackgroundCompiler.cs index 304bd7e15bc61..cad92c0a1fecf 100644 --- a/src/Features/Core/Portable/Workspace/BackgroundCompiler.cs +++ b/src/Features/Core/Portable/Workspace/BackgroundCompiler.cs @@ -163,10 +163,10 @@ private Task BuildCompilationsAsync( if (p is null) return null; - if (SolutionCrawlerOptions.GetBackgroundAnalysisScope(p) == BackgroundAnalysisScope.ActiveFile + if (SolutionCrawlerOptions.GetBackgroundAnalysisScope(p) is BackgroundAnalysisScope.ActiveFile or BackgroundAnalysisScope.None && p.Id != activeProject) { - // For open files with Active File analysis scope, only build the compilation if the project is + // For open files with Active File or None analysis scope, only build the compilation if the project is // active. return null; } diff --git a/src/Features/Core/Portable/Workspace/BackgroundParser.cs b/src/Features/Core/Portable/Workspace/BackgroundParser.cs index f39ab27ce372d..801c4a328fcba 100644 --- a/src/Features/Core/Portable/Workspace/BackgroundParser.cs +++ b/src/Features/Core/Portable/Workspace/BackgroundParser.cs @@ -208,10 +208,10 @@ private Task ParseDocumentAsync(Document document) { try { - if (SolutionCrawlerOptions.GetBackgroundAnalysisScope(document.Project) == BackgroundAnalysisScope.ActiveFile + if (SolutionCrawlerOptions.GetBackgroundAnalysisScope(document.Project) is BackgroundAnalysisScope.ActiveFile or BackgroundAnalysisScope.None && _documentTrackingService?.TryGetActiveDocument() != document.Id) { - // Active file analysis is enabled, but the document for parsing is not the current + // Active file or None analysis scope is enabled, but the document for parsing is not the current // document. Return immediately without parsing. return; } diff --git a/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml b/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml index 2d729d3f15e84..6e777b9fd2ced 100644 --- a/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml +++ b/src/VisualStudio/CSharp/Impl/Options/AdvancedOptionPageControl.xaml @@ -17,6 +17,9 @@ - public bool IsAnalyzerSuppressed(DiagnosticAnalyzer analyzer, Project project) + public static bool IsAnalyzerSuppressedForProject(DiagnosticAnalyzer analyzer, Project project) { var options = project.CompilationOptions; if (options == null || analyzer == FileContentLoadAnalyzer.Instance || analyzer.IsCompilerAnalyzer()) @@ -121,10 +122,8 @@ public bool IsAnalyzerSuppressed(DiagnosticAnalyzer analyzer, Project project) return false; } - // If user has disabled analyzer execution for this project, we only want to execute required analyzers - // that report diagnostics with category "Compiler". - if (!project.State.RunAnalyzers && - GetDiagnosticDescriptors(analyzer).All(d => d.Category != DiagnosticCategory.Compiler)) + // Check if user has disabled analyzer execution for this project or via options. + if (!project.State.RunAnalyzers || SolutionCrawlerOptions.GetBackgroundAnalysisScope(project) == BackgroundAnalysisScope.None) { return true; } diff --git a/src/Workspaces/Core/Portable/SolutionCrawler/BackgroundAnalysisScope.cs b/src/Workspaces/Core/Portable/SolutionCrawler/BackgroundAnalysisScope.cs index 9142b9054056b..502986c7f574a 100644 --- a/src/Workspaces/Core/Portable/SolutionCrawler/BackgroundAnalysisScope.cs +++ b/src/Workspaces/Core/Portable/SolutionCrawler/BackgroundAnalysisScope.cs @@ -6,11 +6,32 @@ namespace Microsoft.CodeAnalysis.SolutionCrawler { internal enum BackgroundAnalysisScope { + // NOTE: Do not change existing field ordering/values as this scope is saved as a user option, + // and that would break users who have saved a non-default scope for the option. + + /// + /// Analyzers are executed only for currently active document. + /// Compiler analyzer is treated specially and executed for all open documents. + /// ActiveFile, + + /// + /// All analyzers, including compiler analyzer, are executed for all open documents. + /// OpenFilesAndProjects, + + /// + /// All analyzers, including compiler analyzer, are executed for all documents in the current solution. + /// FullSolution, - Minimal = ActiveFile, + /// + /// Analyzers are disabled for all documents. + /// Compiler analyzer is treated specially and executed for all open documents. + /// + None, + + Minimal = None, Default = OpenFilesAndProjects, } }