File: src\Analyzers\Core\Analyzers\SimplifyBooleanExpression\AbstractSimplifyConditionalDiagnosticAnalyzer.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 System.Threading;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Operations;
 
namespace Microsoft.CodeAnalysis.SimplifyBooleanExpression;
 
using static SimplifyBooleanExpressionConstants;
 
internal abstract class AbstractSimplifyConditionalDiagnosticAnalyzer<
    TSyntaxKind,
    TExpressionSyntax,
    TConditionalExpressionSyntax> :
    AbstractBuiltInCodeStyleDiagnosticAnalyzer
    where TSyntaxKind : struct
    where TExpressionSyntax : SyntaxNode
    where TConditionalExpressionSyntax : TExpressionSyntax
{
    private static readonly ImmutableDictionary<string, string?> s_takeCondition
        = ImmutableDictionary<string, string?>.Empty;
    private static readonly ImmutableDictionary<string, string?> s_negateCondition
        = s_takeCondition.Add(Negate, Negate);
    private static readonly ImmutableDictionary<string, string?> s_takeConditionOrWhenFalse
        = s_takeCondition.Add(Or, Or).Add(WhenFalse, WhenFalse);
    private static readonly ImmutableDictionary<string, string?> s_negateConditionAndWhenFalse
        = s_negateCondition.Add(And, And).Add(WhenFalse, WhenFalse);
    private static readonly ImmutableDictionary<string, string?> s_negateConditionOrWhenTrue
        = s_negateCondition.Add(Or, Or).Add(WhenTrue, WhenTrue);
    private static readonly ImmutableDictionary<string, string?> s_takeConditionAndWhenTrue
        = s_takeCondition.Add(And, And).Add(WhenTrue, WhenTrue);
    private static readonly ImmutableDictionary<string, string?> s_takeConditionAndWhenFalse
        = s_takeCondition.Add(And, And).Add(WhenFalse, WhenFalse);
 
    protected AbstractSimplifyConditionalDiagnosticAnalyzer()
        : base(IDEDiagnosticIds.SimplifyConditionalExpressionDiagnosticId,
               EnforceOnBuildValues.SimplifyConditionalExpression,
               CodeStyleOptions2.PreferSimplifiedBooleanExpressions,
               new LocalizableResourceString(nameof(AnalyzersResources.Simplify_conditional_expression), AnalyzersResources.ResourceManager, typeof(AnalyzersResources)),
               new LocalizableResourceString(nameof(AnalyzersResources.Conditional_expression_can_be_simplified), AnalyzersResources.ResourceManager, typeof(AnalyzersResources)))
    {
    }
 
    protected abstract ISyntaxFacts SyntaxFacts { get; }
 
    protected abstract CommonConversion GetConversion(SemanticModel semanticModel, TExpressionSyntax node, CancellationToken cancellationToken);
 
    public sealed override DiagnosticAnalyzerCategory GetAnalyzerCategory()
        => DiagnosticAnalyzerCategory.SemanticSpanAnalysis;
 
    protected sealed override void InitializeWorker(AnalysisContext context)
    {
        var syntaxKinds = SyntaxFacts.SyntaxKinds;
        context.RegisterSyntaxNodeAction(
            AnalyzeConditionalExpression, syntaxKinds.Convert<TSyntaxKind>(syntaxKinds.ConditionalExpression));
    }
 
    private void AnalyzeConditionalExpression(SyntaxNodeAnalysisContext context)
    {
        var styleOption = context.GetAnalyzerOptions().PreferSimplifiedBooleanExpressions;
        if (!styleOption.Value || ShouldSkipAnalysis(context, styleOption.Notification))
        {
            // Bail immediately if the user has disabled this feature.
            return;
        }
 
        var semanticModel = context.SemanticModel;
        var cancellationToken = context.CancellationToken;
        var conditionalExpression = (TConditionalExpressionSyntax)context.Node;
        SyntaxFacts.GetPartsOfConditionalExpression(
            conditionalExpression, out var conditionNode, out var whenTrueNode, out var whenFalseNode);
        var condition = (TExpressionSyntax)conditionNode;
        var whenTrue = (TExpressionSyntax)whenTrueNode;
        var whenFalse = (TExpressionSyntax)whenFalseNode;
 
        // Only offer when everything is a basic boolean type.  That way we don't have to worry
        // about any sort of subtle cases with implicit or bool conversions.
        if (!IsSimpleBooleanType(condition) ||
            !IsSimpleBooleanType(whenTrue) ||
            !IsSimpleBooleanType(whenFalse))
        {
            return;
        }
 
        var whenTrue_isTrue = IsTrue(whenTrue);
        var whenTrue_isFalse = IsFalse(whenTrue);
 
        var whenFalse_isTrue = IsTrue(whenFalse);
        var whenFalse_isFalse = IsFalse(whenFalse);
 
        if (whenTrue_isTrue && whenFalse_isFalse)
        {
            // c ? true : false     =>      c
            ReportDiagnostic(s_takeCondition);
        }
        else if (whenTrue_isFalse && whenFalse_isTrue)
        {
            // c ? false : true     =>      !c
            ReportDiagnostic(s_negateCondition);
        }
        else if (whenTrue_isFalse && whenFalse_isFalse)
        {
            // c ? false : false      =>      c && false
            // Note: this is a slight optimization over the when `c ? false : wf`
            // case below.  It allows to generate `c && false` instead of `!c && false`
            ReportDiagnostic(s_takeConditionAndWhenFalse);
        }
        else if (whenTrue_isTrue)
        {
            // c ? true : wf        =>      c || wf
            ReportDiagnostic(s_takeConditionOrWhenFalse);
        }
        else if (whenTrue_isFalse)
        {
            // c ? false : wf       =>      !c && wf
            ReportDiagnostic(s_negateConditionAndWhenFalse);
        }
        else if (whenFalse_isTrue)
        {
            // c ? wt : true        =>      !c or wt
            ReportDiagnostic(s_negateConditionOrWhenTrue);
        }
        else if (whenFalse_isFalse)
        {
            // c ? wt : false       =>      c && wt
            ReportDiagnostic(s_takeConditionAndWhenTrue);
        }
 
        return;
 
        // local functions
 
        void ReportDiagnostic(ImmutableDictionary<string, string?> properties)
            => context.ReportDiagnostic(DiagnosticHelper.Create(
                Descriptor,
                conditionalExpression.GetLocation(),
                styleOption.Notification,
                context.Options,
                additionalLocations: null,
                properties));
 
        bool IsSimpleBooleanType(TExpressionSyntax node)
        {
            var typeInfo = semanticModel.GetTypeInfo(node, cancellationToken);
            var conversion = GetConversion(semanticModel, node, cancellationToken);
 
            return
                conversion.MethodSymbol == null &&
                typeInfo.Type?.SpecialType == SpecialType.System_Boolean &&
                typeInfo.ConvertedType?.SpecialType == SpecialType.System_Boolean;
        }
 
        bool IsTrue(TExpressionSyntax node) => IsBoolValue(node, true);
        bool IsFalse(TExpressionSyntax node) => IsBoolValue(node, false);
 
        bool IsBoolValue(TExpressionSyntax node, bool value)
        {
            var constantValue = semanticModel.GetConstantValue(node, cancellationToken);
            return constantValue.HasValue && constantValue.Value is bool b && b == value;
        }
    }
}