File: Microsoft.CodeQuality.Analyzers\ApiDesignGuidelines\OverrideEqualsAndOperatorEqualsOnValueTypes.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.Generic;
using System.Collections.Immutable;
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>
    /// CA1815: Override equals and operator equals on value types
    /// </summary>
    public abstract class OverrideEqualsAndOperatorEqualsOnValueTypesFixer : CodeFixProvider
    {
        public sealed override ImmutableArray<string> FixableDiagnosticIds { get; } = ImmutableArray.Create(OverrideEqualsAndOperatorEqualsOnValueTypesAnalyzer.RuleId);

        // The analyzer reports a missing Equals override and missing equality operators separately, both on
        // the type, so one declaration can carry two diagnostics. The fix adds everything the type is
        // missing, so it has to run once per declaration rather than once per diagnostic.
        public sealed override FixAllProvider GetFixAllProvider()
            => SyntaxEditorFixAllProvider.Create<HashSet<SyntaxNode>>(
                static _ => new HashSet<SyntaxNode>(),
                ImplementMissingMembersAsync);

        public sealed override Task RegisterCodeFixesAsync(CodeFixContext context)
        {
            Document document = context.Document;
            ImmutableArray<Diagnostic> diagnostics = context.Diagnostics;
            string title = MicrosoftCodeQualityAnalyzersResources.OverrideEqualsAndOperatorEqualsOnValueTypesTitle;

            context.RegisterCodeFix(
                CodeAction.Create(
                    title,
                    cancellationToken =>
                    {
                        HashSet<SyntaxNode> fixedDeclarations = new();
                        return SyntaxEditorFixAllProvider.ApplyFixesAsync(
                            document,
                            diagnostics,
                            (doc, diagnostic, editor, token) => ImplementMissingMembersAsync(doc, diagnostic, editor, fixedDeclarations, token),
                            cancellationToken);
                    },
                    title),
                diagnostics);

            return Task.CompletedTask;
        }

        private static async Task ImplementMissingMembersAsync(
            Document document,
            Diagnostic diagnostic,
            SyntaxEditor editor,
            HashSet<SyntaxNode> fixedDeclarations,
            CancellationToken cancellationToken)
        {
            SyntaxNode enclosingNode = editor.OriginalRoot.FindNode(diagnostic.Location.SourceSpan);
            SyntaxNode? declaration = editor.Generator.GetDeclaration(enclosingNode);
            if (declaration is null || !fixedDeclarations.Add(declaration))
            {
                return;
            }

            SemanticModel model = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
            if (model.GetDeclaredSymbol(declaration, cancellationToken) is not INamedTypeSymbol typeSymbol)
            {
                return;
            }

            SyntaxGenerator generator = editor.Generator;

            if (!typeSymbol.OverridesEquals())
            {
                editor.AddMember(declaration, generator.DefaultEqualsOverrideDeclaration(model.Compilation, typeSymbol));
            }

            if (!typeSymbol.OverridesGetHashCode())
            {
                editor.AddMember(declaration, generator.DefaultGetHashCodeOverrideDeclaration(model.Compilation));
            }

            if (!typeSymbol.ImplementsOperator(WellKnownMemberNames.EqualityOperatorName))
            {
                editor.AddMember(declaration, generator.DefaultOperatorEqualityDeclaration(typeSymbol));
            }

            if (!typeSymbol.ImplementsOperator(WellKnownMemberNames.InequalityOperatorName))
            {
                editor.AddMember(declaration, generator.DefaultOperatorInequalityDeclaration(typeSymbol));
            }
        }
    }
}