| File: src\roslyn\src\Analyzers\CSharp\Analyzers\UsePatternCombinators\CSharpUsePatternCombinatorsDiagnosticAnalyzer.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 System.Collections.Immutable; using System.Linq; using Microsoft.CodeAnalysis.CodeStyle; using Microsoft.CodeAnalysis.CSharp.CodeStyle; using Microsoft.CodeAnalysis.CSharp.Extensions; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; namespace Microsoft.CodeAnalysis.CSharp.UsePatternCombinators; using static AnalyzedPattern; [DiagnosticAnalyzer(LanguageNames.CSharp)] internal sealed class CSharpUsePatternCombinatorsDiagnosticAnalyzer() : AbstractBuiltInCodeStyleDiagnosticAnalyzer( IDEDiagnosticIds.UsePatternCombinatorsDiagnosticId, EnforceOnBuildValues.UsePatternCombinators, CSharpCodeStyleOptions.PreferPatternMatching, s_safePatternTitle, s_safePatternTitle) { private const string SafeKey = "safe"; private static readonly LocalizableResourceString s_safePatternTitle = new(nameof(CSharpAnalyzersResources.Use_pattern_matching), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources)); private static readonly LocalizableResourceString s_unsafePatternTitle = new(nameof(CSharpAnalyzersResources.Use_pattern_matching_may_change_code_meaning), CSharpAnalyzersResources.ResourceManager, typeof(CSharpAnalyzersResources)); private static readonly ImmutableDictionary<string, string?> s_safeProperties = ImmutableDictionary<string, string?>.Empty.Add(SafeKey, ""); private static readonly DiagnosticDescriptor s_unsafeDescriptor = CreateDescriptorWithId( IDEDiagnosticIds.UsePatternCombinatorsDiagnosticId, EnforceOnBuildValues.UsePatternCombinators, hasAnyCodeStyleOption: true, s_unsafePatternTitle); public static bool IsSafe(Diagnostic diagnostic) => diagnostic.Properties.ContainsKey(SafeKey); protected override void InitializeWorker(AnalysisContext context) => context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.LogicalAndExpression, SyntaxKind.LogicalOrExpression, SyntaxKind.LogicalNotExpression); private void AnalyzeNode(SyntaxNodeAnalysisContext context) { var expression = (ExpressionSyntax)context.Node; if (expression.GetDiagnostics().Any(diagnostic => diagnostic.Severity == DiagnosticSeverity.Error)) return; // Bail if this is not a topmost expression // to avoid overlapping diagnostics. if (!IsTopmostExpression(expression)) return; var syntaxTree = expression.SyntaxTree; if (syntaxTree.Options.LanguageVersion() < LanguageVersion.CSharp9) return; var cancellationToken = context.CancellationToken; var styleOption = context.GetCSharpAnalyzerOptions().PreferPatternMatching; if (!styleOption.Value || ShouldSkipAnalysis(context, styleOption.Notification)) return; var semanticModel = context.SemanticModel; var expressionType = semanticModel.Compilation.ExpressionOfTType(); if (expression.IsInExpressionTree(semanticModel, expressionType, cancellationToken)) return; var operation = semanticModel.GetOperation(expression, cancellationToken); if (operation is null) return; var pattern = CSharpUsePatternCombinatorsAnalyzer.Analyze(operation); if (pattern is null) return; // Avoid rewriting trivial patterns, such as a single relational or a constant pattern. if (IsTrivial(pattern)) return; // C# 9.0 does not support pattern variables under `not` and `or` combinators, // except for top-level `not` patterns. if (HasIllegalPatternVariables(pattern, isTopLevel: true)) return; // if the target (the common expression in the pattern) is a method call, // then we can't guarantee that the rewritting won't have side-effects, // so we should warn the user var isSafe = pattern.Target.UnwrapImplicitConversion() is not Operations.IInvocationOperation; context.ReportDiagnostic(DiagnosticHelper.Create( descriptor: isSafe ? this.Descriptor : s_unsafeDescriptor, expression.GetLocation(), styleOption.Notification, context.Options, additionalLocations: null, properties: isSafe ? s_safeProperties : null)); } private static bool HasIllegalPatternVariables(AnalyzedPattern pattern, bool permitDesignations = true, bool isTopLevel = false) { switch (pattern) { case Not p: return HasIllegalPatternVariables(p.Pattern, permitDesignations: isTopLevel); case Binary p: if (p.IsDisjunctive) permitDesignations = false; return HasIllegalPatternVariables(p.Left, permitDesignations) || HasIllegalPatternVariables(p.Right, permitDesignations); case Source p when !permitDesignations: return p.PatternSyntax.DescendantNodes() .OfType<SingleVariableDesignationSyntax>() .Any(variable => !variable.Identifier.IsMissing); default: return false; } } private static bool IsTopmostExpression(ExpressionSyntax node) { return node.WalkUpParentheses().Parent switch { LambdaExpressionSyntax => true, AssignmentExpressionSyntax => true, ConditionalExpressionSyntax => true, ExpressionSyntax => false, _ => true }; } private static bool IsTrivial(AnalyzedPattern pattern) { return pattern switch { Not { Pattern: Constant } => true, Not { Pattern: Source { PatternSyntax: ConstantPatternSyntax } } => true, Not => false, Binary => false, _ => true }; } public override DiagnosticAnalyzerCategory GetAnalyzerCategory() => DiagnosticAnalyzerCategory.SemanticSpanAnalysis; }