File: src\Analyzers\Core\Analyzers\RemoveRedundantEquality\AbstractRemoveRedundantEqualityDiagnosticAnalyzer.cs
Web Access
Project: src\src\CodeStyle\Core\Analyzers\Microsoft.CodeAnalysis.CodeStyle.csproj (Microsoft.CodeAnalysis.CodeStyle)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Operations;
 
namespace Microsoft.CodeAnalysis.RemoveRedundantEquality;
 
internal abstract class AbstractRemoveRedundantEqualityDiagnosticAnalyzer(ISyntaxFacts syntaxFacts)
    : AbstractBuiltInCodeStyleDiagnosticAnalyzer(
        IDEDiagnosticIds.RemoveRedundantEqualityDiagnosticId,
        EnforceOnBuildValues.RemoveRedundantEquality,
        option: null,
        new LocalizableResourceString(nameof(AnalyzersResources.Remove_redundant_equality), AnalyzersResources.ResourceManager, typeof(AnalyzersResources)))
{
    public override DiagnosticAnalyzerCategory GetAnalyzerCategory()
        => DiagnosticAnalyzerCategory.SemanticSpanAnalysis;
 
    protected override void InitializeWorker(AnalysisContext context)
    {
        context.RegisterOperationAction(AnalyzeBinaryOperator, OperationKind.BinaryOperator);
        context.RegisterOperationAction(AnalyzeIsPatternOperator, OperationKind.IsPattern);
    }
 
    private void AnalyzeIsPatternOperator(OperationAnalysisContext context)
    {
        if (ShouldSkipAnalysis(context, notification: null))
            return;
 
        var syntax = context.Operation.Syntax;
        if (!syntaxFacts.IsIsPatternExpression(syntax))
            return;
 
        var operation = (IIsPatternOperation)context.Operation;
        if (operation.Pattern is not IConstantPatternOperation { Value.ConstantValue.Value: true or false } constantPattern)
            return;
 
        syntaxFacts.GetPartsOfIsPatternExpression(syntax, out _, out var isToken, out _);
        AnalyzeOperator(
            context,
            leftOperand: operation.Value,
            rightOperand: constantPattern.Value,
            isOperatorEquals: true,
            isToken);
    }
 
    private void AnalyzeBinaryOperator(OperationAnalysisContext context)
    {
        if (ShouldSkipAnalysis(context, notification: null))
            return;
 
        var syntax = context.Operation.Syntax;
        if (!syntaxFacts.IsBinaryExpression(syntax))
            return;
 
        // We shouldn't report diagnostic on overloaded operator as the behavior can change.
        var operation = (IBinaryOperation)context.Operation;
        if (operation.OperatorMethod is not null)
            return;
 
        if (operation.OperatorKind is not (BinaryOperatorKind.Equals or BinaryOperatorKind.NotEquals))
            return;
 
        var isOperatorEquals = operation.OperatorKind == BinaryOperatorKind.Equals;
        syntaxFacts.GetPartsOfBinaryExpression(syntax, out _, out var operatorToken, out _);
        AnalyzeOperator(
            context,
            operation.LeftOperand,
            operation.RightOperand,
            isOperatorEquals,
            operatorToken);
    }
 
    private void AnalyzeOperator(
        OperationAnalysisContext context,
        IOperation leftOperand,
        IOperation rightOperand,
        bool isOperatorEquals,
        SyntaxToken operatorToken)
    {
        var leftType = leftOperand.Type;
        var rightType = rightOperand.Type;
 
        if (leftType?.SpecialType != SpecialType.System_Boolean ||
            rightType?.SpecialType != SpecialType.System_Boolean)
        {
            return;
        }
 
        var properties = ImmutableDictionary.CreateBuilder<string, string?>();
        if (TryGetLiteralValue(rightOperand) is bool rightBool)
        {
            properties.Add(RedundantEqualityConstants.RedundantSide, RedundantEqualityConstants.Right);
            if (rightBool != isOperatorEquals)
                properties.Add(RedundantEqualityConstants.Negate, RedundantEqualityConstants.Negate);
        }
        else if (TryGetLiteralValue(leftOperand) is bool leftBool)
        {
            properties.Add(RedundantEqualityConstants.RedundantSide, RedundantEqualityConstants.Left);
            if (leftBool != isOperatorEquals)
                properties.Add(RedundantEqualityConstants.Negate, RedundantEqualityConstants.Negate);
        }
        else
        {
            return;
        }
 
        context.ReportDiagnostic(Diagnostic.Create(Descriptor,
            operatorToken.GetLocation(),
            additionalLocations: [operatorToken.GetLocation()],
            properties: properties.ToImmutable()));
 
        return;
 
        static bool? TryGetLiteralValue(IOperation? operand)
        {
            // Make sure we only simplify literals to avoid changing
            // something like the following example:
            // const bool Activated = true; ... if (state == Activated)
            if (operand is
                {
                    Kind: OperationKind.Literal,
                    ConstantValue: { HasValue: true, Value: bool constValue }
                })
            {
                return constValue;
            }
 
            return null;
        }
    }
}