File: AbstractSimplifyConditionalDiagnosticAnalyzer.cs
Web Access
Project: ..\..\..\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;
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)
            {
                // 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.Severity,
                    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;
            }
        }
    }
}