From dd5bbc98c69f68693a10a714edc6eae60c410324 Mon Sep 17 00:00:00 2001 From: Todd Grunke Date: Thu, 20 Jun 2024 11:39:15 -0700 Subject: [PATCH 1/2] Avoid re-running all codeaction requests at low priority. The ICodeActionRequestPriorityProviderExtensions.MatchesPriority method which takes in a CodeFixProvider determines whether a CodeFixProvider is run at a certain priority. This method previously always returned true at low priority, indicating that all CodeFixProviders should be run during the low priority calculations (a 2nd time for all Default/High providers). The reason it did this was to support de-prioritization of slow analyzers. Instead of running all codeFixProviders during low priority, we instead run 1) CodeFixProviders who's RequestPriority is low 2) CodeFixProviders who have an entry in their FixableDiagnosticIds that matches an entry from SupportedDiagnostics of a de-prioritized analyzer. --- .../SuggestedActionPriorityProvider.cs | 25 ++++++++++- .../Suggestions/SuggestedActionsSource.cs | 3 +- .../SuggestedActionsSource_Async.cs | 3 +- .../Test/CodeFixes/CodeFixServiceTests.cs | 5 ++- .../CodeActionRequestPriorityProvider.cs | 44 +++++++++++++++++-- .../Features/CodeFixes/CodeFixService.cs | 15 ++++--- 6 files changed, 80 insertions(+), 15 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionPriorityProvider.cs b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionPriorityProvider.cs index 130f2123ffd70..6d711e848cd96 100644 --- a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionPriorityProvider.cs +++ b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionPriorityProvider.cs @@ -2,6 +2,7 @@ // 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.Collections.Immutable; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.Diagnostics; using Roslyn.Utilities; @@ -12,16 +13,36 @@ namespace Microsoft.CodeAnalysis.Editor.Implementation.Suggestions; /// Set of de-prioritized analyzers that were moved down from 'Normal' to 'Low' priority bucket. Note that this set is /// owned by the and shared across priority buckets. /// +/// +/// Combined set of supported diagnostic ids from all de-prioritized analyzers +/// internal sealed class SuggestedActionPriorityProvider( CodeActionRequestPriority priority, - ConcurrentSet lowPriorityAnalyzers) + ConcurrentSet lowPriorityAnalyzers, + ConcurrentSet lowPriorityAnalyzerSupportedDiagnosticIds) : ICodeActionRequestPriorityProvider { public CodeActionRequestPriority? Priority { get; } = priority; public void AddDeprioritizedAnalyzerWithLowPriority(DiagnosticAnalyzer analyzer) - => lowPriorityAnalyzers.Add(analyzer); + { + lowPriorityAnalyzers.Add(analyzer); + + foreach (var supportedDiagnostic in analyzer.SupportedDiagnostics) + lowPriorityAnalyzerSupportedDiagnosticIds.Add(supportedDiagnostic.Id); + } public bool IsDeprioritizedAnalyzerWithLowPriority(DiagnosticAnalyzer analyzer) => lowPriorityAnalyzers.Contains(analyzer); + + public bool HasDeprioritizedAnalyzerSupportingDiagnosticId(ImmutableArray diagnosticIds) + { + foreach (var diagnosticId in diagnosticIds) + { + if (lowPriorityAnalyzerSupportedDiagnosticIds.Contains(diagnosticId)) + return true; + } + + return false; + } } diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs index be9ddb850fc69..a632a466ea7e0 100644 --- a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs +++ b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs @@ -234,12 +234,13 @@ private void OnTextViewClosed(object sender, EventArgs e) return null; var lowPriorityAnalyzers = new ConcurrentSet(); + var lowPriorityAnalyzerSupportedDiagnosticIds = new ConcurrentSet(); foreach (var order in Orderings) { var priority = TryGetPriority(order); Contract.ThrowIfNull(priority); - var priorityProvider = new SuggestedActionPriorityProvider(priority.Value, lowPriorityAnalyzers); + var priorityProvider = new SuggestedActionPriorityProvider(priority.Value, lowPriorityAnalyzers, lowPriorityAnalyzerSupportedDiagnosticIds); var result = await GetFixCategoryAsync(priorityProvider).ConfigureAwait(false); if (result != null) diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource_Async.cs b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource_Async.cs index a3c88ca4957a2..2c4fcbe94ba97 100644 --- a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource_Async.cs +++ b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource_Async.cs @@ -112,6 +112,7 @@ private async Task GetSuggestedActionsWorkerAsync( // keep track of this *across* calls to each priority. So we create this set outside of the loop and // then pass it continuously from one priority group to the next. var lowPriorityAnalyzers = new ConcurrentSet(); + var lowPriorityAnalyzerSupportedDiagnosticIds = new ConcurrentSet(); using var _2 = TelemetryLogging.LogBlockTimeAggregated(FunctionId.SuggestedAction_Summary, $"Total"); @@ -125,7 +126,7 @@ private async Task GetSuggestedActionsWorkerAsync( var allSets = GetCodeFixesAndRefactoringsAsync( state, requestedActionCategories, document, range, selection, - new SuggestedActionPriorityProvider(priority, lowPriorityAnalyzers), + new SuggestedActionPriorityProvider(priority, lowPriorityAnalyzers, lowPriorityAnalyzerSupportedDiagnosticIds), currentActionCount, cancellationToken).WithCancellation(cancellationToken).ConfigureAwait(false); await foreach (var set in allSets) diff --git a/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs b/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs index 2963fe10fcf4f..7d8d959d9a45a 100644 --- a/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs +++ b/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs @@ -1081,9 +1081,10 @@ await diagnosticIncrementalAnalyzer.GetDiagnosticsForIdsAsync( await diagnosticIncrementalAnalyzer.GetTestAccessor().TextDocumentOpenAsync(sourceDocument); var lowPriorityAnalyzers = new ConcurrentSet(); - var priorityProvider = new SuggestedActionPriorityProvider(CodeActionRequestPriority.Default, lowPriorityAnalyzers); + var lowPriorityAnalyzerSupportedDiagnosticIds = new ConcurrentSet(); + var priorityProvider = new SuggestedActionPriorityProvider(CodeActionRequestPriority.Default, lowPriorityAnalyzers, lowPriorityAnalyzerSupportedDiagnosticIds); var normalPriFixes = await tuple.codeFixService.GetFixesAsync(sourceDocument, testSpan, priorityProvider, CodeActionOptions.DefaultProvider, CancellationToken.None); - priorityProvider = new SuggestedActionPriorityProvider(CodeActionRequestPriority.Low, lowPriorityAnalyzers); + priorityProvider = new SuggestedActionPriorityProvider(CodeActionRequestPriority.Low, lowPriorityAnalyzers, lowPriorityAnalyzerSupportedDiagnosticIds); var lowPriFixes = await tuple.codeFixService.GetFixesAsync(sourceDocument, testSpan, priorityProvider, CodeActionOptions.DefaultProvider, CancellationToken.None); if (expectedNoFixes) diff --git a/src/Features/Core/Portable/CodeFixesAndRefactorings/CodeActionRequestPriorityProvider.cs b/src/Features/Core/Portable/CodeFixesAndRefactorings/CodeActionRequestPriorityProvider.cs index 9ac9e94a0056b..28d1c59b2fe62 100644 --- a/src/Features/Core/Portable/CodeFixesAndRefactorings/CodeActionRequestPriorityProvider.cs +++ b/src/Features/Core/Portable/CodeFixesAndRefactorings/CodeActionRequestPriorityProvider.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Generic; +using System.Collections.Immutable; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.Diagnostics; @@ -22,6 +23,11 @@ internal interface ICodeActionRequestPriorityProvider void AddDeprioritizedAnalyzerWithLowPriority(DiagnosticAnalyzer analyzer); bool IsDeprioritizedAnalyzerWithLowPriority(DiagnosticAnalyzer analyzer); + + /// + /// Indicates whether any deprioritized analyzer supports one of the passed in diagnostic ids. + /// + bool HasDeprioritizedAnalyzerSupportingDiagnosticId(ImmutableArray diagnosticIds); } internal static class ICodeActionRequestPriorityProviderExtensions @@ -81,18 +87,25 @@ public static bool MatchesPriority(this ICodeActionRequestPriorityProvider provi return true; } - if (provider.Priority == CodeActionRequestPriority.Low) + if (provider.Priority == codeFixProvider.RequestPriority) + { + return true; + } + + if (provider.Priority == CodeActionRequestPriority.Low && provider.HasDeprioritizedAnalyzerSupportingDiagnosticId(codeFixProvider.FixableDiagnosticIds)) { // 'Low' priority can be used for two types of code fixers: // 1. Those which explicitly set their 'RequestPriority' to 'Low' and // 2. Those which can fix diagnostics for expensive analyzers which were de-prioritized // to 'Low' priority bucket to improve lightbulb population performance. - // Hence, when processing the 'Low' Priority bucket, we accept fixers with any RequestPriority, - // as long as they can fix a diagnostic from an analyzer that was executed in the 'Low' bucket. + // Hence, when processing the 'Low' Priority bucket and the priority provider indicates + // there was a de-prioritized analyzer supporting one of our fixable diagnostic ids, we accept + // fixers with any RequestPriority, as long as they can fix a diagnostic from an analyzer that + // was executed in the 'Low' bucket. return true; } - return provider.Priority == codeFixProvider.RequestPriority; + return false; } } @@ -100,6 +113,7 @@ internal sealed class DefaultCodeActionRequestPriorityProvider(CodeActionRequest { private readonly object _gate = new(); private HashSet? _lowPriorityAnalyzers; + private HashSet? _lowPriorityAnalyzerSupportedDiagnosticIds; public CodeActionRequestPriority? Priority { get; } = priority; @@ -108,7 +122,29 @@ public void AddDeprioritizedAnalyzerWithLowPriority(DiagnosticAnalyzer analyzer) lock (_gate) { _lowPriorityAnalyzers ??= []; + _lowPriorityAnalyzerSupportedDiagnosticIds ??= []; + _lowPriorityAnalyzers.Add(analyzer); + + foreach (var supportedDiagnostic in analyzer.SupportedDiagnostics) + _lowPriorityAnalyzerSupportedDiagnosticIds.Add(supportedDiagnostic.Id); + } + } + + public bool HasDeprioritizedAnalyzerSupportingDiagnosticId(ImmutableArray diagnosticIds) + { + lock (_gate) + { + if (_lowPriorityAnalyzerSupportedDiagnosticIds == null) + return false; + + foreach (var diagnosticId in diagnosticIds) + { + if (_lowPriorityAnalyzerSupportedDiagnosticIds.Contains(diagnosticId)) + return true; + } + + return false; } } diff --git a/src/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.cs b/src/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.cs index f2f65827afd44..f691125a48fab 100644 --- a/src/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.cs +++ b/src/LanguageServer/Protocol/Features/CodeFixes/CodeFixService.cs @@ -472,23 +472,26 @@ private async IAsyncEnumerable StreamFixesAsync( if (hasAnyProjectFixer && projectFixersMap.TryGetValue(diagnosticId, out var projectFixers)) { Debug.Assert(!isInteractive); - AddAllFixers(projectFixers, range, diagnostics); + AddAllFixers(projectFixers, range, diagnostics, currentFixers, fixerToRangesAndDiagnostics); } if (hasAnySharedFixer && fixerMap!.TryGetValue(diagnosticId, out var workspaceFixers)) { if (isInteractive) { - AddAllFixers(workspaceFixers.WhereAsArray(IsInteractiveCodeFixProvider), range, diagnostics); + AddAllFixers(workspaceFixers.WhereAsArray(IsInteractiveCodeFixProvider), range, diagnostics, currentFixers, fixerToRangesAndDiagnostics); } else { - AddAllFixers(workspaceFixers, range, diagnostics); + AddAllFixers(workspaceFixers, range, diagnostics, currentFixers, fixerToRangesAndDiagnostics); } } } } + if (fixerToRangesAndDiagnostics.Count == 0) + yield break; + // Now, sort the fixers so that the ones that are ordered before others get their chance to run first. var allFixers = fixerToRangesAndDiagnostics.Keys.ToImmutableArray(); if (TryGetWorkspaceFixersPriorityMap(document, out var fixersForLanguage)) @@ -580,10 +583,12 @@ private async IAsyncEnumerable StreamFixesAsync( yield break; - void AddAllFixers( + static void AddAllFixers( ImmutableArray fixers, TextSpan range, - List diagnostics) + List diagnostics, + PooledHashSet currentFixers, + PooledDictionary diagnostics)>> fixerToRangesAndDiagnostics) { foreach (var fixer in fixers) { From 9a62d2f3357f37df1aef4ecd1a181eb868bd9357 Mon Sep 17 00:00:00 2001 From: Todd Grunke Date: Tue, 25 Jun 2024 15:49:59 -0700 Subject: [PATCH 2/2] combine the low priority concurrent sets into a struct. --- .../SuggestedActionPriorityProvider.cs | 25 +++++++++++-------- .../Suggestions/SuggestedActionsSource.cs | 5 ++-- .../SuggestedActionsSource_Async.cs | 5 ++-- .../Test/CodeFixes/CodeFixServiceTests.cs | 13 +++++----- .../CodeActionRequestPriorityProvider.cs | 11 ++++---- 5 files changed, 31 insertions(+), 28 deletions(-) diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionPriorityProvider.cs b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionPriorityProvider.cs index 6d711e848cd96..6679b2358dde4 100644 --- a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionPriorityProvider.cs +++ b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionPriorityProvider.cs @@ -6,40 +6,43 @@ using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.Diagnostics; using Roslyn.Utilities; +using static Microsoft.CodeAnalysis.Editor.Implementation.Suggestions.SuggestedActionPriorityProvider; namespace Microsoft.CodeAnalysis.Editor.Implementation.Suggestions; -/// -/// Set of de-prioritized analyzers that were moved down from 'Normal' to 'Low' priority bucket. Note that this set is +/// +/// Information about de-prioritized analyzers that were moved down from 'Normal' to 'Low' priority bucket. Note that this data is /// owned by the and shared across priority buckets. /// -/// -/// Combined set of supported diagnostic ids from all de-prioritized analyzers -/// internal sealed class SuggestedActionPriorityProvider( CodeActionRequestPriority priority, - ConcurrentSet lowPriorityAnalyzers, - ConcurrentSet lowPriorityAnalyzerSupportedDiagnosticIds) + LowPriorityAnalyzersAndDiagnosticIds lowPriorityAnalyzersAndDiagnosticIds) : ICodeActionRequestPriorityProvider { public CodeActionRequestPriority? Priority { get; } = priority; + public struct LowPriorityAnalyzersAndDiagnosticIds() + { + public ConcurrentSet Analyzers { get; } = new(); + public ConcurrentSet SupportedDiagnosticIds { get; } = new(); + } + public void AddDeprioritizedAnalyzerWithLowPriority(DiagnosticAnalyzer analyzer) { - lowPriorityAnalyzers.Add(analyzer); + lowPriorityAnalyzersAndDiagnosticIds.Analyzers.Add(analyzer); foreach (var supportedDiagnostic in analyzer.SupportedDiagnostics) - lowPriorityAnalyzerSupportedDiagnosticIds.Add(supportedDiagnostic.Id); + lowPriorityAnalyzersAndDiagnosticIds.SupportedDiagnosticIds.Add(supportedDiagnostic.Id); } public bool IsDeprioritizedAnalyzerWithLowPriority(DiagnosticAnalyzer analyzer) - => lowPriorityAnalyzers.Contains(analyzer); + => lowPriorityAnalyzersAndDiagnosticIds.Analyzers.Contains(analyzer); public bool HasDeprioritizedAnalyzerSupportingDiagnosticId(ImmutableArray diagnosticIds) { foreach (var diagnosticId in diagnosticIds) { - if (lowPriorityAnalyzerSupportedDiagnosticIds.Contains(diagnosticId)) + if (lowPriorityAnalyzersAndDiagnosticIds.SupportedDiagnosticIds.Contains(diagnosticId)) return true; } diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs index a632a466ea7e0..77f1966faaea1 100644 --- a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs +++ b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource.cs @@ -233,14 +233,13 @@ private void OnTextViewClosed(object sender, EventArgs e) if (state is null) return null; - var lowPriorityAnalyzers = new ConcurrentSet(); - var lowPriorityAnalyzerSupportedDiagnosticIds = new ConcurrentSet(); + var lowPriorityAnalyzerData = new SuggestedActionPriorityProvider.LowPriorityAnalyzersAndDiagnosticIds(); foreach (var order in Orderings) { var priority = TryGetPriority(order); Contract.ThrowIfNull(priority); - var priorityProvider = new SuggestedActionPriorityProvider(priority.Value, lowPriorityAnalyzers, lowPriorityAnalyzerSupportedDiagnosticIds); + var priorityProvider = new SuggestedActionPriorityProvider(priority.Value, lowPriorityAnalyzerData); var result = await GetFixCategoryAsync(priorityProvider).ConfigureAwait(false); if (result != null) diff --git a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource_Async.cs b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource_Async.cs index 2c4fcbe94ba97..e677293685c06 100644 --- a/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource_Async.cs +++ b/src/EditorFeatures/Core.Wpf/Suggestions/SuggestedActionsSource_Async.cs @@ -111,8 +111,7 @@ private async Task GetSuggestedActionsWorkerAsync( // diagnostic engine. We'll run them once we get around to the low-priority bucket. We want to // keep track of this *across* calls to each priority. So we create this set outside of the loop and // then pass it continuously from one priority group to the next. - var lowPriorityAnalyzers = new ConcurrentSet(); - var lowPriorityAnalyzerSupportedDiagnosticIds = new ConcurrentSet(); + var lowPriorityAnalyzerData = new SuggestedActionPriorityProvider.LowPriorityAnalyzersAndDiagnosticIds(); using var _2 = TelemetryLogging.LogBlockTimeAggregated(FunctionId.SuggestedAction_Summary, $"Total"); @@ -126,7 +125,7 @@ private async Task GetSuggestedActionsWorkerAsync( var allSets = GetCodeFixesAndRefactoringsAsync( state, requestedActionCategories, document, range, selection, - new SuggestedActionPriorityProvider(priority, lowPriorityAnalyzers, lowPriorityAnalyzerSupportedDiagnosticIds), + new SuggestedActionPriorityProvider(priority, lowPriorityAnalyzerData), currentActionCount, cancellationToken).WithCancellation(cancellationToken).ConfigureAwait(false); await foreach (var set in allSets) diff --git a/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs b/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs index 7d8d959d9a45a..a59f6172401b1 100644 --- a/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs +++ b/src/EditorFeatures/Test/CodeFixes/CodeFixServiceTests.cs @@ -1080,11 +1080,10 @@ await diagnosticIncrementalAnalyzer.GetDiagnosticsForIdsAsync( includeSuppressedDiagnostics: true, includeLocalDocumentDiagnostics: true, includeNonLocalDocumentDiagnostics: true, CancellationToken.None); await diagnosticIncrementalAnalyzer.GetTestAccessor().TextDocumentOpenAsync(sourceDocument); - var lowPriorityAnalyzers = new ConcurrentSet(); - var lowPriorityAnalyzerSupportedDiagnosticIds = new ConcurrentSet(); - var priorityProvider = new SuggestedActionPriorityProvider(CodeActionRequestPriority.Default, lowPriorityAnalyzers, lowPriorityAnalyzerSupportedDiagnosticIds); + var lowPriorityAnalyzerData = new SuggestedActionPriorityProvider.LowPriorityAnalyzersAndDiagnosticIds(); + var priorityProvider = new SuggestedActionPriorityProvider(CodeActionRequestPriority.Default, lowPriorityAnalyzerData); var normalPriFixes = await tuple.codeFixService.GetFixesAsync(sourceDocument, testSpan, priorityProvider, CodeActionOptions.DefaultProvider, CancellationToken.None); - priorityProvider = new SuggestedActionPriorityProvider(CodeActionRequestPriority.Low, lowPriorityAnalyzers, lowPriorityAnalyzerSupportedDiagnosticIds); + priorityProvider = new SuggestedActionPriorityProvider(CodeActionRequestPriority.Low, lowPriorityAnalyzerData); var lowPriFixes = await tuple.codeFixService.GetFixesAsync(sourceDocument, testSpan, priorityProvider, CodeActionOptions.DefaultProvider, CancellationToken.None); if (expectedNoFixes) @@ -1099,14 +1098,16 @@ await diagnosticIncrementalAnalyzer.GetDiagnosticsForIdsAsync( { Assert.Empty(normalPriFixes); expectedFixCollection = Assert.Single(lowPriFixes); - var lowPriorityAnalyzer = Assert.Single(lowPriorityAnalyzers); + var lowPriorityAnalyzer = Assert.Single(lowPriorityAnalyzerData.Analyzers); Assert.Same(analyzer, lowPriorityAnalyzer); + Assert.Equal(analyzer.SupportedDiagnostics.Select(d => d.Id), lowPriorityAnalyzerData.SupportedDiagnosticIds); } else { expectedFixCollection = Assert.Single(normalPriFixes); Assert.Empty(lowPriFixes); - Assert.Empty(lowPriorityAnalyzers); + Assert.Empty(lowPriorityAnalyzerData.Analyzers); + Assert.Empty(lowPriorityAnalyzerData.SupportedDiagnosticIds); } var fix = expectedFixCollection.Fixes.Single(); diff --git a/src/Features/Core/Portable/CodeFixesAndRefactorings/CodeActionRequestPriorityProvider.cs b/src/Features/Core/Portable/CodeFixesAndRefactorings/CodeActionRequestPriorityProvider.cs index 28d1c59b2fe62..cdb4e5550f89b 100644 --- a/src/Features/Core/Portable/CodeFixesAndRefactorings/CodeActionRequestPriorityProvider.cs +++ b/src/Features/Core/Portable/CodeFixesAndRefactorings/CodeActionRequestPriorityProvider.cs @@ -92,16 +92,17 @@ public static bool MatchesPriority(this ICodeActionRequestPriorityProvider provi return true; } - if (provider.Priority == CodeActionRequestPriority.Low && provider.HasDeprioritizedAnalyzerSupportingDiagnosticId(codeFixProvider.FixableDiagnosticIds)) + if (provider.Priority == CodeActionRequestPriority.Low + && provider.HasDeprioritizedAnalyzerSupportingDiagnosticId(codeFixProvider.FixableDiagnosticIds) + && codeFixProvider.RequestPriority > CodeActionRequestPriority.Low) { // 'Low' priority can be used for two types of code fixers: // 1. Those which explicitly set their 'RequestPriority' to 'Low' and // 2. Those which can fix diagnostics for expensive analyzers which were de-prioritized // to 'Low' priority bucket to improve lightbulb population performance. - // Hence, when processing the 'Low' Priority bucket and the priority provider indicates - // there was a de-prioritized analyzer supporting one of our fixable diagnostic ids, we accept - // fixers with any RequestPriority, as long as they can fix a diagnostic from an analyzer that - // was executed in the 'Low' bucket. + // The first case is handled by the earlier check against matching priorities. For the second + // case, we accept fixers with any RequestPriority, as long as they can fix a diagnostic from + // an analyzer that was executed in the 'Low' bucket. return true; }