File: Microsoft.CodeQuality.Analyzers\ApiDesignGuidelines\MarkAttributesWithAttributeUsage.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;
using System.Collections.Immutable;
using System.Composition;
using System.Linq;
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;

namespace Microsoft.CodeQuality.Analyzers.ApiDesignGuidelines
{
    [ExportCodeFixProvider(LanguageNames.CSharp, LanguageNames.VisualBasic), Shared]
    public sealed class MarkAttributesWithAttributeUsageFixer : CodeFixProvider
    {
        public sealed override ImmutableArray<string> FixableDiagnosticIds { get; } = ImmutableArray.Create(MarkAttributesWithAttributeUsageAnalyzer.RuleId);

        public override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer;

        public override async Task RegisterCodeFixesAsync(CodeFixContext context)
        {
            var root = await context.Document.GetRequiredSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
            var nodeToFix = root.FindNode(context.Span);
            if (nodeToFix == null)
            {
                return;
            }

            var semanticModel = await context.Document.GetRequiredSemanticModelAsync(context.CancellationToken).ConfigureAwait(false);
            if (!semanticModel.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemAttributeUsageAttribute, out var attributeUsageAttributeType) ||
                !semanticModel.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemAttributeTargets, out var attributeTargetsType))
            {
                return;
            }

            var applyAttributeTargetValues = Enum.GetValues(typeof(AttributeTargets))
                .Cast<AttributeTargets>()
                .Select(attributeTarget =>
                {
                    var attributeTargetValue = attributeTarget.ToString();
                    var title = $"{nameof(AttributeTargets)}.{attributeTargetValue}";

                    return CodeAction.Create(
                        title,
                        async ct => await AddAttributeUsageAttributeAsync(context.Document, nodeToFix, attributeUsageAttributeType, attributeTargetsType, attributeTargetValue, ct).ConfigureAwait(false),
                        equivalenceKey: title);
                })
                .OrderBy(a => a.Title)
                .ToImmutableArray();

#pragma warning disable RS1010 // Provide an explicit value for EquivalenceKey - false positive
            context.RegisterCodeFix(
                CodeAction.Create(MicrosoftCodeQualityAnalyzersResources.MarkAttributesWithAttributeUsageCodeFix, applyAttributeTargetValues, isInlinable: false),
                context.Diagnostics);
#pragma warning restore RS1010
        }

        private static async Task<Document> AddAttributeUsageAttributeAsync(Document document, SyntaxNode nodeToFix, INamedTypeSymbol attributeUsageAttributeType,
            INamedTypeSymbol attributeTargetsType, string attributeTargetValue, CancellationToken cancellationToken)
        {
            var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false);

            var attribute = editor.Generator.Attribute(editor.Generator.TypeExpression(attributeUsageAttributeType),
                new[] { editor.Generator.MemberAccessExpression(editor.Generator.TypeExpression(attributeTargetsType), attributeTargetValue) });
            editor.AddAttribute(nodeToFix, attribute);

            return editor.GetChangedDocument();
        }
    }
}