File: src\Analyzers\CSharp\CodeFixes\RemoveConfusingSuppression\CSharpRemoveConfusingSuppressionCodeFixProvider.cs
Web Access
Project: src\src\Features\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.Features.csproj (Microsoft.CodeAnalysis.CSharp.Features)
// Licensed to the .NET Foundation under one or more agreements.
// 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;
using System.Collections.Immutable;
using System.Composition;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.RemoveConfusingSuppression;
 
[ExportCodeFixProvider(LanguageNames.CSharp, Name = PredefinedCodeFixProviderNames.RemoveConfusingSuppression), Shared]
[method: ImportingConstructor]
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
internal sealed partial class CSharpRemoveConfusingSuppressionCodeFixProvider() : CodeFixProvider
{
    public const string RemoveOperator = nameof(RemoveOperator);
    public const string NegateExpression = nameof(NegateExpression);
 
    public override ImmutableArray<string> FixableDiagnosticIds
        => [IDEDiagnosticIds.RemoveConfusingSuppressionForIsExpressionDiagnosticId];
 
    public override Task RegisterCodeFixesAsync(CodeFixContext context)
    {
        var document = context.Document;
        var diagnostics = context.Diagnostics;
        var cancellationToken = context.CancellationToken;
 
        context.RegisterCodeFix(
            CodeAction.Create(
                CSharpAnalyzersResources.Remove_operator_preserves_semantics,
                c => FixAllAsync(document, diagnostics, negate: false, c),
                RemoveOperator),
            context.Diagnostics);
 
        context.RegisterCodeFix(
            CodeAction.Create(
                CSharpAnalyzersResources.Negate_expression_changes_semantics,
                c => FixAllAsync(document, diagnostics, negate: true, c),
                NegateExpression),
            context.Diagnostics);
 
        return Task.CompletedTask;
    }
 
    private static async Task<Document> FixAllAsync(
        Document document, ImmutableArray<Diagnostic> diagnostics,
        bool negate, CancellationToken cancellationToken)
    {
        var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
        var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
        var editor = new SyntaxEditor(root, document.Project.Solution.Services);
        var generator = editor.Generator;
        var generatorInternal = document.GetRequiredLanguageService<SyntaxGeneratorInternal>();
 
        foreach (var diagnostic in diagnostics)
        {
            var node = diagnostic.AdditionalLocations[0].FindNode(getInnermostNodeForTie: true, cancellationToken);
            Debug.Assert(node.Kind() is SyntaxKind.IsExpression or SyntaxKind.IsPatternExpression);
 
            // Negate the result if requested.
            var updatedNode = negate
                ? generator.Negate(generatorInternal, node, semanticModel, cancellationToken)
                : node;
 
            var isNode = updatedNode.DescendantNodesAndSelf().First(
                n => n.Kind() is SyntaxKind.IsExpression or SyntaxKind.IsPatternExpression);
            var left = isNode switch
            {
                BinaryExpressionSyntax binary => binary.Left,
                IsPatternExpressionSyntax isPattern => isPattern.Expression,
                _ => throw ExceptionUtilities.UnexpectedValue(node),
            };
 
            // Remove the suppression operator.
            var suppression = (PostfixUnaryExpressionSyntax)left;
            var withoutSuppression = suppression.Operand.WithAppendedTrailingTrivia(suppression.OperatorToken.GetAllTrivia());
            var isWithoutSuppression = updatedNode.ReplaceNode(suppression, withoutSuppression);
 
            editor.ReplaceNode(node, isWithoutSuppression);
        }
 
        return document.WithSyntaxRoot(editor.GetChangedRoot());
    }
 
    public override FixAllProvider GetFixAllProvider()
        => FixAllProvider.Create(async (context, document, diagnostics) =>
            await FixAllAsync(
                document, diagnostics,
                context.CodeActionEquivalenceKey == NegateExpression,
                context.CancellationToken).ConfigureAwait(false));
}