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);
        }
Example #2
0
        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))
            {
            }
        }
Example #3
0
        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);
        }