File: Microsoft.CodeQuality.Analyzers\ApiDesignGuidelines\DefineAccessorsForAttributeArguments.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.Globalization;
using System.Linq;
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;

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

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

            foreach (var diagnostic in context.Diagnostics)
            {
                if (diagnostic.Properties.TryGetValue("case", out var fixCase))
                {
                    string title;
                    switch (fixCase)
                    {
                        case DefineAccessorsForAttributeArgumentsAnalyzer.AddAccessorCase:
                            SyntaxNode parameter = generator.GetDeclaration(node, DeclarationKind.Parameter);
                            if (parameter != null)
                            {
                                title = MicrosoftCodeQualityAnalyzersResources.CreatePropertyAccessorForParameter;
                                context.RegisterCodeFix(CodeAction.Create(title,
                                                             async ct => await AddAccessorAsync(context.Document, parameter, ct).ConfigureAwait(false),
                                                             equivalenceKey: title),
                                                        diagnostic);
                            }

                            return;

                        case DefineAccessorsForAttributeArgumentsAnalyzer.MakePublicCase:
                            SyntaxNode property = generator.GetDeclaration(node, DeclarationKind.Property);
                            if (property != null)
                            {
                                title = MicrosoftCodeQualityAnalyzersResources.MakeGetterPublic;
                                context.RegisterCodeFix(CodeAction.Create(title,
                                                                 async ct => await MakePublicAsync(context.Document, node, property, ct).ConfigureAwait(false),
                                                                 equivalenceKey: title),
                                                        diagnostic);
                            }

                            return;

                        case DefineAccessorsForAttributeArgumentsAnalyzer.RemoveSetterCase:
                            title = MicrosoftCodeQualityAnalyzersResources.MakeSetterNonPublic;
                            context.RegisterCodeFix(CodeAction.Create(title,
                                                         async ct => await RemoveSetterAsync(context.Document, node, ct).ConfigureAwait(false),
                                                         equivalenceKey: title),
                                                    diagnostic);
                            return;

                        default:
                            return;
                    }
                }
            }
        }

        private static async Task<Document> AddAccessorAsync(Document document, SyntaxNode parameter, CancellationToken cancellationToken)
        {
            SymbolEditor symbolEditor = SymbolEditor.Create(document);
            SemanticModel model = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);

            if (model.GetDeclaredSymbol(parameter, cancellationToken) is not IParameterSymbol parameterSymbol)
            {
                return document;
            }

            // Make the first character uppercase since we are generating a property.
            string propName = char.ToUpper(parameterSymbol.Name[0], CultureInfo.InvariantCulture).ToString() + parameterSymbol.Name[1..];

            INamedTypeSymbol typeSymbol = parameterSymbol.ContainingType;
            ISymbol? propertySymbol = typeSymbol.GetMembers(propName).FirstOrDefault(m => m.Kind == SymbolKind.Property);

            // Add a new property
            if (propertySymbol == null)
            {
                await symbolEditor.EditOneDeclarationAsync(typeSymbol,
                                                           parameter.GetLocation(), // edit the partial declaration that has this parameter symbol.
                                                           (editor, typeDeclaration) =>
                                                           {
                                                               SyntaxNode newProperty = editor.Generator.PropertyDeclaration(propName,
                                                                                                                      editor.Generator.TypeExpression(parameterSymbol.Type),
                                                                                                                      Accessibility.Public,
                                                                                                                      DeclarationModifiers.ReadOnly);
                                                               newProperty = editor.Generator.WithGetAccessorStatements(newProperty, null);
                                                               editor.AddMember(typeDeclaration, newProperty);
                                                           },
                                                           cancellationToken).ConfigureAwait(false);
            }
            else
            {
                await symbolEditor.EditOneDeclarationAsync(propertySymbol,
                                                          (editor, propertyDeclaration) =>
                                                          {
                                                              editor.SetGetAccessorStatements(propertyDeclaration, editor.Generator.DefaultMethodBody(model.Compilation));
                                                              editor.SetModifiers(propertyDeclaration, editor.Generator.GetModifiers(propertyDeclaration) - DeclarationModifiers.WriteOnly);
                                                          },
                                                          cancellationToken).ConfigureAwait(false);
            }

            return symbolEditor.GetChangedDocuments().First();
        }

        private static async Task<Document> MakePublicAsync(Document document, SyntaxNode getMethod, SyntaxNode property, CancellationToken cancellationToken)
        {
            // Clear the accessibility on the getter.
            DocumentEditor editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false);
            editor.SetAccessibility(getMethod, Accessibility.NotApplicable);

            // If the containing property is not public, make it so
            Accessibility propertyAccessibility = editor.Generator.GetAccessibility(property);
            if (propertyAccessibility != Accessibility.Public)
            {
                editor.SetAccessibility(property, Accessibility.Public);

                // Having just made the property public, if it has a setter with no Accessibility set, then we've just made the setter public.
                // Instead restore the setter's original accessibility so that we don't fire a violation with the generated code.
                SyntaxNode setter = editor.Generator.GetAccessor(property, DeclarationKind.SetAccessor);
                if (setter != null)
                {
                    Accessibility setterAccessibility = editor.Generator.GetAccessibility(setter);
                    if (setterAccessibility == Accessibility.NotApplicable)
                    {
                        editor.SetAccessibility(setter, propertyAccessibility);
                    }
                }
            }

            return editor.GetChangedDocument();
        }

        private static async Task<Document> RemoveSetterAsync(Document document, SyntaxNode setMethod, CancellationToken cancellationToken)
        {
            DocumentEditor editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false);
            editor.SetAccessibility(setMethod, Accessibility.Internal);
            return editor.GetChangedDocument();
        }

        public override FixAllProvider GetFixAllProvider()
        {
            return WellKnownFixAllProviders.BatchFixer;
        }
    }
}