From 041b9ac7bea926f66924aa41a99e0890753a4da2 Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Fri, 24 Jan 2025 16:28:25 -0500 Subject: [PATCH] Add analyzers for Regex.Match(...).Success and Regex.Matches(...).Count --- .../Core/AnalyzerReleases.Unshipped.md | 2 + .../MicrosoftNetCoreAnalyzersResources.resx | 24 ++ .../Runtime/UseRegexMembers.cs | 155 +++++++++++ .../Runtime/UseRegexMembersFixer.cs | 81 ++++++ .../MicrosoftNetCoreAnalyzersResources.cs.xlf | 40 +++ .../MicrosoftNetCoreAnalyzersResources.de.xlf | 40 +++ .../MicrosoftNetCoreAnalyzersResources.es.xlf | 40 +++ .../MicrosoftNetCoreAnalyzersResources.fr.xlf | 40 +++ .../MicrosoftNetCoreAnalyzersResources.it.xlf | 40 +++ .../MicrosoftNetCoreAnalyzersResources.ja.xlf | 40 +++ .../MicrosoftNetCoreAnalyzersResources.ko.xlf | 40 +++ .../MicrosoftNetCoreAnalyzersResources.pl.xlf | 40 +++ ...crosoftNetCoreAnalyzersResources.pt-BR.xlf | 40 +++ .../MicrosoftNetCoreAnalyzersResources.ru.xlf | 40 +++ .../MicrosoftNetCoreAnalyzersResources.tr.xlf | 40 +++ ...osoftNetCoreAnalyzersResources.zh-Hans.xlf | 40 +++ ...osoftNetCoreAnalyzersResources.zh-Hant.xlf | 40 +++ .../Microsoft.CodeAnalysis.NetAnalyzers.md | 24 ++ .../Microsoft.CodeAnalysis.NetAnalyzers.sarif | 40 +++ src/NetAnalyzers/RulesMissingDocumentation.md | 2 + .../Runtime/UseRegexMembersTests.cs | 253 ++++++++++++++++++ .../DiagnosticCategoryAndIdRanges.txt | 2 +- src/Utilities/Compiler/WellKnownTypeNames.cs | 2 + 23 files changed, 1104 insertions(+), 1 deletion(-) create mode 100644 src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseRegexMembers.cs create mode 100644 src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseRegexMembersFixer.cs create mode 100644 src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseRegexMembersTests.cs diff --git a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md index 579df745a8..1daebbc0fb 100644 --- a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md +++ b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md @@ -5,5 +5,7 @@ Rule ID | Category | Severity | Notes --------|----------|----------|------- CA1873 | Performance | Info | AvoidPotentiallyExpensiveCallWhenLoggingAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1873) +CA1874 | Performance | Info | UseRegexMembers, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1874) +CA1875 | Performance | Info | UseRegexMembers, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1875) CA2023 | Reliability | Warning | LoggerMessageDefineAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2023) CA2024 | Reliability | Warning | DoNotUseEndOfStreamInAsyncMethods, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2024) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx index 3de9a32206..55948fcbc8 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx @@ -1376,6 +1376,30 @@ Use 'Environment.CurrentManagedThreadId' + + 'Regex.Count' is simpler and faster than 'Regex.Matches(...).Count'. + + + Use 'Regex.Count' instead of 'Regex.Matches(...).Count' + + + Use 'Regex.Count' + + + Use 'Regex.Count' + + + 'Regex.IsMatch' is simpler and faster than 'Regex.Match(...).Success'. + + + Use 'Regex.IsMatch' instead of 'Regex.Match(...).Success' + + + Use 'Regex.IsMatch' + + + Use 'Regex.IsMatch' + 'ThreadStatic' only affects static fields. When applied to instance fields, it has no impact on behavior. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseRegexMembers.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseRegexMembers.cs new file mode 100644 index 0000000000..c13c849641 --- /dev/null +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseRegexMembers.cs @@ -0,0 +1,155 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +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; + + /// + /// CA1874: + /// CA1875: + /// + [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 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 overloads) + { + ImmutableArray 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 left, ImmutableArray 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); + }); + } + } +} \ No newline at end of file diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseRegexMembersFixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseRegexMembersFixer.cs new file mode 100644 index 0000000000..5bafeb281b --- /dev/null +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseRegexMembersFixer.cs @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using System.Composition; +using System.Linq; +using System.Threading.Tasks; +using Analyzer.Utilities; +using Analyzer.Utilities.Extensions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Operations; + +namespace Microsoft.NetCore.Analyzers.Runtime +{ + using static MicrosoftNetCoreAnalyzersResources; + + /// + /// CA1874: + /// CA1875: + /// + [ExportCodeFixProvider(LanguageNames.CSharp, LanguageNames.VisualBasic), Shared] + public sealed class UseRegexMembersFixer : CodeFixProvider + { + public sealed override ImmutableArray FixableDiagnosticIds { get; } = ImmutableArray.Create( + UseRegexMembers.RegexIsMatchRuleId, + UseRegexMembers.RegexCountRuleId); + + public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + Document doc = context.Document; + SemanticModel model = await doc.GetRequiredSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); + SyntaxNode root = await doc.GetRequiredSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + + if (root.FindNode(context.Span, getInnermostNodeForTie: true) is SyntaxNode node && + model.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemTextRegularExpressionsRegex, out var regexType) && + model.GetOperation(node, context.CancellationToken) is IPropertyReferenceOperation operation && + operation.Instance is IInvocationOperation regexCall) + { + string title, memberName; + switch (context.Diagnostics[0].Id) + { + case UseRegexMembers.RegexIsMatchRuleId: + title = UseRegexIsMatchFix; + memberName = "IsMatch"; + break; + + case UseRegexMembers.RegexCountRuleId: + title = UseRegexCountFix; + memberName = "Count"; + break; + + default: + RoslynDebug.Assert(false, $"Unknown id {context.Diagnostics[0].Id}"); + return; + } + + context.RegisterCodeFix( + CodeAction.Create(title, + async cancellationToken => + { + DocumentEditor editor = await DocumentEditor.CreateAsync(doc, cancellationToken).ConfigureAwait(false); + + var replacement = editor.Generator.InvocationExpression( // swap in new method name, dropping the subsequent parameter access + editor.Generator.MemberAccessExpression( + regexCall.Instance?.Syntax ?? editor.Generator.TypeExpressionForStaticMemberAccess(regexType), + memberName), + regexCall.Arguments.Select(arg => arg.Syntax)); // use the exact same arguments + + editor.ReplaceNode(node, replacement.WithTriviaFrom(node)); + return editor.GetChangedDocument(); + }, + equivalenceKey: title), + context.Diagnostics); + } + } + } +} \ No newline at end of file diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf index 09eda85d7d..52c58d5eae 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf @@ -3163,6 +3163,46 @@ Obecné přetypování (IL unbox.any) používané sekvencí vrácenou metodou E Velikost klíče algoritmu asymetrického šifrování {0} je menší než 2048. Použijte radši algoritmus RSA s velikostí klíče alespoň 2048, ECDH nebo ECDSA. + + 'Regex.Count' is simpler and faster than 'Regex.Matches(...).Count'. + 'Regex.Count' is simpler and faster than 'Regex.Matches(...).Count'. + + + + Use 'Regex.Count' + Use 'Regex.Count' + + + + Use 'Regex.Count' instead of 'Regex.Matches(...).Count' + Use 'Regex.Count' instead of 'Regex.Matches(...).Count' + + + + Use 'Regex.Count' + Use 'Regex.Count' + + + + 'Regex.IsMatch' is simpler and faster than 'Regex.Match(...).Success'. + 'Regex.IsMatch' is simpler and faster than 'Regex.Match(...).Success'. + + + + Use 'Regex.IsMatch' + Use 'Regex.IsMatch' + + + + Use 'Regex.IsMatch' instead of 'Regex.Match(...).Success' + Use 'Regex.IsMatch' instead of 'Regex.Match(...).Success' + + + + Use 'Regex.IsMatch' + Use 'Regex.IsMatch' + + Use 'SearchValues' Používat SearchValues diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf index 9f60a4f73f..162d9ab98d 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf @@ -3163,6 +3163,46 @@ Erweiterungen und benutzerdefinierte Konvertierungen werden bei generischen Type Die Schlüsselgröße des asymmetrischen Verschlüsselungsalgorithmus "{0}" beträgt weniger als 2048. Wechseln Sie stattdessen zu einer RSA-Verschlüsselung mit ECDH- oder ECDSA-Algorithmus mit einer Schlüsselgröße von mindestens 2048. + + 'Regex.Count' is simpler and faster than 'Regex.Matches(...).Count'. + 'Regex.Count' is simpler and faster than 'Regex.Matches(...).Count'. + + + + Use 'Regex.Count' + Use 'Regex.Count' + + + + Use 'Regex.Count' instead of 'Regex.Matches(...).Count' + Use 'Regex.Count' instead of 'Regex.Matches(...).Count' + + + + Use 'Regex.Count' + Use 'Regex.Count' + + + + 'Regex.IsMatch' is simpler and faster than 'Regex.Match(...).Success'. + 'Regex.IsMatch' is simpler and faster than 'Regex.Match(...).Success'. + + + + Use 'Regex.IsMatch' + Use 'Regex.IsMatch' + + + + Use 'Regex.IsMatch' instead of 'Regex.Match(...).Success' + Use 'Regex.IsMatch' instead of 'Regex.Match(...).Success' + + + + Use 'Regex.IsMatch' + Use 'Regex.IsMatch' + + Use 'SearchValues' "SearchValues" verwenden diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf index 58bdd810fd..d18a9ac2f9 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf @@ -3163,6 +3163,46 @@ La ampliación y las conversiones definidas por el usuario no se admiten con tip El tamaño de clave del algoritmo de cifrado asimétrico {0} es inferior a 2048. Cambie a un algoritmo de ECDSA o ECDH con RSA que tenga un tamaño de clave mínimo de 2048. + + 'Regex.Count' is simpler and faster than 'Regex.Matches(...).Count'. + 'Regex.Count' is simpler and faster than 'Regex.Matches(...).Count'. + + + + Use 'Regex.Count' + Use 'Regex.Count' + + + + Use 'Regex.Count' instead of 'Regex.Matches(...).Count' + Use 'Regex.Count' instead of 'Regex.Matches(...).Count' + + + + Use 'Regex.Count' + Use 'Regex.Count' + + + + 'Regex.IsMatch' is simpler and faster than 'Regex.Match(...).Success'. + 'Regex.IsMatch' is simpler and faster than 'Regex.Match(...).Success'. + + + + Use 'Regex.IsMatch' + Use 'Regex.IsMatch' + + + + Use 'Regex.IsMatch' instead of 'Regex.Match(...).Success' + Use 'Regex.IsMatch' instead of 'Regex.Match(...).Success' + + + + Use 'Regex.IsMatch' + Use 'Regex.IsMatch' + + Use 'SearchValues' Usar "SearchValues" diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf index a4f7ef0d7d..1708afee34 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf @@ -3163,6 +3163,46 @@ Les conversions étendues et définies par l’utilisateur ne sont pas prises en La taille de clé de l'algorithme de chiffrement asymétrique {0} est inférieure à 2 048 bits. Passez plutôt à un algorithme RSA avec une taille de clé d'au moins 2 048 bits, à un algorithme ECDH ou à un algorithme ECDSA. + + 'Regex.Count' is simpler and faster than 'Regex.Matches(...).Count'. + 'Regex.Count' is simpler and faster than 'Regex.Matches(...).Count'. + + + + Use 'Regex.Count' + Use 'Regex.Count' + + + + Use 'Regex.Count' instead of 'Regex.Matches(...).Count' + Use 'Regex.Count' instead of 'Regex.Matches(...).Count' + + + + Use 'Regex.Count' + Use 'Regex.Count' + + + + 'Regex.IsMatch' is simpler and faster than 'Regex.Match(...).Success'. + 'Regex.IsMatch' is simpler and faster than 'Regex.Match(...).Success'. + + + + Use 'Regex.IsMatch' + Use 'Regex.IsMatch' + + + + Use 'Regex.IsMatch' instead of 'Regex.Match(...).Success' + Use 'Regex.IsMatch' instead of 'Regex.Match(...).Success' + + + + Use 'Regex.IsMatch' + Use 'Regex.IsMatch' + + Use 'SearchValues' Utiliser « SearchValues » diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf index e55ae22733..378cb11b24 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf @@ -3163,6 +3163,46 @@ L'ampliamento e le conversioni definite dall'utente non sono supportate con tipi La dimensione di chiave dell'algoritmo di crittografia asimmetrica {0} è minore di 2048. Passare a un algoritmo RSA con dimensione di chiave minima pari a 2048 oppure a un algoritmo ECDH o ECDSA. + + 'Regex.Count' is simpler and faster than 'Regex.Matches(...).Count'. + 'Regex.Count' is simpler and faster than 'Regex.Matches(...).Count'. + + + + Use 'Regex.Count' + Use 'Regex.Count' + + + + Use 'Regex.Count' instead of 'Regex.Matches(...).Count' + Use 'Regex.Count' instead of 'Regex.Matches(...).Count' + + + + Use 'Regex.Count' + Use 'Regex.Count' + + + + 'Regex.IsMatch' is simpler and faster than 'Regex.Match(...).Success'. + 'Regex.IsMatch' is simpler and faster than 'Regex.Match(...).Success'. + + + + Use 'Regex.IsMatch' + Use 'Regex.IsMatch' + + + + Use 'Regex.IsMatch' instead of 'Regex.Match(...).Success' + Use 'Regex.IsMatch' instead of 'Regex.Match(...).Success' + + + + Use 'Regex.IsMatch' + Use 'Regex.IsMatch' + + Use 'SearchValues' Usare 'SearchValues' diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf index baa1b0b00f..e55b5e9d8d 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf @@ -3163,6 +3163,46 @@ Enumerable.OfType<T> で使用されるジェネリック型チェック ( 非対称暗号化アルゴリズム {0} のキー サイズが 2048 未満です。キー サイズが少なくとも 2048 の RSA、ECDH、または ECDSA アルゴリズムに切り替えてください。 + + 'Regex.Count' is simpler and faster than 'Regex.Matches(...).Count'. + 'Regex.Count' is simpler and faster than 'Regex.Matches(...).Count'. + + + + Use 'Regex.Count' + Use 'Regex.Count' + + + + Use 'Regex.Count' instead of 'Regex.Matches(...).Count' + Use 'Regex.Count' instead of 'Regex.Matches(...).Count' + + + + Use 'Regex.Count' + Use 'Regex.Count' + + + + 'Regex.IsMatch' is simpler and faster than 'Regex.Match(...).Success'. + 'Regex.IsMatch' is simpler and faster than 'Regex.Match(...).Success'. + + + + Use 'Regex.IsMatch' + Use 'Regex.IsMatch' + + + + Use 'Regex.IsMatch' instead of 'Regex.Match(...).Success' + Use 'Regex.IsMatch' instead of 'Regex.Match(...).Success' + + + + Use 'Regex.IsMatch' + Use 'Regex.IsMatch' + + Use 'SearchValues' 'SearchValues' を使用する diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf index 0ae09fed49..8aa978f260 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf @@ -3163,6 +3163,46 @@ Enumerable.OfType<T>에서 사용하는 제네릭 형식 검사(C# 'is' 비대칭 암호화 알고리즘 {0}의 키 크기가 2048보다 작습니다. 최소 2048 키 크기, ECDH 또는 ECDSA 알고리즘이 포함된 RSA로 전환하세요. + + 'Regex.Count' is simpler and faster than 'Regex.Matches(...).Count'. + 'Regex.Count' is simpler and faster than 'Regex.Matches(...).Count'. + + + + Use 'Regex.Count' + Use 'Regex.Count' + + + + Use 'Regex.Count' instead of 'Regex.Matches(...).Count' + Use 'Regex.Count' instead of 'Regex.Matches(...).Count' + + + + Use 'Regex.Count' + Use 'Regex.Count' + + + + 'Regex.IsMatch' is simpler and faster than 'Regex.Match(...).Success'. + 'Regex.IsMatch' is simpler and faster than 'Regex.Match(...).Success'. + + + + Use 'Regex.IsMatch' + Use 'Regex.IsMatch' + + + + Use 'Regex.IsMatch' instead of 'Regex.Match(...).Success' + Use 'Regex.IsMatch' instead of 'Regex.Match(...).Success' + + + + Use 'Regex.IsMatch' + Use 'Regex.IsMatch' + + Use 'SearchValues' 'SearchValues' 사용 diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf index 88e60d33a2..a5c6b09a36 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf @@ -3163,6 +3163,46 @@ Konwersje poszerzane i zdefiniowane przez użytkownika nie są obsługiwane w pr Rozmiar klucza algorytmu szyfrowania asymetrycznego {0} jest mniejszy niż 2048. Przełącz się na algorytm RSA z kluczem o rozmiarze co najmniej 2048, algorytm ECDH lub algorytm ECDSA. + + 'Regex.Count' is simpler and faster than 'Regex.Matches(...).Count'. + 'Regex.Count' is simpler and faster than 'Regex.Matches(...).Count'. + + + + Use 'Regex.Count' + Use 'Regex.Count' + + + + Use 'Regex.Count' instead of 'Regex.Matches(...).Count' + Use 'Regex.Count' instead of 'Regex.Matches(...).Count' + + + + Use 'Regex.Count' + Use 'Regex.Count' + + + + 'Regex.IsMatch' is simpler and faster than 'Regex.Match(...).Success'. + 'Regex.IsMatch' is simpler and faster than 'Regex.Match(...).Success'. + + + + Use 'Regex.IsMatch' + Use 'Regex.IsMatch' + + + + Use 'Regex.IsMatch' instead of 'Regex.Match(...).Success' + Use 'Regex.IsMatch' instead of 'Regex.Match(...).Success' + + + + Use 'Regex.IsMatch' + Use 'Regex.IsMatch' + + Use 'SearchValues' Użyj elementu „SearchValues” diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf index d5ded437ee..973fa01fc3 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf @@ -3163,6 +3163,46 @@ Ampliação e conversões definidas pelo usuário não são compatíveis com tip O tamanho de chave do algoritmo de criptografia assimétrica {0} é menor que 2048. Alterne para um RSA com um tamanho de chave de pelo menos 2048 ou para um algoritmo ECDH ou ECDSA. + + 'Regex.Count' is simpler and faster than 'Regex.Matches(...).Count'. + 'Regex.Count' is simpler and faster than 'Regex.Matches(...).Count'. + + + + Use 'Regex.Count' + Use 'Regex.Count' + + + + Use 'Regex.Count' instead of 'Regex.Matches(...).Count' + Use 'Regex.Count' instead of 'Regex.Matches(...).Count' + + + + Use 'Regex.Count' + Use 'Regex.Count' + + + + 'Regex.IsMatch' is simpler and faster than 'Regex.Match(...).Success'. + 'Regex.IsMatch' is simpler and faster than 'Regex.Match(...).Success'. + + + + Use 'Regex.IsMatch' + Use 'Regex.IsMatch' + + + + Use 'Regex.IsMatch' instead of 'Regex.Match(...).Success' + Use 'Regex.IsMatch' instead of 'Regex.Match(...).Success' + + + + Use 'Regex.IsMatch' + Use 'Regex.IsMatch' + + Use 'SearchValues' Usar 'SearchValues' diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf index 2257594ed7..cc35437b19 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf @@ -3163,6 +3163,46 @@ Widening and user defined conversions are not supported with generic types.Размер ключа для алгоритма асимметричного шифрования {0} меньше 2048 бит. Перейдите на алгоритм RSA с размером ключа не менее 2048 бит или на алгоритмы ECDH или ECDSA. + + 'Regex.Count' is simpler and faster than 'Regex.Matches(...).Count'. + 'Regex.Count' is simpler and faster than 'Regex.Matches(...).Count'. + + + + Use 'Regex.Count' + Use 'Regex.Count' + + + + Use 'Regex.Count' instead of 'Regex.Matches(...).Count' + Use 'Regex.Count' instead of 'Regex.Matches(...).Count' + + + + Use 'Regex.Count' + Use 'Regex.Count' + + + + 'Regex.IsMatch' is simpler and faster than 'Regex.Match(...).Success'. + 'Regex.IsMatch' is simpler and faster than 'Regex.Match(...).Success'. + + + + Use 'Regex.IsMatch' + Use 'Regex.IsMatch' + + + + Use 'Regex.IsMatch' instead of 'Regex.Match(...).Success' + Use 'Regex.IsMatch' instead of 'Regex.Match(...).Success' + + + + Use 'Regex.IsMatch' + Use 'Regex.IsMatch' + + Use 'SearchValues' Использовать "SearchValues" diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf index 868029639b..74f631661e 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf @@ -3163,6 +3163,46 @@ Genel türlerde genişletme ve kullanıcı tanımlı dönüştürmeler desteklen Asimetrik şifreleme algoritması {0} anahtar boyutu 2048'den az. Bunun yerine en az 2048 anahtar boyutuna, ECDH veya ECDSA algoritmasına sahip bir RSA'ya geçiş yapın. + + 'Regex.Count' is simpler and faster than 'Regex.Matches(...).Count'. + 'Regex.Count' is simpler and faster than 'Regex.Matches(...).Count'. + + + + Use 'Regex.Count' + Use 'Regex.Count' + + + + Use 'Regex.Count' instead of 'Regex.Matches(...).Count' + Use 'Regex.Count' instead of 'Regex.Matches(...).Count' + + + + Use 'Regex.Count' + Use 'Regex.Count' + + + + 'Regex.IsMatch' is simpler and faster than 'Regex.Match(...).Success'. + 'Regex.IsMatch' is simpler and faster than 'Regex.Match(...).Success'. + + + + Use 'Regex.IsMatch' + Use 'Regex.IsMatch' + + + + Use 'Regex.IsMatch' instead of 'Regex.Match(...).Success' + Use 'Regex.IsMatch' instead of 'Regex.Match(...).Success' + + + + Use 'Regex.IsMatch' + Use 'Regex.IsMatch' + + Use 'SearchValues' 'SearchValues' kullan diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf index 86f3156882..8318184b26 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf @@ -3163,6 +3163,46 @@ Enumerable.OfType<T> 使用的泛型类型检查 (C# 'is' operator/IL 'isi 非对称加密算法 {0} 的密钥大小小于 2048 位。请转而切换到至少具有 2048 位密钥大小的 RSA、ECDH 或者 ECDSA 算法。 + + 'Regex.Count' is simpler and faster than 'Regex.Matches(...).Count'. + 'Regex.Count' is simpler and faster than 'Regex.Matches(...).Count'. + + + + Use 'Regex.Count' + Use 'Regex.Count' + + + + Use 'Regex.Count' instead of 'Regex.Matches(...).Count' + Use 'Regex.Count' instead of 'Regex.Matches(...).Count' + + + + Use 'Regex.Count' + Use 'Regex.Count' + + + + 'Regex.IsMatch' is simpler and faster than 'Regex.Match(...).Success'. + 'Regex.IsMatch' is simpler and faster than 'Regex.Match(...).Success'. + + + + Use 'Regex.IsMatch' + Use 'Regex.IsMatch' + + + + Use 'Regex.IsMatch' instead of 'Regex.Match(...).Success' + Use 'Regex.IsMatch' instead of 'Regex.Match(...).Success' + + + + Use 'Regex.IsMatch' + Use 'Regex.IsMatch' + + Use 'SearchValues' 使用 "SearchValues" diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf index 60b58f36ef..153070cd4f 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf @@ -3163,6 +3163,46 @@ Enumerable.OfType<T> 使用的一般型別檢查 (C# 'is' operator/IL 'isi 非對稱式加密演算法 {0} 的金鑰大小小於 2048。請改為切換成至少有 2048 金鑰大小的 RSA、ECDH 或 ECDSA 演算法。 + + 'Regex.Count' is simpler and faster than 'Regex.Matches(...).Count'. + 'Regex.Count' is simpler and faster than 'Regex.Matches(...).Count'. + + + + Use 'Regex.Count' + Use 'Regex.Count' + + + + Use 'Regex.Count' instead of 'Regex.Matches(...).Count' + Use 'Regex.Count' instead of 'Regex.Matches(...).Count' + + + + Use 'Regex.Count' + Use 'Regex.Count' + + + + 'Regex.IsMatch' is simpler and faster than 'Regex.Match(...).Success'. + 'Regex.IsMatch' is simpler and faster than 'Regex.Match(...).Success'. + + + + Use 'Regex.IsMatch' + Use 'Regex.IsMatch' + + + + Use 'Regex.IsMatch' instead of 'Regex.Match(...).Success' + Use 'Regex.IsMatch' instead of 'Regex.Match(...).Success' + + + + Use 'Regex.IsMatch' + Use 'Regex.IsMatch' + + Use 'SearchValues' 使用 'SearchValues' diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md index bfb110318e..a9f675abf0 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md @@ -1872,6 +1872,30 @@ In many situations, logging is disabled or set to a log level that results in an |CodeFix|False| --- +## [CA1874](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1874): Use 'Regex.IsMatch' + +'Regex.IsMatch' is simpler and faster than 'Regex.Match(...).Success'. + +|Item|Value| +|-|-| +|Category|Performance| +|Enabled|True| +|Severity|Info| +|CodeFix|True| +--- + +## [CA1875](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1875): Use 'Regex.Count' + +'Regex.Count' is simpler and faster than 'Regex.Matches(...).Count'. + +|Item|Value| +|-|-| +|Category|Performance| +|Enabled|True| +|Severity|Info| +|CodeFix|True| +--- + ## [CA2000](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000): Dispose objects before losing scope If a disposable object is not explicitly disposed before all references to it are out of scope, the object will be disposed at some indeterminate time when the garbage collector runs the finalizer of the object. Because an exceptional event might occur that will prevent the finalizer of the object from running, the object should be explicitly disposed instead. diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif index d6d19ab152..a46fddbcd2 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif @@ -3448,6 +3448,46 @@ ] } }, + "CA1874": { + "id": "CA1874", + "shortDescription": "Use 'Regex.IsMatch'", + "fullDescription": "'Regex.IsMatch' is simpler and faster than 'Regex.Match(...).Success'.", + "defaultLevel": "note", + "helpUri": "https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1874", + "properties": { + "category": "Performance", + "isEnabledByDefault": true, + "typeName": "UseRegexMembers", + "languages": [ + "C#", + "Visual Basic" + ], + "tags": [ + "Telemetry", + "EnabledRuleInAggressiveMode" + ] + } + }, + "CA1875": { + "id": "CA1875", + "shortDescription": "Use 'Regex.Count'", + "fullDescription": "'Regex.Count' is simpler and faster than 'Regex.Matches(...).Count'.", + "defaultLevel": "note", + "helpUri": "https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1875", + "properties": { + "category": "Performance", + "isEnabledByDefault": true, + "typeName": "UseRegexMembers", + "languages": [ + "C#", + "Visual Basic" + ], + "tags": [ + "Telemetry", + "EnabledRuleInAggressiveMode" + ] + } + }, "CA2000": { "id": "CA2000", "shortDescription": "Dispose objects before losing scope", diff --git a/src/NetAnalyzers/RulesMissingDocumentation.md b/src/NetAnalyzers/RulesMissingDocumentation.md index aca4ac13b4..c5820fde91 100644 --- a/src/NetAnalyzers/RulesMissingDocumentation.md +++ b/src/NetAnalyzers/RulesMissingDocumentation.md @@ -3,4 +3,6 @@ Rule ID | Missing Help Link | Title | --------|-------------------|-------| CA1873 | | Avoid potentially expensive logging | +CA1874 | | Use 'Regex.IsMatch' | +CA1875 | | Use 'Regex.Count' | CA2023 | | Invalid braces in message template | diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseRegexMembersTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseRegexMembersTests.cs new file mode 100644 index 0000000000..0e57358652 --- /dev/null +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseRegexMembersTests.cs @@ -0,0 +1,253 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Microsoft.CodeAnalysis.Testing; +using Xunit; +using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< + Microsoft.NetCore.Analyzers.Runtime.UseRegexMembers, + Microsoft.NetCore.Analyzers.Runtime.UseRegexMembersFixer>; +using VerifyVB = Test.Utilities.VisualBasicCodeFixVerifier< + Microsoft.NetCore.Analyzers.Runtime.UseRegexMembers, + Microsoft.NetCore.Analyzers.Runtime.UseRegexMembersFixer>; + +namespace Microsoft.NetCore.Analyzers.Runtime.UnitTests +{ + public class UseRegexMembersTests + { + [Fact] + public async Task Regex_MatchToIsMatch_CSharpAsync() + { + await VerifyCS.VerifyCodeFixAsync(""" + using System; + using System.Text.RegularExpressions; + + class C + { + bool HandleMatchSuccess_Instance(Regex r) + { + Match m = r.Match("input"); + bool success = m.Success; + + m = r.Match("input"); + if (m.Success) { } + + success = {|CA1874:r.Match("input").Success|}; + success = r.Match("input", 0, 5).Success; // no corresponding IsMatch overload + Use({|CA1874:r.Match("input", 1)/* will be removed */.Success|}); + Use("test", + {|CA1874:r.Match("input", 2).Success|}); + Use("test", + r.Match("input", 1, 3).Success, // no corresponding IsMatch overload + 0.0); + return {|CA1874:r.Match("input").Success|}; + } + + bool HandleMatchSuccess_Static() + { + Match m = Regex.Match("input", "pattern"); + bool success = m.Success; + + m = Regex.Match("input", "pattern"); + if (m.Success) { } + + success = {|CA1874:Regex.Match("input", "pattern").Success|}; + success = {|CA1874:Regex.Match("input", "pattern")/*willberemoved*/.Success|}; + Use({|CA1874:Regex.Match("input", "pattern", RegexOptions.Compiled).Success|}); + Use("test", + {|CA1874:Regex.Match("input", "pattern", RegexOptions.IgnoreCase, TimeSpan.FromSeconds(10)).Success|}); + Use("test", + {|CA1874:Regex.Match("input", "pattern", RegexOptions.Compiled | RegexOptions.IgnoreCase).Success|} /* comment */, + 0.0); + return {|CA1874:Regex.Match("input", "pattern").Success|}; + } + + void Use(bool success) {} + void Use(string something, bool success) {} + void Use(string something, bool success, double somethingElse) { } + } + """, + """ + using System; + using System.Text.RegularExpressions; + + class C + { + bool HandleMatchSuccess_Instance(Regex r) + { + Match m = r.Match("input"); + bool success = m.Success; + + m = r.Match("input"); + if (m.Success) { } + + success = r.IsMatch("input"); + success = r.Match("input", 0, 5).Success; // no corresponding IsMatch overload + Use(r.IsMatch("input", 1)); + Use("test", + r.IsMatch("input", 2)); + Use("test", + r.Match("input", 1, 3).Success, // no corresponding IsMatch overload + 0.0); + return r.IsMatch("input"); + } + + bool HandleMatchSuccess_Static() + { + Match m = Regex.Match("input", "pattern"); + bool success = m.Success; + + m = Regex.Match("input", "pattern"); + if (m.Success) { } + + success = Regex.IsMatch("input", "pattern"); + success = Regex.IsMatch("input", "pattern"); + Use(Regex.IsMatch("input", "pattern", RegexOptions.Compiled)); + Use("test", + Regex.IsMatch("input", "pattern", RegexOptions.IgnoreCase, TimeSpan.FromSeconds(10))); + Use("test", + Regex.IsMatch("input", "pattern", RegexOptions.Compiled | RegexOptions.IgnoreCase) /* comment */, + 0.0); + return Regex.IsMatch("input", "pattern"); + } + + void Use(bool success) {} + void Use(string something, bool success) {} + void Use(string something, bool success, double somethingElse) { } + } + """); + } + + [Fact] + public async Task Regex_MatchToIsMatch_VisualBasicAsync() + { + await VerifyVB.VerifyCodeFixAsync(""" + Imports System + Imports System.Text.RegularExpressions + + Class C + Function HandleMatchSuccess_Instance(r As Regex) As Boolean + Return {|CA1874:r.Match("input", 2).Success|} + End Function + + Function HandleMatchSuccess_Static() As Boolean + Return {|CA1874:Regex.Match("input", "pattern").Success|} + End Function + End Class + """, + """ + Imports System + Imports System.Text.RegularExpressions + + Class C + Function HandleMatchSuccess_Instance(r As Regex) As Boolean + Return r.IsMatch("input", 2) + End Function + + Function HandleMatchSuccess_Static() As Boolean + Return Regex.IsMatch("input", "pattern") + End Function + End Class + """); + } + + [Fact] + public async Task Regex_MatchesToCount_CSharpAsync() + { + await new VerifyCS.Test() + { + ReferenceAssemblies = ReferenceAssemblies.Net.Net90, + TestCode = /* lang=c#-test */ """ + using System; + using System.Text.RegularExpressions; + + class C + { + int HandleMatchesCount_Instance(Regex r) + { + var m = r.Matches("input"); + int count = m.Count; + + m = r.Matches("input"); + if (m.Count != 0) { } + + count = {|CA1875:r.Matches("input").Count|}; + Use(r.Matches("input", 1).Count); // no corresponding Count overload + return {|CA1875:r.Matches("input").Count|}; + } + + int HandleMatchesCount_Static() + { + var m = Regex.Matches("input", "pattern"); + int count = m.Count; + + m = Regex.Matches("input", "pattern"); + if (m.Count != 0) { } + + count = {|CA1875:Regex.Matches("input", "pattern").Count|}; + Use({|CA1875:Regex.Matches("input", "pattern", RegexOptions.Compiled).Count|}); + return {|CA1875:Regex.Matches("input", "pattern", RegexOptions.Compiled, TimeSpan.FromMinutes(1)).Count|}; + } + + void Use(int count) {} + } + """, + FixedCode = /* lang=c#-test */ """ + using System; + using System.Text.RegularExpressions; + + class C + { + int HandleMatchesCount_Instance(Regex r) + { + var m = r.Matches("input"); + int count = m.Count; + + m = r.Matches("input"); + if (m.Count != 0) { } + + count = r.Count("input"); + Use(r.Matches("input", 1).Count); // no corresponding Count overload + return r.Count("input"); + } + + int HandleMatchesCount_Static() + { + var m = Regex.Matches("input", "pattern"); + int count = m.Count; + + m = Regex.Matches("input", "pattern"); + if (m.Count != 0) { } + + count = Regex.Count("input", "pattern"); + Use(Regex.Count("input", "pattern", RegexOptions.Compiled)); + return Regex.Count("input", "pattern", RegexOptions.Compiled, TimeSpan.FromMinutes(1)); + } + + void Use(int count) {} + } + """ + }.RunAsync(); + } + + [Fact] + public async Task NoDiagnostics_NoRegexCount_CSharpAsync() + { + await new VerifyCS.Test() + { + ReferenceAssemblies = ReferenceAssemblies.NetFramework.Net48.Default, + TestCode = /* lang=c#-test */ """ + using System.Text.RegularExpressions; + + class C + { + void M(Regex r) + { + int count1 = r.Matches("input").Count; + int count2 = Regex.Matches("input", "pattern").Count; + } + } + """ + }.RunAsync(); + } + } +} \ No newline at end of file diff --git a/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt b/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt index 7f78652b66..c97e0bab9f 100644 --- a/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt +++ b/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt @@ -12,7 +12,7 @@ Design: CA2210, CA1000-CA1070 Globalization: CA2101, CA1300-CA1311 Mobility: CA1600-CA1601 -Performance: HA, CA1800-CA1873 +Performance: HA, CA1800-CA1875 Security: CA2100-CA2153, CA2300-CA2330, CA3000-CA3147, CA5300-CA5405 Usage: CA1801, CA1806, CA1816, CA2200-CA2209, CA2211-CA2265 Naming: CA1700-CA1727 diff --git a/src/Utilities/Compiler/WellKnownTypeNames.cs b/src/Utilities/Compiler/WellKnownTypeNames.cs index 82c6b00ff5..c39c0e9a61 100644 --- a/src/Utilities/Compiler/WellKnownTypeNames.cs +++ b/src/Utilities/Compiler/WellKnownTypeNames.cs @@ -423,6 +423,8 @@ internal static class WellKnownTypeNames public const string SystemTextEncoding = "System.Text.Encoding"; public const string SystemTextJsonJsonSerializerOptions = "System.Text.Json.JsonSerializerOptions"; public const string SystemTextJsonJsonSerializer = "System.Text.Json.JsonSerializer"; + public const string SystemTextRegularExpressionsGroup = "System.Text.RegularExpressions.Group"; + public const string SystemTextRegularExpressionsMatchCollection = "System.Text.RegularExpressions.MatchCollection"; public const string SystemTextRegularExpressionsRegex = "System.Text.RegularExpressions.Regex"; public const string SystemTextStringBuilder = "System.Text.StringBuilder"; public const string SystemThreadStaticAttribute = "System.ThreadStaticAttribute";