提交 f3e22d72 编写于 作者: S Steve Chovanec

UseNullPropagation codefix doesn't recognize `x is null ? null : x.y` pattern (#23043)

上级 65438bd5
......@@ -478,5 +478,125 @@ void Main(S? s)
}
");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)]
public async Task TestWithNullableTypeAndIsNull()
{
await TestInRegularAndScriptAsync(
@"
class C
{
public int? f;
void M(C c)
{
int? x = [||]c is null ? null : c.f;
}
}",
@"
class C
{
public int? f;
void M(C c)
{
int? x = c?.f;
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)]
public async Task TestWithNullableTypeAndReferenceEquals()
{
await TestInRegularAndScriptAsync(
@"
class C
{
public int? f;
void M(C c)
{
int? x = [||]ReferenceEquals(c, null) ? null : c.f;
}
}",
@"
class C
{
public int? f;
void M(C c)
{
int? x = c?.f;
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)]
public async Task TestWithNullableTypeAndReferenceEqualsReversed()
{
await TestInRegularAndScriptAsync(
@"
class C
{
public int? f;
void M(C c)
{
int? x = [||]ReferenceEquals(null, c) ? null : c.f;
}
}",
@"
class C
{
public int? f;
void M(C c)
{
int? x = c?.f;
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)]
public async Task TestWithNullableTypeAndReferenceEqualsWithObject()
{
await TestInRegularAndScriptAsync(
@"
class C
{
public int? f;
void M(C c)
{
int? x = [||]object.ReferenceEquals(c, null) ? null : c.f;
}
}",
@"
class C
{
public int? f;
void M(C c)
{
int? x = c?.f;
}
}");
}
[Fact, Trait(Traits.Feature, Traits.Features.CodeActionsUseNullPropagation)]
public async Task TestWithNullableTypeAndReferenceEqualsWithObjectReversed()
{
await TestInRegularAndScriptAsync(
@"
class C
{
public int? f;
void M(C c)
{
int? x = [||]object.ReferenceEquals(null, c) ? null : c.f;
}
}",
@"
class C
{
public int? f;
void M(C c)
{
int? x = c?.f;
}
}");
}
}
}
......@@ -37,5 +37,28 @@ protected override bool IsEquals(BinaryExpressionSyntax condition)
protected override bool IsNotEquals(BinaryExpressionSyntax condition)
=> condition.Kind() == SyntaxKind.NotEqualsExpression;
protected override bool TryAnalyzePatternCondition(SyntaxNode conditionNode, out SyntaxNode conditionLeft, out SyntaxNode conditionRight, out bool isEquals)
{
conditionLeft = null;
conditionRight = null;
isEquals = true;
var patternExpression = conditionNode as IsPatternExpressionSyntax;
if (patternExpression == null)
{
return false;
}
var constantPattern = patternExpression.Pattern as ConstantPatternSyntax;
if (constantPattern == null)
{
return false;
}
conditionLeft = patternExpression.Expression;
conditionRight = constantPattern.Expression;
return true;
}
}
}
......@@ -2,6 +2,7 @@
using System;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.LanguageServices;
......@@ -49,18 +50,27 @@ protected AbstractUseNullPropagationDiagnosticAnalyzer()
protected abstract ISyntaxFactsService GetSyntaxFactsService();
protected abstract ISemanticFactsService GetSemanticFactsService();
protected abstract bool TryAnalyzePatternCondition(SyntaxNode conditionNode, out SyntaxNode conditionLeft, out SyntaxNode conditionRight, out bool isEquals);
protected override void InitializeWorker(AnalysisContext context)
{
context.RegisterCompilationStartAction(startContext =>
{
var expressionTypeOpt = startContext.Compilation.GetTypeByMetadataName("System.Linq.Expressions.Expression`1");
var objectType = startContext.Compilation.GetSpecialType(SpecialType.System_Object);
var referenceEqualsMethod = objectType?.GetMembers(nameof(ReferenceEquals))
.OfType<IMethodSymbol>()
.FirstOrDefault(m => m.DeclaredAccessibility == Accessibility.Public &&
m.Parameters.Length == 2);
startContext.RegisterSyntaxNodeAction(
c => AnalyzeSyntax(c, expressionTypeOpt), GetSyntaxKindToAnalyze());
c => AnalyzeSyntax(c, expressionTypeOpt, referenceEqualsMethod), GetSyntaxKindToAnalyze());
});
}
private void AnalyzeSyntax(SyntaxNodeAnalysisContext context, INamedTypeSymbol expressionTypeOpt)
private void AnalyzeSyntax(SyntaxNodeAnalysisContext context, INamedTypeSymbol expressionTypeOpt, IMethodSymbol referenceEqualsMethod)
{
var conditionalExpression = (TConditionalExpressionSyntax)context.Node;
if (!ShouldAnalyze(conditionalExpression.SyntaxTree.Options))
......@@ -88,21 +98,13 @@ private void AnalyzeSyntax(SyntaxNodeAnalysisContext context, INamedTypeSymbol e
conditionNode = syntaxFacts.WalkDownParentheses(conditionNode);
var condition = conditionNode as TBinaryExpressionSyntax;
if (condition == null)
var isEqualityLikeCondition = TryAnalyzeCondition(
context, syntaxFacts, referenceEqualsMethod, conditionNode, out var conditionLeft, out var conditionRight, out var isEquals);
if (!isEqualityLikeCondition)
{
return;
}
var isEquals = IsEquals(condition);
var isNotEquals = IsNotEquals(condition);
if (!isEquals && !isNotEquals)
{
return;
}
syntaxFacts.GetPartsOfBinaryExpression(condition, out var conditionLeft, out var conditionRight);
var conditionLeftIsNull = syntaxFacts.IsNullLiteralExpression(conditionLeft);
var conditionRightIsNull = syntaxFacts.IsNullLiteralExpression(conditionRight);
......@@ -125,7 +127,7 @@ private void AnalyzeSyntax(SyntaxNodeAnalysisContext context, INamedTypeSymbol e
return;
}
if (isNotEquals && !syntaxFacts.IsNullLiteralExpression(whenFalseNode))
if (!isEquals && !syntaxFacts.IsNullLiteralExpression(whenFalseNode))
{
return;
}
......@@ -181,9 +183,94 @@ private void AnalyzeSyntax(SyntaxNodeAnalysisContext context, INamedTypeSymbol e
properties));
}
private bool TryAnalyzeCondition(
SyntaxNodeAnalysisContext context, ISyntaxFactsService syntaxFacts, IMethodSymbol referenceEqualsMethod, SyntaxNode conditionNode,
out SyntaxNode conditionLeft, out SyntaxNode conditionRight, out bool isEquals)
{
switch (conditionNode)
{
case TBinaryExpressionSyntax binaryExpression:
return TryAnalyzeBinaryExpressionCondition(
syntaxFacts, binaryExpression, out conditionLeft, out conditionRight, out isEquals);
case TInvocationExpression invocation:
return TryAnalyzeInvocationCondition(
context, syntaxFacts, referenceEqualsMethod, invocation,
out conditionLeft, out conditionRight, out isEquals);
default:
return TryAnalyzePatternCondition(
conditionNode, out conditionLeft, out conditionRight, out isEquals);
}
}
private bool TryAnalyzeBinaryExpressionCondition(
ISyntaxFactsService syntaxFacts, TBinaryExpressionSyntax condition,
out SyntaxNode conditionLeft, out SyntaxNode conditionRight, out bool isEquals)
{
isEquals = IsEquals(condition);
if (!isEquals && !IsNotEquals(condition))
{
conditionLeft = null;
conditionRight = null;
return false;
}
else
{
syntaxFacts.GetPartsOfBinaryExpression(condition, out conditionLeft, out conditionRight);
return true;
}
}
private static bool TryAnalyzeInvocationCondition(
SyntaxNodeAnalysisContext context, ISyntaxFactsService syntaxFacts, IMethodSymbol referenceEqualsMethod, TInvocationExpression invocation,
out SyntaxNode conditionLeft, out SyntaxNode conditionRight, out bool isEquals)
{
conditionLeft = null;
conditionRight = null;
isEquals = true;
var expression = syntaxFacts.GetExpressionOfInvocationExpression(invocation);
var nameNode = syntaxFacts.IsIdentifierName(expression)
? expression
: syntaxFacts.IsSimpleMemberAccessExpression(expression)
? syntaxFacts.GetNameOfMemberAccessExpression(expression)
: null;
if (!syntaxFacts.IsIdentifierName(nameNode))
{
return false;
}
syntaxFacts.GetNameAndArityOfSimpleName(nameNode, out var name, out _);
if (!syntaxFacts.StringComparer.Equals(name, nameof(ReferenceEquals)))
{
return false;
}
var arguments = syntaxFacts.GetArgumentsOfInvocationExpression(invocation);
if (arguments.Count != 2)
{
return false;
}
var semanticModel = context.SemanticModel;
var cancellationToken = context.CancellationToken;
var symbol = semanticModel.GetSymbolInfo(invocation, cancellationToken).Symbol;
if (!referenceEqualsMethod.Equals(symbol))
{
return false;
}
conditionLeft = syntaxFacts.GetExpressionOfArgument(arguments[0]);
conditionRight = syntaxFacts.GetExpressionOfArgument(arguments[1]);
return true;
}
internal static SyntaxNode GetWhenPartMatch(
ISyntaxFactsService syntaxFacts, ISemanticFactsService semanticFacts, SemanticModel semanticModel, SyntaxNode expressionToMatch, SyntaxNode whenPart)
{
expressionToMatch = GetExpressionIfArgument(syntaxFacts, expressionToMatch);
expressionToMatch = RemoveObjectCastIfAny(syntaxFacts, semanticModel, expressionToMatch);
var current = whenPart;
while (true)
......@@ -207,6 +294,16 @@ private void AnalyzeSyntax(SyntaxNodeAnalysisContext context, INamedTypeSymbol e
}
}
private static SyntaxNode GetExpressionIfArgument(ISyntaxFactsService syntaxFacts, SyntaxNode node)
{
if (syntaxFacts.IsArgument(node))
{
return syntaxFacts.GetExpressionOfArgument(node);
}
return node;
}
private static SyntaxNode RemoveObjectCastIfAny(ISyntaxFactsService syntaxFacts, SemanticModel semanticModel, SyntaxNode node)
{
if (syntaxFacts.IsCastExpression(node))
......
......@@ -27,7 +27,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UseNullPropagation
End Function
Protected Overrides Function GetSemanticFactsService() As ISemanticFactsService
Return VisualBasicSemanticFactsService.instance
Return VisualBasicSemanticFactsService.Instance
End Function
Protected Overrides Function GetSyntaxKindToAnalyze() As SyntaxKind
......@@ -41,5 +41,12 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.UseNullPropagation
Protected Overrides Function IsNotEquals(condition As BinaryExpressionSyntax) As Boolean
Return condition.Kind() = SyntaxKind.IsNotExpression
End Function
Protected Overrides Function TryAnalyzePatternCondition(conditionNode As SyntaxNode, ByRef conditionLeft As SyntaxNode, ByRef conditionRight As SyntaxNode, ByRef isEquals As Boolean) As Boolean
conditionLeft = Nothing
conditionRight = Nothing
isEquals = False
Return False
End Function
End Class
End Namespace
......@@ -41,7 +41,7 @@ public SyntaxTrivia ElasticCarriageReturnLineFeed
protected override IDocumentationCommentService DocumentationCommentService
=> CSharpDocumentationCommentService.Instance;
public bool SupportsIndexingInitializer(ParseOptions options)
public bool SupportsIndexingInitializer(ParseOptions options)
=> ((CSharpParseOptions)options).LanguageVersion >= LanguageVersion.CSharp6;
public bool SupportsThrowExpression(ParseOptions options)
......@@ -280,10 +280,10 @@ public bool IsReturnStatement(SyntaxNode node)
public bool IsStatement(SyntaxNode node)
=> node is StatementSyntax;
public bool IsParameter(SyntaxNode node)
=> node is ParameterSyntax;
public bool IsVariableDeclarator(SyntaxNode node)
=> node is VariableDeclaratorSyntax;
......@@ -651,6 +651,9 @@ public SyntaxNode GetExpressionOfArgument(SyntaxNode node)
public RefKind GetRefKindOfArgument(SyntaxNode node)
=> (node as ArgumentSyntax).GetRefKind();
public bool IsArgument(SyntaxNode node)
=> node is ArgumentSyntax;
public bool IsSimpleArgument(SyntaxNode node)
{
var argument = (ArgumentSyntax)node;
......@@ -1443,7 +1446,7 @@ public bool IsDeclaration(SyntaxNode node)
private static readonly SyntaxAnnotation s_annotation = new SyntaxAnnotation();
public void AddFirstMissingCloseBrace(
SyntaxNode root, SyntaxNode contextNode,
SyntaxNode root, SyntaxNode contextNode,
out SyntaxNode newRoot, out SyntaxNode newContextNode)
{
// First, annotate the context node in the tree so that we can find it again
......@@ -1608,9 +1611,9 @@ public override bool IsShebangDirectiveTrivia(SyntaxTrivia trivia)
public override bool IsPreprocessorDirective(SyntaxTrivia trivia)
=> SyntaxFacts.IsPreprocessorDirective(trivia.Kind());
private class AddFirstMissingCloseBaceRewriter: CSharpSyntaxRewriter
private class AddFirstMissingCloseBaceRewriter : CSharpSyntaxRewriter
{
private readonly SyntaxNode _contextNode;
private readonly SyntaxNode _contextNode;
private bool _seenContextNode = false;
private bool _addedFirstCloseCurly = false;
......@@ -1647,7 +1650,7 @@ public override SyntaxNode Visit(SyntaxNode node)
// then still ask to format its close curly to make sure all the
// curlies up the stack are properly formatted.
var braces = rewritten.GetBraces();
if (braces.openBrace.Kind() == SyntaxKind.None &&
if (braces.openBrace.Kind() == SyntaxKind.None &&
braces.closeBrace.Kind() == SyntaxKind.None)
{
// Not an item with braces. Just pass it up.
......
......@@ -160,6 +160,8 @@ internal interface ISyntaxFactsService : ILanguageService
SyntaxToken GetIdentifierOfSimpleName(SyntaxNode node);
SyntaxToken GetIdentifierOfVariableDeclarator(SyntaxNode node);
bool IsArgument(SyntaxNode node);
/// <summary>
/// True if this is an argument with just an expression and nothing else (i.e. no ref/out,
/// no named params, no omitted args).
......@@ -319,7 +321,7 @@ internal interface ISyntaxFactsService : ILanguageService
// updates root will be returned. The context node in that new tree will also
// be returned.
void AddFirstMissingCloseBrace(
SyntaxNode root, SyntaxNode contextNode,
SyntaxNode root, SyntaxNode contextNode,
out SyntaxNode newRoot, out SyntaxNode newContextNode);
SyntaxNode GetNextExecutableStatement(SyntaxNode statement);
......
......@@ -582,6 +582,10 @@ Namespace Microsoft.CodeAnalysis.VisualBasic
Return RefKind.None
End Function
Public Function IsArgument(node As SyntaxNode) As Boolean Implements ISyntaxFactsService.IsArgument
Return TypeOf node Is ArgumentSyntax
End Function
Public Function IsSimpleArgument(node As SyntaxNode) As Boolean Implements ISyntaxFactsService.IsSimpleArgument
Dim argument = DirectCast(node, ArgumentSyntax)
Return Not argument.IsNamed AndAlso Not argument.IsOmitted
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册