// 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.Composition; using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeFixes.Suppression; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.ErrorLogger; using Microsoft.CodeAnalysis.Extensions; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.Utilities; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.Threading; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.CodeFixes { using Editor.Shared.Utilities; using DiagnosticId = String; using LanguageKind = String; [Export(typeof(ICodeFixService)), Shared] internal partial class CodeFixService : ForegroundThreadAffinitizedObject, ICodeFixService { private readonly IDiagnosticAnalyzerService _diagnosticService; private readonly ImmutableDictionary>>> _workspaceFixersMap; private readonly ConditionalWeakTable, ImmutableDictionary>> _projectFixersMap; // Shared by project fixers and workspace fixers. private ImmutableDictionary> _fixerToFixableIdsMap = ImmutableDictionary>.Empty; private readonly ImmutableDictionary>> _fixerPriorityMap; private readonly ConditionalWeakTable _analyzerReferenceToFixersMap; private readonly ConditionalWeakTable.CreateValueCallback _createProjectCodeFixProvider; private readonly ImmutableDictionary>> _configurationProvidersMap; private readonly IEnumerable> _errorLoggers; private ImmutableDictionary _fixAllProviderMap; [ImportingConstructor] public CodeFixService( IThreadingContext threadingContext, IDiagnosticAnalyzerService service, [ImportMany]IEnumerable> loggers, [ImportMany]IEnumerable> fixers, [ImportMany]IEnumerable> configurationProviders) : base(threadingContext, assertIsForeground: false) { _errorLoggers = loggers; _diagnosticService = service; var fixersPerLanguageMap = fixers.ToPerLanguageMapWithMultipleLanguages(); var configurationProvidersPerLanguageMap = configurationProviders.ToPerLanguageMapWithMultipleLanguages(); _workspaceFixersMap = GetFixerPerLanguageMap(fixersPerLanguageMap, null); _configurationProvidersMap = GetConfigurationProvidersPerLanguageMap(configurationProvidersPerLanguageMap); // REVIEW: currently, fixer's priority is statically defined by the fixer itself. might considering making it more dynamic or configurable. _fixerPriorityMap = GetFixerPriorityPerLanguageMap(fixersPerLanguageMap); // Per-project fixers _projectFixersMap = new ConditionalWeakTable, ImmutableDictionary>>(); _analyzerReferenceToFixersMap = new ConditionalWeakTable(); _createProjectCodeFixProvider = new ConditionalWeakTable.CreateValueCallback(r => new ProjectCodeFixProvider(r)); _fixAllProviderMap = ImmutableDictionary.Empty; } public async Task GetMostSevereFixableDiagnosticAsync( Document document, TextSpan range, CancellationToken cancellationToken) { if (document == null || !document.IsOpen()) { return default; } using var diagnostics = SharedPools.Default>().GetPooledObject(); using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); var linkedToken = linkedTokenSource.Token; // This flag is used by SuggestedActionsSource to track what solution is was // last able to get "full results" for. var isFullResult = await _diagnosticService.TryAppendDiagnosticsForSpanAsync( document, range, diagnostics.Object, cancellationToken: linkedToken).ConfigureAwait(false); var errorDiagnostics = diagnostics.Object.Where(d => d.Severity == DiagnosticSeverity.Error); var otherDiagnostics = diagnostics.Object.Where(d => d.Severity != DiagnosticSeverity.Error); // Kick off a task that will determine there's an Error Diagnostic with a fixer var errorDiagnosticsTask = Task.Run( () => GetFirstDiagnosticWithFixAsync(document, errorDiagnostics, range, linkedToken), linkedToken); // Kick off a task that will determine if any non-Error Diagnostic has a fixer var otherDiagnosticsTask = Task.Run( () => GetFirstDiagnosticWithFixAsync(document, otherDiagnostics, range, linkedToken), linkedToken); // If the error diagnostics task happens to complete with a non-null result before // the other diagnostics task, we can cancel the other task. var diagnostic = await errorDiagnosticsTask.ConfigureAwait(false) ?? await otherDiagnosticsTask.ConfigureAwait(false); linkedTokenSource.Cancel(); return new FirstDiagnosticResult(partialResult: !isFullResult, hasFix: diagnostic != null, diagnostic: diagnostic); } private async Task GetFirstDiagnosticWithFixAsync( Document document, IEnumerable severityGroup, TextSpan range, CancellationToken cancellationToken) { foreach (var diagnostic in severityGroup) { if (!range.IntersectsWith(diagnostic.TextSpan)) { continue; } if (await ContainsAnyFixAsync(document, diagnostic, cancellationToken).ConfigureAwait(false)) { return diagnostic; } } return null; } public async Task> GetFixesAsync(Document document, TextSpan range, bool includeConfigurationFixes, CancellationToken cancellationToken) { // REVIEW: this is the first and simplest design. basically, when ctrl+. is pressed, it asks diagnostic service to give back // current diagnostics for the given span, and it will use that to get fixes. internally diagnostic service will either return cached information // (if it is up-to-date) or synchronously do the work at the spot. // // this design's weakness is that each side don't have enough information to narrow down works to do. it will most likely always do more works than needed. // sometimes way more than it is needed. (compilation) Dictionary> aggregatedDiagnostics = null; foreach (var diagnostic in await _diagnosticService.GetDiagnosticsForSpanAsync(document, range, diagnosticIdOpt: null, includeConfigurationFixes, cancellationToken).ConfigureAwait(false)) { if (diagnostic.IsSuppressed) { continue; } cancellationToken.ThrowIfCancellationRequested(); aggregatedDiagnostics ??= new Dictionary>(); aggregatedDiagnostics.GetOrAdd(diagnostic.TextSpan, _ => new List()).Add(diagnostic); } if (aggregatedDiagnostics == null) { return ImmutableArray.Empty; } using var resultDisposer = ArrayBuilder.GetInstance(out var result); foreach (var spanAndDiagnostic in aggregatedDiagnostics) { await AppendFixesAsync( document, spanAndDiagnostic.Key, spanAndDiagnostic.Value, fixAllForInSpan: false, result, cancellationToken).ConfigureAwait(false); } if (result.Count > 0) { // sort the result to the order defined by the fixers var priorityMap = _fixerPriorityMap[document.Project.Language].Value; result.Sort((d1, d2) => { if (priorityMap.TryGetValue((CodeFixProvider)d1.Provider, out var priority1)) { if (priorityMap.TryGetValue((CodeFixProvider)d2.Provider, out var priority2)) { return priority1 - priority2; } else { return -1; } } else { return 1; } }); } // TODO (https://github.com/dotnet/roslyn/issues/4932): Don't restrict CodeFixes in Interactive if (document.Project.Solution.Workspace.Kind != WorkspaceKind.Interactive && includeConfigurationFixes) { foreach (var spanAndDiagnostic in aggregatedDiagnostics) { await AppendConfigurationsAsync( document, spanAndDiagnostic.Key, spanAndDiagnostic.Value, result, cancellationToken).ConfigureAwait(false); } } return result.ToImmutable(); } public async Task GetDocumentFixAllForIdInSpanAsync(Document document, TextSpan range, string diagnosticId, CancellationToken cancellationToken) { var diagnostics = (await _diagnosticService.GetDiagnosticsForSpanAsync(document, range, diagnosticId, includeSuppressedDiagnostics: false, cancellationToken: cancellationToken).ConfigureAwait(false)).ToList(); if (diagnostics.Count == 0) { return null; } using var resultDisposer = ArrayBuilder.GetInstance(out var result); await AppendFixesAsync(document, range, diagnostics, fixAllForInSpan: true, result, cancellationToken).ConfigureAwait(false); // TODO: Just get the first fix for now until we have a way to config user's preferred fix // https://github.com/dotnet/roslyn/issues/27066 return result.ToImmutable().FirstOrDefault(); } public async Task ApplyCodeFixesForSpecificDiagnosticIdAsync(Document document, string diagnosticId, IProgressTracker progressTracker, CancellationToken cancellationToken) { var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false); var textSpan = new TextSpan(0, tree.Length); var fixCollection = await GetDocumentFixAllForIdInSpanAsync( document, textSpan, diagnosticId, cancellationToken).ConfigureAwait(false); if (fixCollection == null) { return document; } var fixAllService = document.Project.Solution.Workspace.Services.GetService(); var solution = await fixAllService.GetFixAllChangedSolutionAsync( fixCollection.FixAllState.CreateFixAllContext(progressTracker, cancellationToken)).ConfigureAwait(false); return solution.GetDocument(document.Id); } private async Task AppendFixesAsync( Document document, TextSpan span, IEnumerable diagnostics, bool fixAllForInSpan, ArrayBuilder result, CancellationToken cancellationToken) { var hasAnySharedFixer = _workspaceFixersMap.TryGetValue(document.Project.Language, out var fixerMap); var projectFixersMap = GetProjectFixers(document.Project); var hasAnyProjectFixer = projectFixersMap.Any(); if (!hasAnySharedFixer && !hasAnyProjectFixer) { return; } var allFixers = new List(); // TODO (https://github.com/dotnet/roslyn/issues/4932): Don't restrict CodeFixes in Interactive var isInteractive = document.Project.Solution.Workspace.Kind == WorkspaceKind.Interactive; foreach (var diagnosticId in diagnostics.Select(d => d.Id).Distinct()) { cancellationToken.ThrowIfCancellationRequested(); if (hasAnySharedFixer && fixerMap.Value.TryGetValue(diagnosticId, out var workspaceFixers)) { if (isInteractive) { allFixers.AddRange(workspaceFixers.Where(IsInteractiveCodeFixProvider)); } else { allFixers.AddRange(workspaceFixers); } } if (hasAnyProjectFixer && projectFixersMap.TryGetValue(diagnosticId, out var projectFixers)) { Debug.Assert(!isInteractive); allFixers.AddRange(projectFixers); } } var extensionManager = document.Project.Solution.Workspace.Services.GetService(); foreach (var fixer in allFixers.Distinct()) { cancellationToken.ThrowIfCancellationRequested(); await AppendFixesOrConfigurationsAsync( document, span, diagnostics, fixAllForInSpan, result, fixer, hasFix: d => this.GetFixableDiagnosticIds(fixer, extensionManager).Contains(d.Id), getFixes: dxs => { if (fixAllForInSpan) { var primaryDiagnostic = dxs.First(); return GetCodeFixesAsync(document, primaryDiagnostic.Location.SourceSpan, fixer, ImmutableArray.Create(primaryDiagnostic), cancellationToken); } else { return GetCodeFixesAsync(document, span, fixer, dxs, cancellationToken); } }, cancellationToken: cancellationToken).ConfigureAwait(false); // Just need the first result if we are doing fix all in span if (fixAllForInSpan && result.Any()) return; } } private async Task> GetCodeFixesAsync( Document document, TextSpan span, CodeFixProvider fixer, ImmutableArray diagnostics, CancellationToken cancellationToken) { using var fixesDisposer = ArrayBuilder.GetInstance(out var fixes); var context = new CodeFixContext(document, span, diagnostics, // TODO: Can we share code between similar lambdas that we pass to this API in BatchFixAllProvider.cs, CodeFixService.cs and CodeRefactoringService.cs? (action, applicableDiagnostics) => { // Serialize access for thread safety - we don't know what thread the fix provider will call this delegate from. lock (fixes) { fixes.Add(new CodeFix(document.Project, action, applicableDiagnostics)); } }, verifyArguments: false, cancellationToken: cancellationToken); var task = fixer.RegisterCodeFixesAsync(context) ?? Task.CompletedTask; await task.ConfigureAwait(false); return fixes.ToImmutable(); } private async Task AppendConfigurationsAsync( Document document, TextSpan span, IEnumerable diagnostics, ArrayBuilder result, CancellationToken cancellationToken) { if (!_configurationProvidersMap.TryGetValue(document.Project.Language, out var lazyConfigurationProviders) || lazyConfigurationProviders.Value == null) { return; } foreach (var provider in lazyConfigurationProviders.Value) { await AppendFixesOrConfigurationsAsync( document, span, diagnostics, fixAllForInSpan: false, result, provider, hasFix: d => provider.IsFixableDiagnostic(d), getFixes: dxs => provider.GetFixesAsync( document, span, dxs, cancellationToken), cancellationToken: cancellationToken).ConfigureAwait(false); } } private async Task AppendFixesOrConfigurationsAsync( Document document, TextSpan span, IEnumerable diagnosticsWithSameSpan, bool fixAllForInSpan, ArrayBuilder result, TCodeFixProvider fixer, Func hasFix, Func, Task>> getFixes, CancellationToken cancellationToken) { var allDiagnostics = await diagnosticsWithSameSpan.OrderByDescending(d => d.Severity) .ToDiagnosticsAsync(document.Project, cancellationToken).ConfigureAwait(false); var diagnostics = allDiagnostics.WhereAsArray(hasFix); if (diagnostics.Length <= 0) { // this can happen for suppression case where all diagnostics can't be suppressed return; } var extensionManager = document.Project.Solution.Workspace.Services.GetService(); var fixes = await extensionManager.PerformFunctionAsync(fixer, () => getFixes(diagnostics), defaultValue: ImmutableArray.Empty).ConfigureAwait(false); if (fixes.IsDefaultOrEmpty) { return; } // If the fix provider supports fix all occurrences, then get the corresponding FixAllProviderInfo and fix all context. var fixAllProviderInfo = extensionManager.PerformFunction(fixer, () => ImmutableInterlocked.GetOrAdd(ref _fixAllProviderMap, fixer, FixAllProviderInfo.Create), defaultValue: null); FixAllState fixAllState = null; var supportedScopes = ImmutableArray.Empty; if (fixAllProviderInfo != null) { var codeFixProvider = (fixer as CodeFixProvider) ?? new WrapperCodeFixProvider((IConfigurationFixProvider)fixer, diagnostics.Select(d => d.Id)); var diagnosticIds = diagnostics.Where(fixAllProviderInfo.CanBeFixed) .Select(d => d.Id) .ToImmutableHashSet(); var diagnosticProvider = fixAllForInSpan ? new FixAllPredefinedDiagnosticProvider(allDiagnostics) : (FixAllContext.DiagnosticProvider)new FixAllDiagnosticProvider(this, diagnosticIds); fixAllState = new FixAllState( fixAllProvider: fixAllProviderInfo.FixAllProvider, document: document, codeFixProvider: codeFixProvider, scope: FixAllScope.Document, codeActionEquivalenceKey: null, diagnosticIds: diagnosticIds, fixAllDiagnosticProvider: diagnosticProvider); supportedScopes = fixAllProviderInfo.SupportedScopes; } var codeFix = new CodeFixCollection( fixer, span, fixes, fixAllState, supportedScopes, diagnostics.First()); result.Add(codeFix); } /// Looks explicitly for an . public CodeFixProvider GetSuppressionFixer(string language, IEnumerable diagnosticIds) { if (!_configurationProvidersMap.TryGetValue(language, out var lazyConfigurationProviders) || lazyConfigurationProviders.Value.IsDefault) { return null; } // Explicitly looks for an AbstractSuppressionCodeFixProvider var fixer = lazyConfigurationProviders.Value.OfType().FirstOrDefault(); if (fixer == null) { return null; } return new WrapperCodeFixProvider(fixer, diagnosticIds); } private async Task> GetDocumentDiagnosticsAsync(Document document, ImmutableHashSet diagnosticIds, CancellationToken cancellationToken) { Contract.ThrowIfNull(document); var solution = document.Project.Solution; var diagnostics = await _diagnosticService.GetDiagnosticsForIdsAsync(solution, null, document.Id, diagnosticIds, cancellationToken: cancellationToken).ConfigureAwait(false); Contract.ThrowIfFalse(diagnostics.All(d => d.DocumentId != null)); return await diagnostics.ToDiagnosticsAsync(document.Project, cancellationToken).ConfigureAwait(false); } private async Task> GetProjectDiagnosticsAsync(Project project, bool includeAllDocumentDiagnostics, ImmutableHashSet diagnosticIds, CancellationToken cancellationToken) { Contract.ThrowIfNull(project); if (includeAllDocumentDiagnostics) { // Get all diagnostics for the entire project, including document diagnostics. var diagnostics = await _diagnosticService.GetDiagnosticsForIdsAsync(project.Solution, project.Id, diagnosticIds: diagnosticIds, cancellationToken: cancellationToken).ConfigureAwait(false); return await diagnostics.ToDiagnosticsAsync(project, cancellationToken).ConfigureAwait(false); } else { // Get all no-location diagnostics for the project, doesn't include document diagnostics. var diagnostics = await _diagnosticService.GetProjectDiagnosticsForIdsAsync(project.Solution, project.Id, diagnosticIds, cancellationToken: cancellationToken).ConfigureAwait(false); Contract.ThrowIfFalse(diagnostics.All(d => d.DocumentId == null)); return await diagnostics.ToDiagnosticsAsync(project, cancellationToken).ConfigureAwait(false); } } private async Task ContainsAnyFixAsync( Document document, DiagnosticData diagnostic, CancellationToken cancellationToken) { var workspaceFixers = ImmutableArray.Empty; var hasAnySharedFixer = _workspaceFixersMap.TryGetValue(document.Project.Language, out var fixerMap) && fixerMap.Value.TryGetValue(diagnostic.Id, out workspaceFixers); var hasAnyProjectFixer = GetProjectFixers(document.Project).TryGetValue(diagnostic.Id, out var projectFixers); // TODO (https://github.com/dotnet/roslyn/issues/4932): Don't restrict CodeFixes in Interactive if (hasAnySharedFixer && document.Project.Solution.Workspace.Kind == WorkspaceKind.Interactive) { workspaceFixers = workspaceFixers.WhereAsArray(IsInteractiveCodeFixProvider); hasAnySharedFixer = workspaceFixers.Any(); } var hasConfigurationFixer = _configurationProvidersMap.TryGetValue(document.Project.Language, out var lazyConfigurationProviders) && !lazyConfigurationProviders.Value.IsDefaultOrEmpty; if (!hasAnySharedFixer && !hasAnyProjectFixer && !hasConfigurationFixer) { return false; } var allFixers = ImmutableArray.Empty; if (hasAnySharedFixer) { allFixers = workspaceFixers; } if (hasAnyProjectFixer) { allFixers = allFixers.AddRange(projectFixers); } var dx = await diagnostic.ToDiagnosticAsync(document.Project, cancellationToken).ConfigureAwait(false); if (hasConfigurationFixer) { foreach (var lazyConfigurationProvider in lazyConfigurationProviders.Value) { if (lazyConfigurationProvider.IsFixableDiagnostic(dx)) { return true; } } } var fixes = new List(); var context = new CodeFixContext(document, dx, // TODO: Can we share code between similar lambdas that we pass to this API in BatchFixAllProvider.cs, CodeFixService.cs and CodeRefactoringService.cs? (action, applicableDiagnostics) => { // Serialize access for thread safety - we don't know what thread the fix provider will call this delegate from. lock (fixes) { fixes.Add(new CodeFix(document.Project, action, applicableDiagnostics)); } }, verifyArguments: false, cancellationToken: cancellationToken); var extensionManager = document.Project.Solution.Workspace.Services.GetService(); // we do have fixer. now let's see whether it actually can fix it foreach (var fixer in allFixers) { await extensionManager.PerformActionAsync(fixer, () => fixer.RegisterCodeFixesAsync(context) ?? Task.CompletedTask).ConfigureAwait(false); foreach (var fix in fixes) { if (!fix.Action.PerformFinalApplicabilityCheck) { return true; } // Have to see if this fix is still applicable. Jump to the foreground thread // to make that check. await ThreadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(alwaysYield: true, cancellationToken); cancellationToken.ThrowIfCancellationRequested(); var applicable = fix.Action.IsApplicable(document.Project.Solution.Workspace); await TaskScheduler.Default; if (applicable) { return true; } } } return false; } private bool IsInteractiveCodeFixProvider(CodeFixProvider provider) { // TODO (https://github.com/dotnet/roslyn/issues/4932): Don't restrict CodeFixes in Interactive return provider is FullyQualify.AbstractFullyQualifyCodeFixProvider || provider is AddImport.AbstractAddImportCodeFixProvider; } private static readonly Func> s_createList = _ => new List(); private ImmutableArray GetFixableDiagnosticIds(CodeFixProvider fixer, IExtensionManager extensionManager) { // If we are passed a null extension manager it means we do not have access to a document so there is nothing to // show the user. In this case we will log any exceptions that occur, but the user will not see them. if (extensionManager != null) { return extensionManager.PerformFunction( fixer, () => ImmutableInterlocked.GetOrAdd(ref _fixerToFixableIdsMap, fixer, f => GetAndTestFixableDiagnosticIds(f)), defaultValue: ImmutableArray.Empty); } try { return ImmutableInterlocked.GetOrAdd(ref _fixerToFixableIdsMap, fixer, f => GetAndTestFixableDiagnosticIds(f)); } catch (OperationCanceledException) { throw; } catch (Exception e) { foreach (var logger in _errorLoggers) { logger.Value.LogException(fixer, e); } return ImmutableArray.Empty; } } private static ImmutableArray GetAndTestFixableDiagnosticIds(CodeFixProvider codeFixProvider) { var ids = codeFixProvider.FixableDiagnosticIds; if (ids.IsDefault) { throw new InvalidOperationException( string.Format( WorkspacesResources._0_returned_an_uninitialized_ImmutableArray, codeFixProvider.GetType().Name + "." + nameof(CodeFixProvider.FixableDiagnosticIds))); } return ids; } private ImmutableDictionary>>> GetFixerPerLanguageMap( Dictionary>> fixersPerLanguage, IExtensionManager extensionManager) { var fixerMap = ImmutableDictionary.Create>>>(); foreach (var languageKindAndFixers in fixersPerLanguage) { var lazyMap = new Lazy>>(() => { var mutableMap = new Dictionary>(); foreach (var fixer in languageKindAndFixers.Value) { foreach (var id in this.GetFixableDiagnosticIds(fixer.Value, extensionManager)) { if (string.IsNullOrWhiteSpace(id)) { continue; } var list = mutableMap.GetOrAdd(id, s_createList); list.Add(fixer.Value); } } var immutableMap = ImmutableDictionary.CreateBuilder>(); foreach (var diagnosticIdAndFixers in mutableMap) { immutableMap.Add(diagnosticIdAndFixers.Key, diagnosticIdAndFixers.Value.AsImmutableOrEmpty()); } return immutableMap.ToImmutable(); }, isThreadSafe: true); fixerMap = fixerMap.Add(languageKindAndFixers.Key, lazyMap); } return fixerMap; } private static ImmutableDictionary>> GetConfigurationProvidersPerLanguageMap( Dictionary>> configurationProvidersPerLanguage) { var configurationFixerMap = ImmutableDictionary.Create>>(); foreach (var languageKindAndFixers in configurationProvidersPerLanguage) { var lazyConfigurationFixers = new Lazy>(() => GetConfigurationFixProviders(languageKindAndFixers.Value)); configurationFixerMap = configurationFixerMap.Add(languageKindAndFixers.Key, lazyConfigurationFixers); } return configurationFixerMap; static ImmutableArray GetConfigurationFixProviders(List> languageKindAndFixers) { using var builderDisposer = ArrayBuilder.GetInstance(out var builder); var orderedLanguageKindAndFixers = ExtensionOrderer.Order(languageKindAndFixers); foreach (var languageKindAndFixersValue in orderedLanguageKindAndFixers) { builder.Add(languageKindAndFixersValue.Value); } return builder.ToImmutable(); } } private static ImmutableDictionary>> GetFixerPriorityPerLanguageMap( Dictionary>> fixersPerLanguage) { var languageMap = ImmutableDictionary.CreateBuilder>>(); foreach (var languageAndFixers in fixersPerLanguage) { var lazyMap = new Lazy>(() => { var priorityMap = ImmutableDictionary.CreateBuilder(); var fixers = ExtensionOrderer.Order(languageAndFixers.Value); for (var i = 0; i < fixers.Count; i++) { priorityMap.Add(fixers[i].Value, i); } return priorityMap.ToImmutable(); }, isThreadSafe: true); languageMap.Add(languageAndFixers.Key, lazyMap); } return languageMap.ToImmutable(); } private ImmutableDictionary> GetProjectFixers(Project project) { // TODO (https://github.com/dotnet/roslyn/issues/4932): Don't restrict CodeFixes in Interactive return project.Solution.Workspace.Kind == WorkspaceKind.Interactive ? ImmutableDictionary>.Empty : _projectFixersMap.GetValue(project.AnalyzerReferences, pId => ComputeProjectFixers(project)); } private ImmutableDictionary> ComputeProjectFixers(Project project) { var extensionManager = project.Solution.Workspace.Services.GetService(); ImmutableDictionary>.Builder builder = null; foreach (var reference in project.AnalyzerReferences) { var projectCodeFixerProvider = _analyzerReferenceToFixersMap.GetValue(reference, _createProjectCodeFixProvider); foreach (var fixer in projectCodeFixerProvider.GetFixers(project.Language)) { var fixableIds = this.GetFixableDiagnosticIds(fixer, extensionManager); foreach (var id in fixableIds) { if (string.IsNullOrWhiteSpace(id)) { continue; } builder ??= ImmutableDictionary.CreateBuilder>(); var list = builder.GetOrAdd(id, s_createList); list.Add(fixer); } } } if (builder == null) { return ImmutableDictionary>.Empty; } return builder.ToImmutable(); } } }