File: Microsoft.NetCore.Analyzers\InteropServices\DynamicInterfaceCastableImplementation.Fixer.cs
Web Access
Project: src\sdk\src\Microsoft.CodeAnalysis.NetAnalyzers\src\Microsoft.CodeAnalysis.NetAnalyzers\Microsoft.CodeAnalysis.NetAnalyzers.csproj (Microsoft.CodeAnalysis.NetAnalyzers)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Analyzer.Utilities;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.NetAnalyzers;

namespace Microsoft.NetCore.Analyzers.InteropServices
{
    public abstract class DynamicInterfaceCastableImplementationFixer : CodeFixProvider
    {
        public override ImmutableArray<string> FixableDiagnosticIds { get; } =
            ImmutableArray.Create(
                DynamicInterfaceCastableImplementationAnalyzer.InterfaceMembersMissingImplementationRuleId,
                DynamicInterfaceCastableImplementationAnalyzer.MembersDeclaredOnImplementationTypeMustBeStaticRuleId);

        public sealed override FixAllProvider GetFixAllProvider()
            => SyntaxEditorFixAllProvider.Create<string?>(context => context.CodeActionEquivalenceKey, ApplyFixAsync);

        public override async Task RegisterCodeFixesAsync(CodeFixContext context)
        {
            SyntaxGenerator generator = SyntaxGenerator.GetGenerator(context.Document);
            SyntaxNode root = await context.Document.GetRequiredSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);

            SyntaxNode enclosingNode = root.FindNode(context.Span, getInnermostNodeForTie: true);
            SyntaxNode? declaration = generator.GetDeclaration(enclosingNode);
            if (declaration == null || !CodeFixSupportsDeclaration(declaration))
            {
                return;
            }

            foreach (Diagnostic diagnostic in context.Diagnostics)
            {
                if (GetEquivalenceKey(diagnostic) is not string equivalenceKey)
                {
                    continue;
                }

                ImmutableArray<Diagnostic> diagnostics = ImmutableArray.Create(diagnostic);
                Document document = context.Document;

                context.RegisterCodeFix(
                    CodeAction.Create(
                        GetTitle(equivalenceKey),
                        cancellationToken => SyntaxEditorFixAllProvider.ApplyFixesAsync(
                            document,
                            diagnostics,
                            (doc, diag, editor, token) => ApplyFixAsync(doc, diag, editor, equivalenceKey, token),
                            cancellationToken),
                        equivalenceKey),
                    diagnostic);
            }
        }

        private static string? GetEquivalenceKey(Diagnostic diagnostic)
        {
            if (diagnostic.Id == DynamicInterfaceCastableImplementationAnalyzer.InterfaceMembersMissingImplementationRuleId)
            {
                return nameof(MicrosoftNetCoreAnalyzersResources.ImplementInterfacesOnDynamicCastableImplementation);
            }

            if (diagnostic.Id == DynamicInterfaceCastableImplementationAnalyzer.MembersDeclaredOnImplementationTypeMustBeStaticRuleId
                && diagnostic.Properties.ContainsKey(DynamicInterfaceCastableImplementationAnalyzer.NonStaticMemberIsMethodKey))
            {
                return nameof(MicrosoftNetCoreAnalyzersResources.MakeMethodDeclaredOnImplementationTypeStatic);
            }

            return null;
        }

        private static string GetTitle(string equivalenceKey)
            => equivalenceKey == nameof(MicrosoftNetCoreAnalyzersResources.ImplementInterfacesOnDynamicCastableImplementation)
                ? MicrosoftNetCoreAnalyzersResources.ImplementInterfacesOnDynamicCastableImplementation
                : MicrosoftNetCoreAnalyzersResources.MakeMethodDeclaredOnImplementationTypeStatic;

        private async Task ApplyFixAsync(Document document, Diagnostic diagnostic, SyntaxEditor editor, string? equivalenceKey, CancellationToken cancellationToken)
        {
            if (GetEquivalenceKey(diagnostic) is not string key
                || (equivalenceKey is not null && key != equivalenceKey))
            {
                return;
            }

            SyntaxNode enclosingNode = editor.OriginalRoot.FindNode(diagnostic.Location.SourceSpan, getInnermostNodeForTie: true);
            SyntaxNode? declaration = SyntaxGenerator.GetGenerator(document).GetDeclaration(enclosingNode);
            if (declaration is null || !CodeFixSupportsDeclaration(declaration))
            {
                return;
            }

            if (key == nameof(MicrosoftNetCoreAnalyzersResources.ImplementInterfacesOnDynamicCastableImplementation))
            {
                await ImplementInterfacesOnDynamicCastableImplementationAsync(declaration, document, editor, cancellationToken).ConfigureAwait(false);
            }
            else
            {
                await MakeMemberDeclaredOnImplementationTypeStaticAsync(declaration, document, editor, cancellationToken).ConfigureAwait(false);
            }
        }

        protected static SyntaxAnnotation CreatePossibleInvalidCodeWarning()
        {
            return WarningAnnotation.Create(MicrosoftNetCoreAnalyzersResources.MakeMethodDeclaredOnImplementationTypeStaticMayProduceInvalidCode);
        }

        protected abstract bool CodeFixSupportsDeclaration(SyntaxNode declaration);

        protected abstract Task ImplementInterfacesOnDynamicCastableImplementationAsync(
            SyntaxNode declaration,
            Document document,
            SyntaxEditor editor,
            CancellationToken cancellationToken);

        protected abstract Task MakeMemberDeclaredOnImplementationTypeStaticAsync(
            SyntaxNode declaration,
            Document document,
            SyntaxEditor editor,
            CancellationToken cancellationToken);
    }
}