| File: Microsoft.NetCore.Analyzers\Runtime\UseRegexMembers.cs | Web Access |
| Project: src\sdk\src\Microsoft.CodeAnalysis.NetAnalyzers\src\Microsoft.CodeAnalysis.NetAnalyzers\Microsoft.CodeAnalysis.NetAnalyzers.csproj (Microsoft.CodeAnalysis.NetAnalyzers) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Immutable; using System.Linq; using Analyzer.Utilities; using Analyzer.Utilities.Extensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; namespace Microsoft.NetCore.Analyzers.Runtime { using static MicrosoftNetCoreAnalyzersResources; /// <summary> /// CA1874: <inheritdoc cref="UseRegexIsMatchMessage"/> /// CA1875: <inheritdoc cref="UseRegexCountMessage"/> /// </summary> [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] public sealed class UseRegexMembers : DiagnosticAnalyzer { internal const string RegexIsMatchRuleId = "CA1874"; internal const string RegexCountRuleId = "CA1875"; // Regex.Match(...).Success => Regex.IsMatch(...) internal static readonly DiagnosticDescriptor UseRegexIsMatchRuleId = DiagnosticDescriptorHelper.Create(RegexIsMatchRuleId, CreateLocalizableResourceString(nameof(UseRegexIsMatchTitle)), CreateLocalizableResourceString(nameof(UseRegexIsMatchMessage)), DiagnosticCategory.Performance, RuleLevel.IdeSuggestion, CreateLocalizableResourceString(nameof(UseRegexIsMatchDescription)), isPortedFxCopRule: false, isDataflowRule: false); // Regex.Matches(...).Count => Regex.Count(...) internal static readonly DiagnosticDescriptor UseRegexCountRuleId = DiagnosticDescriptorHelper.Create(RegexCountRuleId, CreateLocalizableResourceString(nameof(UseRegexCountTitle)), CreateLocalizableResourceString(nameof(UseRegexCountMessage)), DiagnosticCategory.Performance, RuleLevel.IdeSuggestion, CreateLocalizableResourceString(nameof(UseRegexCountDescription)), isPortedFxCopRule: false, isDataflowRule: false); public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create( UseRegexIsMatchRuleId, UseRegexCountRuleId); public override void Initialize(AnalysisContext context) { context.EnableConcurrentExecution(); context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); context.RegisterCompilationStartAction(context => { // Require that Regex and supporting types exist. if (!context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemTextRegularExpressionsGroup, out var groupType) || !context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemTextRegularExpressionsMatchCollection, out var matchCollectionType) || !context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemTextRegularExpressionsRegex, out var regexType)) { return; } // Get the various members needed from Regex types. var groupSuccessSymbol = groupType.GetMembers("Success").FirstOrDefault(); var matchCollectionCountSymbol = matchCollectionType.GetMembers("Count").FirstOrDefault(); var regexMatchSymbols = regexType.GetMembers("Match"); var regexMatchesSymbols = regexType.GetMembers("Matches"); var regexIsMatchSymbols = regexType.GetMembers("IsMatch"); var regexCountSymbols = regexType.GetMembers("Count"); // Ensure we have the required member symbols (Regex.Count is optional). if (groupSuccessSymbol is null || matchCollectionCountSymbol is null || regexMatchSymbols.Length == 0 || regexMatchesSymbols.Length == 0 || regexIsMatchSymbols.Length == 0) { return; } // Everything we're looking for is a property, so find all property references. context.RegisterOperationAction(context => { var initialPropRef = (IPropertyReferenceOperation)context.Operation; // Regex.Match(...).Success. Look for Group.Success property access. if (SymbolEqualityComparer.Default.Equals(initialPropRef.Property, groupSuccessSymbol)) { if (initialPropRef.Instance is IInvocationOperation regexMatchInvocation && regexMatchSymbols.Contains(regexMatchInvocation.TargetMethod, SymbolEqualityComparer.Default) && HasMatchingOverload(regexMatchInvocation.TargetMethod, regexIsMatchSymbols)) { context.ReportDiagnostic(initialPropRef.CreateDiagnostic(UseRegexIsMatchRuleId)); } else { return; } return; } // Regex.Matches(...).Count. Look for MatchCollection.Count property access. if (regexCountSymbols.Length != 0 && SymbolEqualityComparer.Default.Equals(initialPropRef.Property, matchCollectionCountSymbol)) { if (initialPropRef.Instance is IInvocationOperation regexMatchesInvocation && regexMatchesSymbols.Contains(regexMatchesInvocation.TargetMethod, SymbolEqualityComparer.Default) && HasMatchingOverload(regexMatchesInvocation.TargetMethod, regexCountSymbols)) { context.ReportDiagnostic(initialPropRef.CreateDiagnostic(UseRegexCountRuleId)); } return; } // Look in overloads to see whether any of the methods there have exactly the same parameters // by type as does the target method. static bool HasMatchingOverload(ISymbol target, ImmutableArray<ISymbol> overloads) { ImmutableArray<IParameterSymbol> targetParameters = target.GetParameters(); foreach (ISymbol overload in overloads) { if (ParameterTypesMatch(targetParameters, overload.GetParameters())) { return true; } } return false; } // Checks whether the two lists of parameters have the same types in the same order. static bool ParameterTypesMatch(ImmutableArray<IParameterSymbol> left, ImmutableArray<IParameterSymbol> right) { if (left.Length != right.Length) { return false; } for (int i = 0; i < left.Length; i++) { if (!SymbolEqualityComparer.Default.Equals(left[i].Type, right[i].Type)) { return false; } } return true; } }, OperationKind.PropertyReference); }); } } }