File: Microsoft.CodeQuality.Analyzers\ApiDesignGuidelines\EnumWithFlagsAttribute.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.Composition;
using System.Threading;
using System.Threading.Tasks;
using Analyzer.Utilities;
using Analyzer.Utilities.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.NetAnalyzers;

namespace Microsoft.CodeQuality.Analyzers.ApiDesignGuidelines
{
    /// <summary>
    /// CA1027: Mark enums with FlagsAttribute
    /// CA2217: Do not mark enums with FlagsAttribute
    /// </summary>
    [ExportCodeFixProvider(LanguageNames.CSharp, LanguageNames.VisualBasic), Shared]
    public sealed class EnumWithFlagsAttributeFixer : CodeFixProvider
    {
        public sealed override ImmutableArray<string> FixableDiagnosticIds { get; } = ImmutableArray.Create(EnumWithFlagsAttributeAnalyzer.RuleIdMarkEnumsWithFlags,
                                                                                   EnumWithFlagsAttributeAnalyzer.RuleIdDoNotMarkEnumsWithFlags);

        public override async Task RegisterCodeFixesAsync(CodeFixContext context)
        {
            SemanticModel model = await context.Document.GetRequiredSemanticModelAsync(context.CancellationToken).ConfigureAwait(false);

            INamedTypeSymbol? flagsAttributeType = model.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemFlagsAttribute);
            if (flagsAttributeType == null)
            {
                return;
            }

            foreach (var diagnostic in context.Diagnostics)
            {
                string fixTitle = GetTitle(diagnostic);
                context.RegisterCodeFix(CodeAction.Create(fixTitle,
                                             ct => SyntaxEditorFixAllProvider.ApplyFixesAsync(context.Document, ImmutableArray.Create(diagnostic), ApplyFixAsync, ct),
                                             equivalenceKey: fixTitle),
                                        diagnostic);
            }
        }

        // The two rules produce opposite fixes, and DocumentBasedFixAllProvider does not filter by
        // CodeActionEquivalenceKey, so a fix-all invoked from one title must skip the other's diagnostics.
        public override FixAllProvider GetFixAllProvider()
            => SyntaxEditorFixAllProvider.Create<string?>(
                fixAllContext => fixAllContext.CodeActionEquivalenceKey,
                (document, diagnostic, editor, equivalenceKey, cancellationToken) => equivalenceKey is null || GetTitle(diagnostic) == equivalenceKey
                    ? ApplyFixAsync(document, diagnostic, editor, cancellationToken)
                    : Task.CompletedTask);

        private static string GetTitle(Diagnostic diagnostic)
            => diagnostic.Id == EnumWithFlagsAttributeAnalyzer.RuleIdMarkEnumsWithFlags
                ? MicrosoftCodeQualityAnalyzersResources.MarkEnumsWithFlagsCodeFix
                : MicrosoftCodeQualityAnalyzersResources.DoNotMarkEnumsWithFlagsCodeFix;

        private static async Task ApplyFixAsync(Document document, Diagnostic diagnostic, SyntaxEditor editor, CancellationToken cancellationToken)
        {
            SemanticModel model = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);

            INamedTypeSymbol? flagsAttributeType = model.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemFlagsAttribute);
            if (flagsAttributeType == null)
            {
                return;
            }

            SyntaxNode node = editor.OriginalRoot.FindNode(diagnostic.Location.SourceSpan);
            if (diagnostic.Id == EnumWithFlagsAttributeAnalyzer.RuleIdMarkEnumsWithFlags)
            {
                SyntaxNode attribute = editor.Generator.Attribute(editor.Generator.TypeExpression(flagsAttributeType));
                editor.ReplaceNode(node, (currentNode, generator) => generator.AddAttributes(currentNode, attribute));
            }
            else if (model.GetDeclaredSymbol(node, cancellationToken) is INamedTypeSymbol enumType)
            {
                SyntaxNode attributeNode = enumType.GetAttribute(flagsAttributeType)!.ApplicationSyntaxReference!.GetSyntax(cancellationToken);
                editor.RemoveNode(attributeNode);
            }
        }
    }
}