File: Microsoft.CodeQuality.Analyzers\ApiDesignGuidelines\OperatorsShouldHaveSymmetricalOverloads.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.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Analyzer.Utilities;
using Analyzer.Utilities.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.NetAnalyzers;

namespace Microsoft.CodeQuality.Analyzers.ApiDesignGuidelines
{
    using static MicrosoftCodeQualityAnalyzersResources;

    /// <summary>
    /// CA2226: Operators should have symmetrical overloads
    /// </summary>
    [ExportCodeFixProvider(LanguageNames.CSharp, LanguageNames.VisualBasic), Shared]
    public sealed class OperatorsShouldHaveSymmetricalOverloadsFixer : SyntaxEditorBasedCodeFixProvider
    {
        public sealed override ImmutableArray<string> FixableDiagnosticIds { get; } = ImmutableArray.Create(OperatorsShouldHaveSymmetricalOverloadsAnalyzer.RuleId);

        public sealed override Task RegisterCodeFixesAsync(CodeFixContext context)
        {
            RegisterCodeFix(context, Generate_missing_operators, nameof(Generate_missing_operators));
            return Task.CompletedTask;
        }

        protected sealed override async Task ApplyFixAsync(
            Document document, Diagnostic diagnostic, SyntaxEditor editor, CancellationToken cancellationToken)
        {
            var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
            var operatorNode = editor.OriginalRoot.FindNode(diagnostic.Location.SourceSpan);

            if (semanticModel.GetDeclaredSymbol(operatorNode, cancellationToken) is not IMethodSymbol containingOperator)
            {
                return;
            }

            Debug.Assert(containingOperator.IsUserDefinedOperator());

            var generator = editor.Generator;
            var newOperator = generator.OperatorDeclaration(
                GetInvertedOperatorKind(containingOperator),
                containingOperator.GetParameters().Select(p => generator.ParameterDeclaration(p)),
                generator.TypeExpression(containingOperator.ReturnType),
                containingOperator.DeclaredAccessibility,
                generator.GetModifiers(operatorNode),
                GetInvertedStatements(generator, containingOperator, semanticModel.Compilation));

            operatorNode = operatorNode.AncestorsAndSelf().First(a => a.RawKind == newOperator.RawKind);

            editor.InsertAfter(operatorNode, newOperator);
        }

        private static IEnumerable<SyntaxNode> GetInvertedStatements(
            SyntaxGenerator generator, IMethodSymbol containingOperator, Compilation compilation)
        {
            if (GetInvertedStatement(generator, containingOperator, compilation) is SyntaxNode statement)
            {
                yield return statement;
            }
        }

        private static SyntaxNode? GetInvertedStatement(
            SyntaxGenerator generator, IMethodSymbol containingOperator, Compilation compilation)
        {
            if (containingOperator.Name == WellKnownMemberNames.EqualityOperatorName)
            {
                return generator.ReturnStatement(
                    generator.LogicalNotExpression(
                        generator.ValueEqualsExpression(
                            generator.IdentifierName(containingOperator.Parameters[0].Name),
                            generator.IdentifierName(containingOperator.Parameters[1].Name))));
            }
            else if (containingOperator.Name == WellKnownMemberNames.InequalityOperatorName)
            {
                return generator.ReturnStatement(
                    generator.LogicalNotExpression(
                        generator.ValueNotEqualsExpression(
                            generator.IdentifierName(containingOperator.Parameters[0].Name),
                            generator.IdentifierName(containingOperator.Parameters[1].Name))));
            }
            else
            {
                // If it's a  <   >   <=   or  >=   operator then we can't simply invert a call
                // to the existing operator.  i.e. the body of the "<" method should *not* be:
                //    return !(a > b);
                // Just provide a throwing impl for now.
                return generator.DefaultMethodStatement(compilation);
            }
        }

        private static OperatorKind GetInvertedOperatorKind(IMethodSymbol containingOperator)
        {
            return containingOperator.Name switch
            {
                WellKnownMemberNames.EqualityOperatorName => OperatorKind.Inequality,
                WellKnownMemberNames.InequalityOperatorName => OperatorKind.Equality,
                WellKnownMemberNames.LessThanOperatorName => OperatorKind.GreaterThan,
                WellKnownMemberNames.LessThanOrEqualOperatorName => OperatorKind.GreaterThanOrEqual,
                WellKnownMemberNames.GreaterThanOperatorName => OperatorKind.LessThan,
                WellKnownMemberNames.GreaterThanOrEqualOperatorName => OperatorKind.LessThanOrEqual,
                _ => throw new InvalidOperationException(),
            };
        }
    }
}