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.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Editing;

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 : CodeFixProvider
    {
        public sealed override ImmutableArray<string> FixableDiagnosticIds { get; } = ImmutableArray.Create(OperatorsShouldHaveSymmetricalOverloadsAnalyzer.RuleId);

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

        public sealed override Task RegisterCodeFixesAsync(CodeFixContext context)
        {
            context.RegisterCodeFix(
                CodeAction.Create(
                    MicrosoftCodeQualityAnalyzersResources.Generate_missing_operators,
                    c => CreateChangedDocumentAsync(context, c),
                    nameof(Generate_missing_operators)),
                context.Diagnostics);
            return Task.FromResult(true);
        }

        private static async Task<Document> CreateChangedDocumentAsync(
            CodeFixContext context, CancellationToken cancellationToken)
        {
            var document = context.Document;
            var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false);
            var semanticModel = editor.SemanticModel;
            var root = await semanticModel.SyntaxTree.GetRootAsync(cancellationToken).ConfigureAwait(false);
            var operatorNode = root.FindNode(context.Diagnostics.First().Location.SourceSpan);

            var containingOperator = (IMethodSymbol)semanticModel.GetDeclaredSymbol(operatorNode, cancellationToken)!;

            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);
            return editor.GetChangedDocument();
        }

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

        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(),
            };
        }
    }
}