| File: src\roslyn\src\Analyzers\CSharp\Analyzers\UsePatternMatching\CSharpUseNotPatternDiagnosticAnalyzer.cs | Web Access |
| Project: src\roslyn\src\CodeStyle\CSharp\Analyzers\Microsoft.CodeAnalysis.CSharp.CodeStyle.csproj (Microsoft.CodeAnalysis.CSharp.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 Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.CSharp.CodeStyle; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; 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(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) { 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; var expressionOfTType = context.Compilation.ExpressionOfTType(); context.RegisterSyntaxNodeAction(n => SyntaxNodeAction(n, expressionOfTType), SyntaxKind.LogicalNotExpression); }); } 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; // Look for the form: !(...) var node = context.Node; if (node is not PrefixUnaryExpressionSyntax(SyntaxKind.LogicalNotExpression) { Operand: ParenthesizedExpressionSyntax parenthesizedExpression }) { return; } 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) // // 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, _ => default }; if (isKeyword == default) return; if (node.IsInExpressionTree(context.SemanticModel, expressionOfTType, cancellationToken)) return; context.ReportDiagnostic(DiagnosticHelper.Create( Descriptor, isKeyword.GetLocation(), styleOption.Notification, context.Options, [node.GetLocation()], properties: null)); } }