private bool ApplySuppressionFix(IEnumerable <DiagnosticData> diagnosticsToFix, Func <Project, bool> shouldFixInProject, bool filterStaleDiagnostics, bool isAddSuppression, bool isSuppressionInSource, bool onlyCompilerDiagnostics, bool showPreviewChangesDialog) { if (diagnosticsToFix == null) { return(false); } diagnosticsToFix = FilterDiagnostics(diagnosticsToFix, isAddSuppression, isSuppressionInSource, onlyCompilerDiagnostics); if (diagnosticsToFix.IsEmpty()) { // Nothing to fix. return(true); } ImmutableDictionary <Document, ImmutableArray <Diagnostic> > documentDiagnosticsToFixMap = null; ImmutableDictionary <Project, ImmutableArray <Diagnostic> > projectDiagnosticsToFixMap = null; var title = GetFixTitle(isAddSuppression); var waitDialogMessage = GetWaitDialogMessage(isAddSuppression); var noDiagnosticsToFix = false; var cancelled = false; var newSolution = _workspace.CurrentSolution; HashSet <string> languages = null; void computeDiagnosticsAndFix(IWaitContext context) { var cancellationToken = context.CancellationToken; cancellationToken.ThrowIfCancellationRequested(); documentDiagnosticsToFixMap = GetDocumentDiagnosticsToFixAsync(diagnosticsToFix, shouldFixInProject, filterStaleDiagnostics: filterStaleDiagnostics, cancellationToken: cancellationToken) .WaitAndGetResult(cancellationToken); cancellationToken.ThrowIfCancellationRequested(); projectDiagnosticsToFixMap = isSuppressionInSource ? ImmutableDictionary <Project, ImmutableArray <Diagnostic> > .Empty : GetProjectDiagnosticsToFixAsync(diagnosticsToFix, shouldFixInProject, filterStaleDiagnostics: filterStaleDiagnostics, cancellationToken: cancellationToken) .WaitAndGetResult(cancellationToken); if (documentDiagnosticsToFixMap == null || projectDiagnosticsToFixMap == null || (documentDiagnosticsToFixMap.IsEmpty && projectDiagnosticsToFixMap.IsEmpty)) { // Nothing to fix. noDiagnosticsToFix = true; return; } cancellationToken.ThrowIfCancellationRequested(); // Equivalence key determines what fix will be applied. // Make sure we don't include any specific diagnostic ID, as we want all of the given diagnostics (which can have varied ID) to be fixed. var equivalenceKey = isAddSuppression ? (isSuppressionInSource ? FeaturesResources.in_Source : FeaturesResources.in_Suppression_File) : FeaturesResources.Remove_Suppression; // We have different suppression fixers for every language. // So we need to group diagnostics by the containing project language and apply fixes separately. languages = new HashSet <string>(projectDiagnosticsToFixMap.Select(p => p.Key.Language).Concat(documentDiagnosticsToFixMap.Select(kvp => kvp.Key.Project.Language))); foreach (var language in languages) { // Use the Fix multiple occurrences service to compute a bulk suppression fix for the specified document and project diagnostics, // show a preview changes dialog and then apply the fix to the workspace. cancellationToken.ThrowIfCancellationRequested(); var documentDiagnosticsPerLanguage = GetDocumentDiagnosticsMappedToNewSolution(documentDiagnosticsToFixMap, newSolution, language); if (!documentDiagnosticsPerLanguage.IsEmpty) { var suppressionFixer = GetSuppressionFixer(documentDiagnosticsPerLanguage.SelectMany(kvp => kvp.Value), language, _codeFixService); if (suppressionFixer != null) { var suppressionFixAllProvider = suppressionFixer.GetFixAllProvider(); newSolution = _fixMultipleOccurencesService.GetFix( documentDiagnosticsPerLanguage, _workspace, suppressionFixer, suppressionFixAllProvider, equivalenceKey, title, waitDialogMessage, cancellationToken); if (newSolution == null) { // User cancelled or fixer threw an exception, so we just bail out. cancelled = true; return; } } } var projectDiagnosticsPerLanguage = GetProjectDiagnosticsMappedToNewSolution(projectDiagnosticsToFixMap, newSolution, language); if (!projectDiagnosticsPerLanguage.IsEmpty) { var suppressionFixer = GetSuppressionFixer(projectDiagnosticsPerLanguage.SelectMany(kvp => kvp.Value), language, _codeFixService); if (suppressionFixer != null) { var suppressionFixAllProvider = suppressionFixer.GetFixAllProvider(); newSolution = _fixMultipleOccurencesService.GetFix( projectDiagnosticsPerLanguage, _workspace, suppressionFixer, suppressionFixAllProvider, equivalenceKey, title, waitDialogMessage, cancellationToken); if (newSolution == null) { // User cancelled or fixer threw an exception, so we just bail out. cancelled = true; return; } } } } } var result = InvokeWithWaitDialog(computeDiagnosticsAndFix, title, waitDialogMessage); // Bail out if the user cancelled. if (cancelled || result == WaitIndicatorResult.Canceled) { return(false); } else if (noDiagnosticsToFix || newSolution == _workspace.CurrentSolution) { // No changes. return(true); } if (showPreviewChangesDialog) { newSolution = FixAllGetFixesService.PreviewChanges( _workspace.CurrentSolution, newSolution, fixAllPreviewChangesTitle: title, fixAllTopLevelHeader: title, languageOpt: languages?.Count == 1 ? languages.Single() : null, workspace: _workspace); if (newSolution == null) { return(false); } } waitDialogMessage = isAddSuppression ? ServicesVSResources.Applying_suppressions_fix : ServicesVSResources.Applying_remove_suppressions_fix; void applyFix(IWaitContext context) { var operations = ImmutableArray.Create <CodeActionOperation>(new ApplyChangesOperation(newSolution)); var cancellationToken = context.CancellationToken; _editHandlerService.ApplyAsync( _workspace, fromDocument: null, operations: operations, title: title, progressTracker: context.ProgressTracker, cancellationToken: cancellationToken).Wait(cancellationToken); } result = InvokeWithWaitDialog(applyFix, title, waitDialogMessage); if (result == WaitIndicatorResult.Canceled) { return(false); } // Kick off diagnostic re-analysis for affected projects so that diagnostics gets refreshed. Task.Run(() => { var reanalyzeDocuments = diagnosticsToFix.Where(d => d.DocumentId != null).Select(d => d.DocumentId).Distinct(); _diagnosticService.Reanalyze(_workspace, documentIds: reanalyzeDocuments, highPriority: true); }); return(true); }
private async Task ApplySuppressionFixAsync(IEnumerable <DiagnosticData>?diagnosticsToFix, Func <Project, bool> shouldFixInProject, bool filterStaleDiagnostics, bool isAddSuppression, bool isSuppressionInSource, bool onlyCompilerDiagnostics, bool showPreviewChangesDialog) { try { using var token = _listener.BeginAsyncOperation(nameof(ApplySuppressionFix)); var title = GetFixTitle(isAddSuppression); var waitDialogMessage = GetWaitDialogMessage(isAddSuppression); using var context = _uiThreadOperationExecutor.BeginExecute( title, waitDialogMessage, allowCancellation: true, showProgress: true); if (diagnosticsToFix == null) { return; } diagnosticsToFix = FilterDiagnostics(diagnosticsToFix, isAddSuppression, isSuppressionInSource, onlyCompilerDiagnostics); if (diagnosticsToFix.IsEmpty()) { return; } var newSolution = _workspace.CurrentSolution; var cancellationToken = context.UserCancellationToken; cancellationToken.ThrowIfCancellationRequested(); var documentDiagnosticsToFixMap = await GetDocumentDiagnosticsToFixAsync( diagnosticsToFix, shouldFixInProject, filterStaleDiagnostics, cancellationToken).ConfigureAwait(false); cancellationToken.ThrowIfCancellationRequested(); var projectDiagnosticsToFixMap = isSuppressionInSource ? ImmutableDictionary <Project, ImmutableArray <Diagnostic> > .Empty : await GetProjectDiagnosticsToFixAsync(diagnosticsToFix, shouldFixInProject, filterStaleDiagnostics, cancellationToken).ConfigureAwait(false); if (documentDiagnosticsToFixMap == null || projectDiagnosticsToFixMap == null || (documentDiagnosticsToFixMap.IsEmpty && projectDiagnosticsToFixMap.IsEmpty)) { // Nothing to fix. return; } cancellationToken.ThrowIfCancellationRequested(); // Equivalence key determines what fix will be applied. // Make sure we don't include any specific diagnostic ID, as we want all of the given diagnostics (which can have varied ID) to be fixed. var equivalenceKey = isAddSuppression ? (isSuppressionInSource ? FeaturesResources.in_Source : FeaturesResources.in_Suppression_File) : FeaturesResources.Remove_Suppression; // We have different suppression fixers for every language. // So we need to group diagnostics by the containing project language and apply fixes separately. var languages = new HashSet <string>(projectDiagnosticsToFixMap.Select(p => p.Key.Language).Concat(documentDiagnosticsToFixMap.Select(kvp => kvp.Key.Project.Language))); foreach (var language in languages) { // Use the Fix multiple occurrences service to compute a bulk suppression fix for the specified document and project diagnostics, // show a preview changes dialog and then apply the fix to the workspace. cancellationToken.ThrowIfCancellationRequested(); var options = _globalOptions.GetCodeActionOptions(language); var optionsProvider = new CodeActionOptionsProvider(_ => options); var documentDiagnosticsPerLanguage = GetDocumentDiagnosticsMappedToNewSolution(documentDiagnosticsToFixMap, newSolution, language); if (!documentDiagnosticsPerLanguage.IsEmpty) { var suppressionFixer = GetSuppressionFixer(documentDiagnosticsPerLanguage.SelectMany(kvp => kvp.Value), language, _codeFixService); if (suppressionFixer != null) { var suppressionFixAllProvider = suppressionFixer.GetFixAllProvider(); newSolution = _fixMultipleOccurencesService.GetFix( documentDiagnosticsPerLanguage, _workspace, suppressionFixer, suppressionFixAllProvider, optionsProvider, equivalenceKey, title, waitDialogMessage, cancellationToken); if (newSolution == null) { // User cancelled or fixer threw an exception, so we just bail out. return; } } } var projectDiagnosticsPerLanguage = GetProjectDiagnosticsMappedToNewSolution(projectDiagnosticsToFixMap, newSolution, language); if (!projectDiagnosticsPerLanguage.IsEmpty) { var suppressionFixer = GetSuppressionFixer(projectDiagnosticsPerLanguage.SelectMany(kvp => kvp.Value), language, _codeFixService); if (suppressionFixer != null) { var suppressionFixAllProvider = suppressionFixer.GetFixAllProvider(); newSolution = _fixMultipleOccurencesService.GetFix( projectDiagnosticsPerLanguage, _workspace, suppressionFixer, suppressionFixAllProvider, optionsProvider, equivalenceKey, title, waitDialogMessage, cancellationToken); if (newSolution == null) { return; } } } if (newSolution == _workspace.CurrentSolution) { // No changes. return; } if (showPreviewChangesDialog) { newSolution = FixAllGetFixesService.PreviewChanges( _workspace.CurrentSolution, newSolution, fixAllPreviewChangesTitle: title, fixAllTopLevelHeader: title, languageOpt: languages?.Count == 1 ? languages.Single() : null, workspace: _workspace); if (newSolution == null) { return; } } waitDialogMessage = isAddSuppression ? ServicesVSResources.Applying_suppressions_fix : ServicesVSResources.Applying_remove_suppressions_fix; var operations = ImmutableArray.Create <CodeActionOperation>(new ApplyChangesOperation(newSolution)); using var scope = context.AddScope(allowCancellation: true, description: waitDialogMessage); await _editHandlerService.ApplyAsync( _workspace, fromDocument : null, operations : operations, title : title, progressTracker : new UIThreadOperationContextProgressTracker(scope), cancellationToken : cancellationToken).ConfigureAwait(false); // Kick off diagnostic re-analysis for affected projects so that diagnostics gets refreshed. _ = Task.Run(() => { var reanalyzeDocuments = diagnosticsToFix.Select(d => d.DocumentId).WhereNotNull().Distinct(); _diagnosticService.Reanalyze(_workspace, documentIds: reanalyzeDocuments, highPriority: true); }); } } catch (OperationCanceledException) { } catch (Exception ex) when(FatalError.ReportAndCatch(ex)) { } }
private bool ApplySuppressionFix(IEnumerable <DiagnosticData> diagnosticsToFix, Func <Project, bool> shouldFixInProject, bool filterStaleDiagnostics, bool isAddSuppression, bool isSuppressionInSource, bool onlyCompilerDiagnostics, bool showPreviewChangesDialog) { if (diagnosticsToFix == null) { return(false); } diagnosticsToFix = FilterDiagnostics(diagnosticsToFix, isAddSuppression, isSuppressionInSource, onlyCompilerDiagnostics); if (diagnosticsToFix.IsEmpty()) { // Nothing to fix. return(true); } ImmutableDictionary <Document, ImmutableArray <Diagnostic> > documentDiagnosticsToFixMap = null; ImmutableDictionary <Project, ImmutableArray <Diagnostic> > projectDiagnosticsToFixMap = null; var title = GetFixTitle(isAddSuppression); var waitDialogMessage = GetWaitDialogMessage(isAddSuppression); Action <CancellationToken> computeDiagnosticsToFix = cancellationToken => { cancellationToken.ThrowIfCancellationRequested(); documentDiagnosticsToFixMap = GetDocumentDiagnosticsToFixAsync(diagnosticsToFix, shouldFixInProject, filterStaleDiagnostics: filterStaleDiagnostics, cancellationToken: cancellationToken) .WaitAndGetResult(cancellationToken); cancellationToken.ThrowIfCancellationRequested(); projectDiagnosticsToFixMap = isSuppressionInSource ? ImmutableDictionary <Project, ImmutableArray <Diagnostic> > .Empty : GetProjectDiagnosticsToFixAsync(diagnosticsToFix, shouldFixInProject, filterStaleDiagnostics: filterStaleDiagnostics, cancellationToken: cancellationToken) .WaitAndGetResult(cancellationToken); }; var result = InvokeWithWaitDialog(computeDiagnosticsToFix, title, waitDialogMessage); // Bail out if the user cancelled. if (result == WaitIndicatorResult.Canceled) { return(false); } if (documentDiagnosticsToFixMap == null || projectDiagnosticsToFixMap == null || (documentDiagnosticsToFixMap.IsEmpty && projectDiagnosticsToFixMap.IsEmpty)) { // Nothing to fix. return(true); } // Equivalence key determines what fix will be applied. // Make sure we don't include any specific diagnostic ID, as we want all of the given diagnostics (which can have varied ID) to be fixed. var equivalenceKey = isAddSuppression ? (isSuppressionInSource ? FeaturesResources.SuppressWithPragma : FeaturesResources.SuppressWithGlobalSuppressMessage) : FeaturesResources.RemoveSuppressionEquivalenceKeyPrefix; // We have different suppression fixers for every language. // So we need to group diagnostics by the containing project language and apply fixes separately. var languages = new HashSet <string>(projectDiagnosticsToFixMap.Keys.Select(p => p.Language).Concat(documentDiagnosticsToFixMap.Select(kvp => kvp.Key.Project.Language))); var newSolution = _workspace.CurrentSolution; foreach (var language in languages) { // Use the Fix multiple occurrences service to compute a bulk suppression fix for the specified document and project diagnostics, // show a preview changes dialog and then apply the fix to the workspace. var documentDiagnosticsPerLanguage = GetDocumentDiagnosticsMappedToNewSolution(documentDiagnosticsToFixMap, newSolution, language); if (!documentDiagnosticsPerLanguage.IsEmpty) { var suppressionFixer = GetSuppressionFixer(documentDiagnosticsPerLanguage.SelectMany(kvp => kvp.Value), language, _codeFixService); if (suppressionFixer != null) { var suppressionFixAllProvider = suppressionFixer.GetFixAllProvider(); newSolution = _fixMultipleOccurencesService.GetFix( documentDiagnosticsPerLanguage, _workspace, suppressionFixer, suppressionFixAllProvider, equivalenceKey, title, waitDialogMessage, cancellationToken: CancellationToken.None); if (newSolution == null) { // User cancelled or fixer threw an exception, so we just bail out. return(false); } } } var projectDiagnosticsPerLanguage = GetProjectDiagnosticsMappedToNewSolution(projectDiagnosticsToFixMap, newSolution, language); if (!projectDiagnosticsPerLanguage.IsEmpty) { var suppressionFixer = GetSuppressionFixer(projectDiagnosticsPerLanguage.SelectMany(kvp => kvp.Value), language, _codeFixService); if (suppressionFixer != null) { var suppressionFixAllProvider = suppressionFixer.GetFixAllProvider(); newSolution = _fixMultipleOccurencesService.GetFix( projectDiagnosticsPerLanguage, _workspace, suppressionFixer, suppressionFixAllProvider, equivalenceKey, title, waitDialogMessage, CancellationToken.None); if (newSolution == null) { // User cancelled or fixer threw an exception, so we just bail out. return(false); } } } } if (newSolution == _workspace.CurrentSolution) { // No changes. return(true); } if (showPreviewChangesDialog) { newSolution = FixAllGetFixesService.PreviewChanges( _workspace.CurrentSolution, newSolution, fixAllPreviewChangesTitle: title, fixAllTopLevelHeader: title, languageOpt: languages.Count == 1 ? languages.Single() : null, workspace: _workspace); if (newSolution == null) { return(false); } } waitDialogMessage = isAddSuppression ? ServicesVSResources.ApplyingSuppressionFix : ServicesVSResources.ApplyingRemoveSuppressionFix; Action <CancellationToken> applyFix = cancellationToken => { var operations = SpecializedCollections.SingletonEnumerable <CodeActionOperation>(new ApplyChangesOperation(newSolution)); _editHandlerService.Apply( _workspace, fromDocument: null, operations: operations, title: title, cancellationToken: cancellationToken); }; result = InvokeWithWaitDialog(applyFix, title, waitDialogMessage); return(result == WaitIndicatorResult.Completed); }