Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix 'use pattern matching' with nullable types #71130

Merged
merged 4 commits into from
Dec 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,92 +10,94 @@
using Microsoft.CodeAnalysis.Diagnostics;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

view with whitespace off.

using Microsoft.CodeAnalysis.Shared.Extensions;

namespace Microsoft.CodeAnalysis.CSharp.UsePatternMatching
{
/// <summary>
/// Looks for code of the forms:
///
/// var x = o as Type;
/// if (!(x is Y y)) ...
///
/// and converts it to:
///
/// if (x is not Y y) ...
///
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal sealed class CSharpUseNotPatternDiagnosticAnalyzer : AbstractBuiltInCodeStyleDiagnosticAnalyzer
{
public CSharpUseNotPatternDiagnosticAnalyzer()
: base(IDEDiagnosticIds.UseNotPatternDiagnosticId,
EnforceOnBuildValues.UseNotPattern,
CSharpCodeStyleOptions.PreferNotPattern,
new LocalizableResourceString(
nameof(CSharpAnalyzersResources.Use_pattern_matching), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources)))
{
}
namespace Microsoft.CodeAnalysis.CSharp.UsePatternMatching;

public override DiagnosticAnalyzerCategory GetAnalyzerCategory()
=> DiagnosticAnalyzerCategory.SemanticSpanAnalysis;
/// <summary>
/// Looks for code of the forms:
///
/// var x = o as Type;
/// if (!(x is Y y)) ...
///
/// and converts it to:
///
/// if (x is not Y y) ...
///
/// </summary>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
internal sealed class CSharpUseNotPatternDiagnosticAnalyzer()
: AbstractBuiltInCodeStyleDiagnosticAnalyzer(IDEDiagnosticIds.UseNotPatternDiagnosticId,
EnforceOnBuildValues.UseNotPattern,
CSharpCodeStyleOptions.PreferNotPattern,
new LocalizableResourceString(
nameof(CSharpAnalyzersResources.Use_pattern_matching), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources)))
{
public override DiagnosticAnalyzerCategory GetAnalyzerCategory()
=> DiagnosticAnalyzerCategory.SemanticSpanAnalysis;

protected override void InitializeWorker(AnalysisContext context)
protected override void InitializeWorker(AnalysisContext context)
{
context.RegisterCompilationStartAction(context =>
{
context.RegisterCompilationStartAction(context =>
{
// "x is not Type y" is only available in C# 9.0 and above. Don't offer this refactoring
// in projects targeting a lesser version.
if (context.Compilation.LanguageVersion() < LanguageVersion.CSharp9)
return;
// "x is not Type y" is only available in C# 9.0 and above. Don't offer this refactoring
// in projects targeting a lesser version.
if (context.Compilation.LanguageVersion() < LanguageVersion.CSharp9)
return;

var expressionOfTType = context.Compilation.ExpressionOfTType();
context.RegisterSyntaxNodeAction(n => SyntaxNodeAction(n, expressionOfTType), SyntaxKind.LogicalNotExpression);
});
}
var expressionOfTType = context.Compilation.ExpressionOfTType();
context.RegisterSyntaxNodeAction(n => SyntaxNodeAction(n, expressionOfTType), SyntaxKind.LogicalNotExpression);
});
}

private void SyntaxNodeAction(
SyntaxNodeAnalysisContext context,
INamedTypeSymbol? expressionOfTType)
{
var cancellationToken = context.CancellationToken;
private void SyntaxNodeAction(
SyntaxNodeAnalysisContext context,
INamedTypeSymbol? expressionOfTType)
{
var cancellationToken = context.CancellationToken;

// Bail immediately if the user has disabled this feature.
var styleOption = context.GetCSharpAnalyzerOptions().PreferNotPattern;
if (!styleOption.Value || ShouldSkipAnalysis(context, styleOption.Notification))
return;
// Bail immediately if the user has disabled this feature.
var styleOption = context.GetCSharpAnalyzerOptions().PreferNotPattern;
if (!styleOption.Value || ShouldSkipAnalysis(context, styleOption.Notification))
return;

// Look for the form: !(...)
var node = context.Node;
if (node is not PrefixUnaryExpressionSyntax(SyntaxKind.LogicalNotExpression)
{
Operand: ParenthesizedExpressionSyntax parenthesizedExpression
})
// Look for the form: !(...)
var node = context.Node;
if (node is not PrefixUnaryExpressionSyntax(SyntaxKind.LogicalNotExpression)
{
return;
}
Operand: ParenthesizedExpressionSyntax parenthesizedExpression
})
{
return;
}

var isKeywordLocation = parenthesizedExpression.Expression switch
{
// Look for the form: !(x is Y y) and !(x is const)
IsPatternExpressionSyntax { Pattern: DeclarationPatternSyntax or ConstantPatternSyntax } isPattern => isPattern.IsKeyword.GetLocation(),
var semanticModel = context.SemanticModel;
var isKeyword = parenthesizedExpression.Expression switch
{
// Look for the form: !(x is Y y) and !(x is const)
IsPatternExpressionSyntax { Pattern: DeclarationPatternSyntax or ConstantPatternSyntax } isPattern
=> isPattern.IsKeyword,

// Look for the form: !(x is Y)
BinaryExpressionSyntax(SyntaxKind.IsExpression) { Right: TypeSyntax } isExpression => isExpression.OperatorToken.GetLocation(),
// Look for the form: !(x is Y)
//
// Note: can't convert `!(x is Y?)` to `x is not Y?`. The latter is not legal.
BinaryExpressionSyntax(SyntaxKind.IsExpression) { Right: TypeSyntax type } isExpression
=> semanticModel.GetTypeInfo(type, cancellationToken).Type.IsNullable()
? default
: isExpression.OperatorToken,

_ => null
};
_ => default
};

if (isKeywordLocation is null)
return;
if (isKeyword == default)
return;

if (node.IsInExpressionTree(context.SemanticModel, expressionOfTType, cancellationToken))
return;
if (node.IsInExpressionTree(context.SemanticModel, expressionOfTType, cancellationToken))
return;

context.ReportDiagnostic(DiagnosticHelper.Create(
Descriptor,
isKeywordLocation,
styleOption.Notification,
ImmutableArray.Create(node.GetLocation()),
properties: null));
}
context.ReportDiagnostic(DiagnosticHelper.Create(
Descriptor,
isKeyword.GetLocation(),
styleOption.Notification,
ImmutableArray.Create(node.GetLocation()),
properties: null));
}
}
Loading
Loading