|
// 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;
namespace Microsoft.CodeAnalysis.UseCoalesceExpression
{
/// <summary>
/// Looks for code of the form "x == null ? y : x" and offers to convert it to "x ?? y";
/// </summary>
internal abstract class AbstractUseCoalesceExpressionForTernaryConditionalCheckDiagnosticAnalyzer<
TSyntaxKind,
TExpressionSyntax,
TConditionalExpressionSyntax,
TBinaryExpressionSyntax> : AbstractBuiltInCodeStyleDiagnosticAnalyzer
where TSyntaxKind : struct
where TExpressionSyntax : SyntaxNode
where TConditionalExpressionSyntax : TExpressionSyntax
where TBinaryExpressionSyntax : TExpressionSyntax
{
protected AbstractUseCoalesceExpressionForTernaryConditionalCheckDiagnosticAnalyzer()
: base(IDEDiagnosticIds.UseCoalesceExpressionForTernaryConditionalCheckDiagnosticId,
EnforceOnBuildValues.UseCoalesceExpression,
CodeStyleOptions2.PreferCoalesceExpression,
new LocalizableResourceString(nameof(AnalyzersResources.Use_coalesce_expression), AnalyzersResources.ResourceManager, typeof(AnalyzersResources)),
new LocalizableResourceString(nameof(AnalyzersResources.Null_check_can_be_simplified), AnalyzersResources.ResourceManager, typeof(AnalyzersResources)))
{
}
public override DiagnosticAnalyzerCategory GetAnalyzerCategory()
=> DiagnosticAnalyzerCategory.SemanticSpanAnalysis;
protected abstract ISyntaxFacts GetSyntaxFacts();
protected abstract bool IsTargetTyped(SemanticModel semanticModel, TConditionalExpressionSyntax conditional, System.Threading.CancellationToken cancellationToken);
protected override void InitializeWorker(AnalysisContext context)
{
var syntaxKinds = GetSyntaxFacts().SyntaxKinds;
context.RegisterSyntaxNodeAction(AnalyzeSyntax,
syntaxKinds.Convert<TSyntaxKind>(syntaxKinds.TernaryConditionalExpression));
}
private void AnalyzeSyntax(SyntaxNodeAnalysisContext context)
{
var cancellationToken = context.CancellationToken;
var conditionalExpression = (TConditionalExpressionSyntax)context.Node;
var option = context.GetAnalyzerOptions().PreferCoalesceExpression;
if (!option.Value)
return;
var syntaxFacts = GetSyntaxFacts();
syntaxFacts.GetPartsOfConditionalExpression(
conditionalExpression, out var conditionNode, out var whenTrueNodeHigh, out var whenFalseNodeHigh);
conditionNode = syntaxFacts.WalkDownParentheses(conditionNode);
var whenTrueNodeLow = syntaxFacts.WalkDownParentheses(whenTrueNodeHigh);
var whenFalseNodeLow = syntaxFacts.WalkDownParentheses(whenFalseNodeHigh);
if (conditionNode is not TBinaryExpressionSyntax condition)
return;
var syntaxKinds = syntaxFacts.SyntaxKinds;
var isEquals = syntaxKinds.ReferenceEqualsExpression == condition.RawKind;
var isNotEquals = syntaxKinds.ReferenceNotEqualsExpression == condition.RawKind;
if (!isEquals && !isNotEquals)
return;
syntaxFacts.GetPartsOfBinaryExpression(condition, out var conditionLeftHigh, out var conditionRightHigh);
var conditionLeftLow = syntaxFacts.WalkDownParentheses(conditionLeftHigh);
var conditionRightLow = syntaxFacts.WalkDownParentheses(conditionRightHigh);
var conditionLeftIsNull = syntaxFacts.IsNullLiteralExpression(conditionLeftLow);
var conditionRightIsNull = syntaxFacts.IsNullLiteralExpression(conditionRightLow);
if (conditionRightIsNull && conditionLeftIsNull)
{
// null == null nothing to do here.
return;
}
if (!conditionRightIsNull && !conditionLeftIsNull)
return;
if (!syntaxFacts.AreEquivalent(
conditionRightIsNull ? conditionLeftLow : conditionRightLow,
isEquals ? whenFalseNodeLow : whenTrueNodeLow))
{
return;
}
// Coalesce expression cannot be target typed. So if we had a ternary that was target typed
// that means the individual parts themselves had no best common type, which would not work
// for a coalesce expression.
var semanticModel = context.SemanticModel;
if (IsTargetTyped(semanticModel, conditionalExpression, cancellationToken))
return;
var conditionType = semanticModel.GetTypeInfo(
conditionLeftIsNull ? conditionRightLow : conditionLeftLow, cancellationToken).Type;
if (conditionType != null &&
!conditionType.IsReferenceType)
{
// Note: it is intentional that we do not support nullable types here. If you have:
//
// int? x;
// var z = x == null ? y : x;
//
// then that's not the same as: x ?? y. ?? will unwrap the nullable, producing a
// int and not an int? like we have in the above code.
//
// Note: we could look for: x == null ? y : x.Value, and simplify that in the future.
return;
}
var conditionPartToCheck = conditionRightIsNull ? conditionLeftHigh : conditionRightHigh;
var whenPartToCheck = isEquals ? whenTrueNodeHigh : whenFalseNodeHigh;
var locations = ImmutableArray.Create(
conditionalExpression.GetLocation(),
conditionPartToCheck.GetLocation(),
whenPartToCheck.GetLocation());
context.ReportDiagnostic(DiagnosticHelper.Create(
Descriptor,
conditionalExpression.GetLocation(),
option.Notification.Severity,
locations,
properties: null));
}
}
}
|