diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpRecommendCaseInsensitiveStringComparisonFixer.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpRecommendCaseInsensitiveStringComparisonFixer.cs index 61fef280f9..5e4e66b03b 100644 --- a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpRecommendCaseInsensitiveStringComparisonFixer.cs +++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Performance/CSharpRecommendCaseInsensitiveStringComparisonFixer.cs @@ -17,7 +17,7 @@ namespace Microsoft.NetCore.CSharp.Analyzers.Performance [ExportCodeFixProvider(LanguageNames.CSharp), Shared] public sealed class CSharpRecommendCaseInsensitiveStringComparisonFixer : RecommendCaseInsensitiveStringComparisonFixer { - protected override List GetNewArguments(SyntaxGenerator generator, IInvocationOperation mainInvocationOperation, + protected override IEnumerable GetNewArgumentsForInvocation(SyntaxGenerator generator, string caseChangingApproachValue, IInvocationOperation mainInvocationOperation, INamedTypeSymbol stringComparisonType, out SyntaxNode? mainInvocationInstance) { List arguments = new(); @@ -25,14 +25,12 @@ protected override List GetNewArguments(SyntaxGenerator generator, I InvocationExpressionSyntax invocationExpression = (InvocationExpressionSyntax)mainInvocationOperation.Syntax; - string? caseChangingApproachName = null; bool isChangingCaseInArgument = false; - mainInvocationInstance = null; if (invocationExpression.Expression is MemberAccessExpressionSyntax memberAccessExpression) { - var internalExpression = memberAccessExpression.Expression is ParenthesizedExpressionSyntax parenthesizedExpression ? + ExpressionSyntax internalExpression = memberAccessExpression.Expression is ParenthesizedExpressionSyntax parenthesizedExpression ? parenthesizedExpression.Expression : memberAccessExpression.Expression; @@ -40,7 +38,6 @@ protected override List GetNewArguments(SyntaxGenerator generator, I internalInvocationExpression.Expression is MemberAccessExpressionSyntax internalMemberAccessExpression) { mainInvocationInstance = internalMemberAccessExpression.Expression; - caseChangingApproachName = GetCaseChangingApproach(internalMemberAccessExpression.Name.Identifier.ValueText); } else { @@ -59,10 +56,9 @@ protected override List GetNewArguments(SyntaxGenerator generator, I node.Expression; MemberAccessExpressionSyntax? argumentMemberAccessExpression = null; - if (argumentExpression is InvocationExpressionSyntax argumentInvocationExpression && - (argumentMemberAccessExpression = argumentInvocationExpression.Expression as MemberAccessExpressionSyntax) != null) + if (argumentExpression is InvocationExpressionSyntax argumentInvocationExpression) { - caseChangingApproachName = GetCaseChangingApproach(argumentMemberAccessExpression.Name.Identifier.ValueText); + argumentMemberAccessExpression = argumentInvocationExpression.Expression as MemberAccessExpressionSyntax; } SyntaxNode newArgumentNode; @@ -84,18 +80,23 @@ protected override List GetNewArguments(SyntaxGenerator generator, I newArgumentNode = node; } - arguments.Add(newArgumentNode); + arguments.Add(newArgumentNode.WithTriviaFrom(node)); } - Debug.Assert(caseChangingApproachName != null); Debug.Assert(mainInvocationInstance != null); - SyntaxNode stringComparisonArgument = GetNewStringComparisonArgument(generator, - stringComparisonType, caseChangingApproachName!, isAnyArgumentNamed); + SyntaxNode stringComparisonArgument = GetNewStringComparisonArgument(generator, stringComparisonType, caseChangingApproachValue, isAnyArgumentNamed); arguments.Add(stringComparisonArgument); return arguments; } + + protected override IEnumerable GetNewArgumentsForBinary(SyntaxGenerator generator, SyntaxNode rightNode, SyntaxNode typeMemberAccess) => + new List() + { + generator.Argument(rightNode), + generator.Argument(typeMemberAccess) + }; } } \ No newline at end of file diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx index 571ee4ee1f..be191199c1 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx @@ -2066,14 +2066,20 @@ Widening and user defined conversions are not supported with generic types. Use the 'string.{0}(string, StringComparison)' overload - - Prefer using 'StringComparer' to perform case-insensitive string comparisons + + Use 'string.Equals(string, StringComparison)' Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons when using 'CompareTo', because they lead to an allocation. Instead, use 'StringComparer' to perform case-insensitive comparisons. Prefer using 'StringComparer' to perform a case-insensitive comparison + + + Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons, as in 'string.ToLower() == string.ToLower()', because they lead to an allocation. Instead, use 'string.Equals(string, StringComparison)' to perform case-insensitive comparisons. + + + Prefer using 'string.Equals(string, StringComparison)' to perform a case-insensitive comparison Prefer the 'StringComparison' method overloads to perform case-insensitive string comparisons diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/RecommendCaseInsensitiveStringComparison.Analyzer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/RecommendCaseInsensitiveStringComparison.Analyzer.cs index 584672eb5b..b77cc41997 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/RecommendCaseInsensitiveStringComparison.Analyzer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/RecommendCaseInsensitiveStringComparison.Analyzer.cs @@ -1,7 +1,10 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. +using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Analyzer.Utilities; using Analyzer.Utilities.Extensions; @@ -32,8 +35,13 @@ public sealed class RecommendCaseInsensitiveStringComparisonAnalyzer : Diagnosti internal const string StringStartsWithMethodName = "StartsWith"; internal const string StringCompareToMethodName = "CompareTo"; internal const string StringComparerCompareMethodName = "Compare"; + internal const string StringEqualsMethodName = "Equals"; internal const string StringParameterName = "value"; internal const string StringComparisonParameterName = "comparisonType"; + internal const string CaseChangingApproachName = "CaseChangingApproach"; + internal const string OffendingMethodName = "OffendingMethodName"; + internal const string LeftOffendingMethodName = "LeftOffendingMethodName"; + internal const string RightOffendingMethodName = "RightOffendingMethodName"; internal static readonly DiagnosticDescriptor RecommendCaseInsensitiveStringComparisonRule = DiagnosticDescriptorHelper.Create( RuleId, @@ -47,7 +55,7 @@ public sealed class RecommendCaseInsensitiveStringComparisonAnalyzer : Diagnosti internal static readonly DiagnosticDescriptor RecommendCaseInsensitiveStringComparerRule = DiagnosticDescriptorHelper.Create( RuleId, - CreateLocalizableResourceString(nameof(RecommendCaseInsensitiveStringComparerTitle)), + CreateLocalizableResourceString(nameof(RecommendCaseInsensitiveStringComparisonTitle)), CreateLocalizableResourceString(nameof(RecommendCaseInsensitiveStringComparerMessage)), DiagnosticCategory.Performance, RuleLevel.IdeSuggestion, @@ -55,8 +63,18 @@ public sealed class RecommendCaseInsensitiveStringComparisonAnalyzer : Diagnosti isPortedFxCopRule: false, isDataflowRule: false); + internal static readonly DiagnosticDescriptor RecommendCaseInsensitiveStringEqualsRule = DiagnosticDescriptorHelper.Create( + RuleId, + CreateLocalizableResourceString(nameof(RecommendCaseInsensitiveStringComparisonTitle)), + CreateLocalizableResourceString(nameof(RecommendCaseInsensitiveStringEqualsMessage)), + DiagnosticCategory.Performance, + RuleLevel.IdeSuggestion, + CreateLocalizableResourceString(nameof(RecommendCaseInsensitiveStringEqualsDescription)), + isPortedFxCopRule: false, + isDataflowRule: false); + public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create( - RecommendCaseInsensitiveStringComparisonRule, RecommendCaseInsensitiveStringComparerRule); + RecommendCaseInsensitiveStringComparisonRule, RecommendCaseInsensitiveStringComparerRule, RecommendCaseInsensitiveStringEqualsRule); public override void Initialize(AnalysisContext context) { @@ -115,18 +133,12 @@ private void AnalyzeCompilationStart(CompilationStartAnalysisContext context) ParameterInfo.GetParameterInfo(stringType) }; - ParameterInfo[] stringInt32Parameters = new[] - { - ParameterInfo.GetParameterInfo(stringType), - ParameterInfo.GetParameterInfo(int32Type) - }; - - ParameterInfo[] stringInt32Int32Parameters = new[] + // Equals(string) + IMethodSymbol? stringEqualsStringMethod = stringType.GetMembers(StringEqualsMethodName).OfType().GetFirstOrDefaultMemberWithParameterInfos(stringParameter); + if (stringEqualsStringMethod == null) { - ParameterInfo.GetParameterInfo(stringType), - ParameterInfo.GetParameterInfo(int32Type), - ParameterInfo.GetParameterInfo(int32Type) - }; + return; + } // Retrieve the diagnosable string overload methods: Contains, IndexOf (3 overloads), StartsWith, CompareTo @@ -153,6 +165,12 @@ private void AnalyzeCompilationStart(CompilationStartAnalysisContext context) return; } + ParameterInfo[] stringInt32Parameters = new[] + { + ParameterInfo.GetParameterInfo(stringType), + ParameterInfo.GetParameterInfo(int32Type) + }; + // IndexOf(string, int startIndex) IMethodSymbol? indexOfStringInt32Method = indexOfMethods.GetFirstOrDefaultMemberWithParameterInfos(stringInt32Parameters); if (indexOfStringInt32Method == null) @@ -160,6 +178,13 @@ private void AnalyzeCompilationStart(CompilationStartAnalysisContext context) return; } + ParameterInfo[] stringInt32Int32Parameters = new[] + { + ParameterInfo.GetParameterInfo(stringType), + ParameterInfo.GetParameterInfo(int32Type), + ParameterInfo.GetParameterInfo(int32Type) + }; + // IndexOf(string, int startIndex, int count) IMethodSymbol? indexOfStringInt32Int32Method = indexOfMethods.GetFirstOrDefaultMemberWithParameterInfos(stringInt32Int32Parameters); if (indexOfStringInt32Int32Method == null) @@ -188,51 +213,171 @@ private void AnalyzeCompilationStart(CompilationStartAnalysisContext context) return; } + // a.ToLower().Method() context.RegisterOperationAction(context => { IInvocationOperation caseChangingInvocation = (IInvocationOperation)context.Operation; - IMethodSymbol caseChangingMethod = caseChangingInvocation.TargetMethod; + AnalyzeInvocation(context, caseChangingInvocation, stringType, + containsStringMethod, startsWithStringMethod, compareToStringMethod, + indexOfStringMethod, indexOfStringInt32Method, indexOfStringInt32Int32Method); + }, OperationKind.Invocation); - if (!caseChangingMethod.Equals(toLowerParameterlessMethod) && - !caseChangingMethod.Equals(toLowerInvariantParameterlessMethod) && - !caseChangingMethod.Equals(toUpperParameterlessMethod) && - !caseChangingMethod.Equals(toUpperInvariantParameterlessMethod)) - { - return; - } + // a.ToLower() == b.ToLower() + context.RegisterOperationAction(context => + { + IBinaryOperation binaryOperation = (IBinaryOperation)context.Operation; + AnalyzeBinaryOperation(context, binaryOperation, stringType); - // Ignore parenthesized operations - IOperation? ancestor = caseChangingInvocation.WalkUpParentheses().WalkUpConversion().Parent; + }, OperationKind.Binary); + } - IInvocationOperation diagnosableInvocation; - if (ancestor is IInvocationOperation invocationAncestor) - { - diagnosableInvocation = invocationAncestor; - } - else if (ancestor is IArgumentOperation argumentAncestor && argumentAncestor.Parent is IInvocationOperation argumentInvocationAncestor) - { - diagnosableInvocation = argumentInvocationAncestor; - } - else - { - return; - } + private static void AnalyzeInvocation(OperationAnalysisContext context, IInvocationOperation caseChangingInvocation, INamedTypeSymbol stringType, + IMethodSymbol containsStringMethod, IMethodSymbol startsWithStringMethod, IMethodSymbol compareToStringMethod, + IMethodSymbol indexOfStringMethod, IMethodSymbol indexOfStringInt32Method, IMethodSymbol indexOfStringInt32Int32Method) + { + if (!IsOffendingMethod(caseChangingInvocation, stringType, out string? offendingMethodName)) + { + return; + } - IMethodSymbol diagnosableMethod = diagnosableInvocation.TargetMethod; + if (!TryGetInvocationWithoutParentheses(caseChangingInvocation, out IInvocationOperation? diagnosableInvocation)) + { + return; + } - if (diagnosableMethod.Equals(containsStringMethod) || - diagnosableMethod.Equals(startsWithStringMethod) || - diagnosableMethod.Equals(indexOfStringMethod) || - diagnosableMethod.Equals(indexOfStringInt32Method) || - diagnosableMethod.Equals(indexOfStringInt32Int32Method)) - { - context.ReportDiagnostic(diagnosableInvocation.CreateDiagnostic(RecommendCaseInsensitiveStringComparisonRule, diagnosableMethod.Name)); - } - else if (diagnosableMethod.Equals(compareToStringMethod)) + string caseChangingApproach = GetCaseChangingApproach(offendingMethodName); + + ImmutableDictionary dict = new Dictionary() + { + { CaseChangingApproachName, caseChangingApproach } + }.ToImmutableDictionary(); + + IMethodSymbol diagnosableMethod = diagnosableInvocation.TargetMethod; + + if (diagnosableMethod.Equals(containsStringMethod) || + diagnosableMethod.Equals(startsWithStringMethod) || + diagnosableMethod.Equals(indexOfStringMethod) || + diagnosableMethod.Equals(indexOfStringInt32Method) || + diagnosableMethod.Equals(indexOfStringInt32Int32Method)) + { + context.ReportDiagnostic(diagnosableInvocation.CreateDiagnostic(RecommendCaseInsensitiveStringComparisonRule, dict, diagnosableMethod.Name)); + } + else if (diagnosableMethod.Equals(compareToStringMethod)) + { + context.ReportDiagnostic(diagnosableInvocation.CreateDiagnostic(RecommendCaseInsensitiveStringComparerRule, dict)); + } + } + + private static void AnalyzeBinaryOperation(OperationAnalysisContext context, IBinaryOperation binaryOperation, INamedTypeSymbol stringType) + { + if (binaryOperation.OperatorKind is not BinaryOperatorKind.Equals and not BinaryOperatorKind.NotEquals) + { + return; + } + + bool atLeastOneOffendingInvocation = false; + + string? leftOffendingMethodName = null; + if (TryGetInvocationWithoutParentheses(binaryOperation.LeftOperand, out IInvocationOperation? leftInvocation)) + { + atLeastOneOffendingInvocation = IsOffendingMethod(leftInvocation, stringType, out leftOffendingMethodName); + } + + string? rightOffendingMethodName = null; + if (TryGetInvocationWithoutParentheses(binaryOperation.RightOperand, out IInvocationOperation? rightInvocation)) + { + atLeastOneOffendingInvocation = IsOffendingMethod(rightInvocation, stringType, out rightOffendingMethodName); + } + + if (atLeastOneOffendingInvocation) + { + string caseChangingApproachValue = GetPreferredCaseChangingApproach(leftOffendingMethodName, rightOffendingMethodName); + + ImmutableDictionary dict = new Dictionary() { - context.ReportDiagnostic(diagnosableInvocation.CreateDiagnostic(RecommendCaseInsensitiveStringComparerRule)); - } - }, OperationKind.Invocation); + { CaseChangingApproachName, caseChangingApproachValue } + }.ToImmutableDictionary(); + + context.ReportDiagnostic(binaryOperation.CreateDiagnostic(RecommendCaseInsensitiveStringEqualsRule, dict)); + } + } + + private static bool TryGetInvocationWithoutParentheses(IOperation operation, + [NotNullWhen(returnValue: true)] out IInvocationOperation? diagnosableInvocation) + { + diagnosableInvocation = null; + if (operation is not IInvocationOperation invocationOperation) + { + return false; + } + + IOperation? ancestor = invocationOperation.WalkUpParentheses().WalkUpConversion().Parent; + + if (ancestor is IInvocationOperation invocationAncestor) + { + diagnosableInvocation = invocationAncestor; + } + else if (ancestor is IArgumentOperation argumentAncestor && argumentAncestor.Parent is IInvocationOperation argumentInvocationAncestor) + { + diagnosableInvocation = argumentInvocationAncestor; + } + else + { + diagnosableInvocation = invocationOperation; + } + + return diagnosableInvocation != null; + } + + private static bool IsOffendingMethod(IInvocationOperation invocation, ITypeSymbol stringType, + [NotNullWhen(returnValue: true)] out string? offendingMethodName) + { + offendingMethodName = null; + + if (invocation.Instance == null || invocation.Instance.Type == null) + { + return false; + } + + if (!invocation.Instance.Type.Equals(stringType)) + { + return false; + } + + if (!invocation.TargetMethod.Name.Equals(StringToLowerMethodName, StringComparison.Ordinal) && + !invocation.TargetMethod.Name.Equals(StringToLowerInvariantMethodName, StringComparison.Ordinal) && + !invocation.TargetMethod.Name.Equals(StringToUpperMethodName, StringComparison.Ordinal) && + !invocation.TargetMethod.Name.Equals(StringToUpperInvariantMethodName, StringComparison.Ordinal)) + { + return false; + } + + offendingMethodName = invocation.TargetMethod.Name; + return true; + } + + private static string GetCaseChangingApproach(string methodName) + { + if (methodName is StringToLowerMethodName or StringToUpperMethodName) + { + return StringComparisonCurrentCultureIgnoreCaseName; + } + + Debug.Assert(methodName is StringToLowerInvariantMethodName or StringToUpperInvariantMethodName); + return StringComparisonInvariantCultureIgnoreCaseName; + } + + private static string GetPreferredCaseChangingApproach(string? leftOffendingMethodName, string? rightOffendingMethodName) + { + if (leftOffendingMethodName is StringToLowerMethodName or StringToUpperMethodName || + rightOffendingMethodName is StringToLowerMethodName or StringToUpperMethodName) + { + return StringComparisonCurrentCultureIgnoreCaseName; + } + + Debug.Assert(leftOffendingMethodName is StringToLowerInvariantMethodName or StringToUpperInvariantMethodName || + rightOffendingMethodName is StringToLowerInvariantMethodName or StringToUpperInvariantMethodName); + return StringComparisonInvariantCultureIgnoreCaseName; } } } diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/RecommendCaseInsensitiveStringComparison.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/RecommendCaseInsensitiveStringComparison.Fixer.cs index f1b9445c65..d4ce31c0b2 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/RecommendCaseInsensitiveStringComparison.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/RecommendCaseInsensitiveStringComparison.Fixer.cs @@ -22,9 +22,11 @@ namespace Microsoft.NetCore.Analyzers.Performance /// public abstract class RecommendCaseInsensitiveStringComparisonFixer : CodeFixProvider { - protected abstract List GetNewArguments(SyntaxGenerator generator, IInvocationOperation mainInvocationOperation, + protected abstract IEnumerable GetNewArgumentsForInvocation(SyntaxGenerator generator, string caseChangingApproachValue, IInvocationOperation mainInvocationOperation, INamedTypeSymbol stringComparisonType, out SyntaxNode? mainInvocationInstance); + protected abstract IEnumerable GetNewArgumentsForBinary(SyntaxGenerator generator, SyntaxNode rightNode, SyntaxNode typeMemberAccess); + public override ImmutableArray FixableDiagnosticIds { get; } = ImmutableArray.Create(RCISCAnalyzer.RuleId); public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; @@ -44,33 +46,59 @@ public override async Task RegisterCodeFixesAsync(CodeFixContext context) SemanticModel model = await doc.GetRequiredSemanticModelAsync(ct).ConfigureAwait(false); - if (model.GetOperation(node, ct) is not IInvocationOperation invocation) + if (model.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemStringComparison) + is not INamedTypeSymbol stringComparisonType) { return; } - if (model.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemStringComparison) - is not INamedTypeSymbol stringComparisonType) + IOperation? operation = model.GetOperation(node, ct); + if (operation == null) + { + return; + } + + SyntaxGenerator generator = SyntaxGenerator.GetGenerator(doc); + + // The desired case changing enum value should've already been chosen in the analyzer + if (!context.Diagnostics[0].Properties.TryGetValue(RCISCAnalyzer.CaseChangingApproachName, out string? caseChangingApproachValue) || caseChangingApproachValue == null) { return; } - Task createChangedDocument(CancellationToken _) => FixInvocationAsync(doc, root, - invocation, stringComparisonType, invocation.TargetMethod.Name); + if (operation is IInvocationOperation invocation) + { + Task createChangedDocument(CancellationToken _) => FixInvocationAsync(generator, doc, root, + invocation, stringComparisonType, invocation.TargetMethod.Name, caseChangingApproachValue); + + string title = string.Format(System.Globalization.CultureInfo.CurrentCulture, + MicrosoftNetCoreAnalyzersResources.RecommendCaseInsensitiveStringComparerStringComparisonCodeFixTitle, invocation.TargetMethod.Name); + + context.RegisterCodeFix( + CodeAction.Create( + title, + createChangedDocument, + equivalenceKey: nameof(MicrosoftNetCoreAnalyzersResources.RecommendCaseInsensitiveStringComparerStringComparisonCodeFixTitle)), + context.Diagnostics); + } + else if (operation is IBinaryOperation binaryOperation && + binaryOperation.LeftOperand != null && binaryOperation.RightOperand != null) + { + Task createChangedDocument(CancellationToken _) => FixBinaryAsync(generator, doc, root, binaryOperation, stringComparisonType, caseChangingApproachValue); - string title = string.Format( - MicrosoftNetCoreAnalyzersResources.RecommendCaseInsensitiveStringComparerStringComparisonCodeFixTitle, invocation.TargetMethod.Name); + string title = MicrosoftNetCoreAnalyzersResources.RecommendCaseInsensitiveStringEqualsCodeFixTitle; - context.RegisterCodeFix( - CodeAction.Create( - title, - createChangedDocument, - equivalenceKey: nameof(MicrosoftNetCoreAnalyzersResources.RecommendCaseInsensitiveStringComparerStringComparisonCodeFixTitle)), - context.Diagnostics); + context.RegisterCodeFix( + CodeAction.Create( + title, + createChangedDocument, + equivalenceKey: nameof(MicrosoftNetCoreAnalyzersResources.RecommendCaseInsensitiveStringEqualsCodeFixTitle)), + context.Diagnostics); + } } - private Task FixInvocationAsync(Document doc, SyntaxNode root, IInvocationOperation mainInvocation, - INamedTypeSymbol stringComparisonType, string diagnosableMethodName) + private Task FixInvocationAsync(SyntaxGenerator generator, Document doc, SyntaxNode root, IInvocationOperation mainInvocation, + INamedTypeSymbol stringComparisonType, string diagnosableMethodName, string caseChangingApproachValue) { // Defensive check: The max number of arguments is held by IndexOf Debug.Assert(mainInvocation.Arguments.Length <= 3); @@ -94,31 +122,59 @@ private Task FixInvocationAsync(Document doc, SyntaxNode root, IInvoca // B) a.IndexOf(b, startIndex: n, StringComparison.Desired) // C) a.IndexOf(b, startIndex: n, count: m, StringComparison.Desired) - SyntaxGenerator generator = SyntaxGenerator.GetGenerator(doc); - // Defensive check: Should not fix string.CompareTo Debug.Assert(diagnosableMethodName is RCISCAnalyzer.StringContainsMethodName or RCISCAnalyzer.StringIndexOfMethodName or RCISCAnalyzer.StringStartsWithMethodName); - List newArguments = GetNewArguments(generator, mainInvocation, stringComparisonType, out SyntaxNode? mainInvocationInstance); + IEnumerable newArguments = GetNewArgumentsForInvocation(generator, caseChangingApproachValue, mainInvocation, stringComparisonType, out SyntaxNode? mainInvocationInstance); SyntaxNode stringMemberAccessExpression = generator.MemberAccessExpression(mainInvocationInstance, mainInvocation.TargetMethod.Name); SyntaxNode newInvocation = generator.InvocationExpression(stringMemberAccessExpression, newArguments).WithTriviaFrom(mainInvocation.Syntax); - SyntaxNode newRoot = generator.ReplaceNode(root, mainInvocation.Syntax, newInvocation); + SyntaxNode newRoot = generator.ReplaceNode(root, mainInvocation.Syntax, newInvocation.WithTriviaFrom(mainInvocation.Syntax)); + return Task.FromResult(doc.WithSyntaxRoot(newRoot)); + } + + private Task FixBinaryAsync(SyntaxGenerator generator, Document doc, SyntaxNode root, IBinaryOperation binaryOperation, + INamedTypeSymbol stringComparisonType, string caseChangingApproachValue) + { + SyntaxNode leftNode = binaryOperation.LeftOperand is IInvocationOperation leftInvocation ? + leftInvocation.Instance!.Syntax : + binaryOperation.LeftOperand.Syntax; + + SyntaxNode rightNode = binaryOperation.RightOperand is IInvocationOperation rightInvocation ? + rightInvocation.Instance!.Syntax : + binaryOperation.RightOperand.Syntax; + + SyntaxNode memberAccess = generator.MemberAccessExpression(leftNode, RCISCAnalyzer.StringEqualsMethodName).WithTriviaFrom(leftNode); + + SyntaxNode stringComparisonTypeExpression = generator.TypeExpressionForStaticMemberAccess(stringComparisonType); + + SyntaxNode typeMemberAccess = generator.MemberAccessExpression(stringComparisonTypeExpression, caseChangingApproachValue); + + IEnumerable newArguments = GetNewArgumentsForBinary(generator, rightNode, typeMemberAccess); + + SyntaxNode equalsInvocation = generator.InvocationExpression(memberAccess, newArguments); + + // Determine if it should be a.Equals or !a.Equals + var replacement = binaryOperation.OperatorKind == BinaryOperatorKind.NotEquals ? + generator.LogicalNotExpression(equalsInvocation) : + equalsInvocation; + + SyntaxNode newRoot = generator.ReplaceNode(root, binaryOperation.Syntax, replacement.WithTriviaFrom(binaryOperation.Syntax)); return Task.FromResult(doc.WithSyntaxRoot(newRoot)); } protected static SyntaxNode GetNewStringComparisonArgument(SyntaxGenerator generator, - INamedTypeSymbol stringComparisonType, string caseChangingApproachName, bool isAnyArgumentNamed) + INamedTypeSymbol stringComparisonType, string caseChangingApproachValue, bool isAnyArgumentNamed) { // Generate the enum access expression for "StringComparison.DesiredCultureDesiredCase" SyntaxNode stringComparisonEnumValueAccess = generator.MemberAccessExpression( generator.TypeExpressionForStaticMemberAccess(stringComparisonType), - generator.IdentifierName(caseChangingApproachName)); + generator.IdentifierName(caseChangingApproachValue)); // Convert the above into an argument node, then append it to the argument list: "b, StringComparison.DesiredCultureDesiredCase" // If at least one of the pre-existing arguments is named, then the StringComparison enum value needs to be named too @@ -128,16 +184,5 @@ protected static SyntaxNode GetNewStringComparisonArgument(SyntaxGenerator gener return stringComparisonArgument; } - - protected static string GetCaseChangingApproach(string methodName) - { - if (methodName is RCISCAnalyzer.StringToLowerMethodName or RCISCAnalyzer.StringToUpperMethodName) - { - return RCISCAnalyzer.StringComparisonCurrentCultureIgnoreCaseName; - } - - Debug.Assert(methodName is RCISCAnalyzer.StringToLowerInvariantMethodName or RCISCAnalyzer.StringToUpperInvariantMethodName); - return RCISCAnalyzer.StringComparisonInvariantCultureIgnoreCaseName; - } } } \ 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 6222d73c75..a6dffbc851 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf @@ -2333,11 +2333,6 @@ Obecné přetypování (IL unbox.any) používané sekvencí vrácenou metodou E Použijte přetížení string.{0}(string, StringComparison) - - Prefer using 'StringComparer' to perform case-insensitive string comparisons - Preferovat použití StringComparer pro porovnávání řetězců bez rozlišení velikosti písmen - - Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons because they lead to an allocation. Instead, prefer calling the method overloads of 'Contains', 'IndexOf' and 'StartsWith' that take a 'StringComparison' enum value to perform case-insensitive comparisons. Vyhněte se volání ToLower, ToUpper, ToLowerInvariant a ToUpperInvariant pro porovnávání řetězců bez ohledu na velikost písmen, protože vedou k alokaci. Místo toho raději volejte přetížení metod Contains, IndexOf a StartsWith, které přebírají hodnotu výčtu StringComparison, aby se provedlo porovnání bez ohledu na velikost písmen. @@ -2353,6 +2348,21 @@ Obecné přetypování (IL unbox.any) používané sekvencí vrácenou metodou E Preferovat přetížení metody StringComparison pro porovnávání řetězců bez ohledu na velikost písmen. + + Use 'string.Equals(string, StringComparison)' + Use 'string.Equals(string, StringComparison)' + + + + Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons, as in 'string.ToLower() == string.ToLower()', because they lead to an allocation. Instead, use 'string.Equals(string, StringComparison)' to perform case-insensitive comparisons. + Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons, as in 'string.ToLower() == string.ToLower()', because they lead to an allocation. Instead, use 'string.Equals(string, StringComparison)' to perform case-insensitive comparisons. + + + + Prefer using 'string.Equals(string, StringComparison)' to perform a case-insensitive comparison + Prefer using 'string.Equals(string, StringComparison)' to perform a case-insensitive comparison + + Remove redundant call Odebrat redundantní volání 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 15cea3dd32..a90c48e9f1 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf @@ -2333,11 +2333,6 @@ Erweiterungen und benutzerdefinierte Konvertierungen werden bei generischen Type Verwenden Sie die "string.{0}(string, StringComparison)"-Überladung - - Prefer using 'StringComparer' to perform case-insensitive string comparisons - "StringComparer" bevorzugen, um Zeichenfolgenvergleiche ohne Beachtung der Groß-/Kleinschreibung durchzuführen. - - Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons because they lead to an allocation. Instead, prefer calling the method overloads of 'Contains', 'IndexOf' and 'StartsWith' that take a 'StringComparison' enum value to perform case-insensitive comparisons. Vermeiden Sie das Aufrufen von "ToLower", "ToUpper", "ToLowerInvariant" und "ToUpperInvariant", um Zeichenfolgenvergleiche ohne Beachtung der Groß-/Kleinschreibung durchzuführen, da sie zu einer Zuordnung führen. Rufen Sie stattdessen lieber die Methodenüberladungen von "Contains", "IndexOf" und "StartsWith" auf, die einen StringComparison-Enumerationswert verwenden, um Vergleiche ohne Beachtung der Groß-/Kleinschreibung durchzuführen. @@ -2353,6 +2348,21 @@ Erweiterungen und benutzerdefinierte Konvertierungen werden bei generischen Type "StringComparison"-Methodenüberladungen bevorzugen, um Zeichenfolgenvergleiche ohne Beachtung der Groß-/Kleinschreibung durchzuführen + + Use 'string.Equals(string, StringComparison)' + Use 'string.Equals(string, StringComparison)' + + + + Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons, as in 'string.ToLower() == string.ToLower()', because they lead to an allocation. Instead, use 'string.Equals(string, StringComparison)' to perform case-insensitive comparisons. + Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons, as in 'string.ToLower() == string.ToLower()', because they lead to an allocation. Instead, use 'string.Equals(string, StringComparison)' to perform case-insensitive comparisons. + + + + Prefer using 'string.Equals(string, StringComparison)' to perform a case-insensitive comparison + Prefer using 'string.Equals(string, StringComparison)' to perform a case-insensitive comparison + + Remove redundant call Redundanten Aufruf entfernen 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 eba673dee2..5722e66558 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf @@ -2333,11 +2333,6 @@ La ampliación y las conversiones definidas por el usuario no se admiten con tip Usar la sobrecarga "string.{0}(string, StringComparison)" - - Prefer using 'StringComparer' to perform case-insensitive string comparisons - Preferir el uso de "StringComparer" para realizar comparaciones de cadenas sin distinción de mayúsculas y minúsculas - - Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons because they lead to an allocation. Instead, prefer calling the method overloads of 'Contains', 'IndexOf' and 'StartsWith' that take a 'StringComparison' enum value to perform case-insensitive comparisons. Evite llamar a "ToLower", "ToUpper", "ToLowerInvariant" y "ToUpperInvariant" para realizar comparaciones de cadenas sin distinción de mayúsculas y minúsculas porque dan lugar a una asignación. En su lugar, es preferible llamar a las sobrecargas de método de "Contains", "IndexOf" y "StartsWith" que toman un valor de enumeración "StringComparison" para realizar comparaciones sin distinción de mayúsculas y minúsculas. @@ -2353,6 +2348,21 @@ La ampliación y las conversiones definidas por el usuario no se admiten con tip Preferir las sobrecargas del método "StringComparison" para realizar comparaciones de cadenas sin distinción de mayúsculas y minúsculas + + Use 'string.Equals(string, StringComparison)' + Use 'string.Equals(string, StringComparison)' + + + + Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons, as in 'string.ToLower() == string.ToLower()', because they lead to an allocation. Instead, use 'string.Equals(string, StringComparison)' to perform case-insensitive comparisons. + Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons, as in 'string.ToLower() == string.ToLower()', because they lead to an allocation. Instead, use 'string.Equals(string, StringComparison)' to perform case-insensitive comparisons. + + + + Prefer using 'string.Equals(string, StringComparison)' to perform a case-insensitive comparison + Prefer using 'string.Equals(string, StringComparison)' to perform a case-insensitive comparison + + Remove redundant call Quitar llamada redundante 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 90cb521b4c..7f4e60116b 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf @@ -2333,11 +2333,6 @@ Les conversions étendues et définies par l’utilisateur ne sont pas prises en Utiliser la surcharge « string.{0}(string, StringComparison) » - - Prefer using 'StringComparer' to perform case-insensitive string comparisons - Préférer l’utilisation de « StringComparer » pour effectuer des comparaisons de chaînes qui ne respectent pas la casse - - Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons because they lead to an allocation. Instead, prefer calling the method overloads of 'Contains', 'IndexOf' and 'StartsWith' that take a 'StringComparison' enum value to perform case-insensitive comparisons. Évitez d’appeler 'ToLower', 'ToUpper', 'ToLowerInvariant' et 'ToUpperInvariant' pour effectuer des comparaisons de chaînes qui ne respectent pas la casse, car elles entraînent une allocation. Préférez plutôt appeler les surcharges de méthode de 'Contains', 'IndexOf' et 'StartsWith' qui acceptent une valeur enum 'StringComparison' pour effectuer des comparaisons qui ne respectent pas la casse. @@ -2353,6 +2348,21 @@ Les conversions étendues et définies par l’utilisateur ne sont pas prises en Préférer les surcharges de méthode 'StringComparison' pour effectuer des comparaisons de chaînes qui ne respectent pas la casse + + Use 'string.Equals(string, StringComparison)' + Use 'string.Equals(string, StringComparison)' + + + + Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons, as in 'string.ToLower() == string.ToLower()', because they lead to an allocation. Instead, use 'string.Equals(string, StringComparison)' to perform case-insensitive comparisons. + Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons, as in 'string.ToLower() == string.ToLower()', because they lead to an allocation. Instead, use 'string.Equals(string, StringComparison)' to perform case-insensitive comparisons. + + + + Prefer using 'string.Equals(string, StringComparison)' to perform a case-insensitive comparison + Prefer using 'string.Equals(string, StringComparison)' to perform a case-insensitive comparison + + Remove redundant call Supprimer un appel redondant 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 8fedc9bb9b..9ad1701d9e 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf @@ -2333,11 +2333,6 @@ L'ampliamento e le conversioni definite dall'utente non sono supportate con tipi Usare l'overload “string.{0}(string, StringComparison)" - - Prefer using 'StringComparer' to perform case-insensitive string comparisons - Preferire l'uso di “StringComparer” per eseguire confronti di stringhe senza distinzione tra maiuscole e minuscole - - Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons because they lead to an allocation. Instead, prefer calling the method overloads of 'Contains', 'IndexOf' and 'StartsWith' that take a 'StringComparison' enum value to perform case-insensitive comparisons. Evitare di chiamare “ToLower”, “ToUpper”, “ToLowerInvariant” e “ToUpperInvariant” per eseguire confronti di stringhe senza distinzione tra maiuscole e minuscole, perché comportano un'allocazione. Preferire invece chiamare i sovraccarichi dei metodi “Contains”, “IndexOf” e “StartsWith” che accettano un valore dell'enumerazione “StringComparison” per eseguire confronti senza distinzione tra maiuscole e minuscole. @@ -2353,6 +2348,21 @@ L'ampliamento e le conversioni definite dall'utente non sono supportate con tipi Preferire gli overload di metodo “StringComparison” per eseguire confronti tra stringhe senza distinzione tra maiuscole e minuscole + + Use 'string.Equals(string, StringComparison)' + Use 'string.Equals(string, StringComparison)' + + + + Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons, as in 'string.ToLower() == string.ToLower()', because they lead to an allocation. Instead, use 'string.Equals(string, StringComparison)' to perform case-insensitive comparisons. + Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons, as in 'string.ToLower() == string.ToLower()', because they lead to an allocation. Instead, use 'string.Equals(string, StringComparison)' to perform case-insensitive comparisons. + + + + Prefer using 'string.Equals(string, StringComparison)' to perform a case-insensitive comparison + Prefer using 'string.Equals(string, StringComparison)' to perform a case-insensitive comparison + + Remove redundant call Rimuovi la chiamata ridondante 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 a60785ffee..99290a9e45 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf @@ -2333,11 +2333,6 @@ Enumerable.OfType<T> で使用されるジェネリック型チェック ( 'string.{0}(string, StringComparison)' オーバーロードを使用してください - - Prefer using 'StringComparer' to perform case-insensitive string comparisons - 大文字と小文字を区別しない文字列比較を実行するには、'StringComparer' を使用することをお勧めします - - Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons because they lead to an allocation. Instead, prefer calling the method overloads of 'Contains', 'IndexOf' and 'StartsWith' that take a 'StringComparison' enum value to perform case-insensitive comparisons. 大文字と小文字を区別しない文字列比較を実行するために、'ToLower'、'ToUpper'、'ToLowerInvariant'、および 'ToUpperInvariant' を呼び出さないようにしてください。これは、割り当てにつながるためです。代わりに、'StringComparison' 列挙型の値を取る 'Contains'、'IndexOf'、'StartsWith' のメソッド オーバーロードを呼び出して、大文字と小文字を区別しない比較を実行することをお勧めします。 @@ -2353,6 +2348,21 @@ Enumerable.OfType<T> で使用されるジェネリック型チェック ( 大文字と小文字を区別しない文字列比較を実行するには、'StringComparison' メソッド オーバーロードをお勧めします + + Use 'string.Equals(string, StringComparison)' + Use 'string.Equals(string, StringComparison)' + + + + Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons, as in 'string.ToLower() == string.ToLower()', because they lead to an allocation. Instead, use 'string.Equals(string, StringComparison)' to perform case-insensitive comparisons. + Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons, as in 'string.ToLower() == string.ToLower()', because they lead to an allocation. Instead, use 'string.Equals(string, StringComparison)' to perform case-insensitive comparisons. + + + + Prefer using 'string.Equals(string, StringComparison)' to perform a case-insensitive comparison + Prefer using 'string.Equals(string, StringComparison)' to perform a case-insensitive comparison + + Remove redundant call 冗長な呼び出しを削除する 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 53fe883ef2..dd7a8f8384 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf @@ -2333,11 +2333,6 @@ Enumerable.OfType<T>에서 사용하는 제네릭 형식 검사(C# 'is' 'string.{0}(string, StringComparison)' 오버로드 사용 - - Prefer using 'StringComparer' to perform case-insensitive string comparisons - 대소문자를 구분하지 않는 문자열 비교를 수행하려면 'StringComparer' 사용을 선호합니다. - - Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons because they lead to an allocation. Instead, prefer calling the method overloads of 'Contains', 'IndexOf' and 'StartsWith' that take a 'StringComparison' enum value to perform case-insensitive comparisons. 대/소문자를 구분하지 않는 문자열 비교를 수행하기 위해 'ToLower', 'ToUpper', 'ToLowerInvariant' 및 'ToUpperInvariant'를 호출하지 마세요. 대신 대/소문자를 구분하지 않는 비교를 수행하기 위해 'StringComparison' 열거형 값을 사용하는 'Contains', 'IndexOf' 및 'StartsWith'의 메서드 오버로드를 호출하는 것이 좋습니다. @@ -2353,6 +2348,21 @@ Enumerable.OfType<T>에서 사용하는 제네릭 형식 검사(C# 'is' 대소문자를 구분하지 않는 문자열 비교를 수행하려면 'StringComparison' 메서드 오버로드를 선호합니다. + + Use 'string.Equals(string, StringComparison)' + Use 'string.Equals(string, StringComparison)' + + + + Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons, as in 'string.ToLower() == string.ToLower()', because they lead to an allocation. Instead, use 'string.Equals(string, StringComparison)' to perform case-insensitive comparisons. + Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons, as in 'string.ToLower() == string.ToLower()', because they lead to an allocation. Instead, use 'string.Equals(string, StringComparison)' to perform case-insensitive comparisons. + + + + Prefer using 'string.Equals(string, StringComparison)' to perform a case-insensitive comparison + Prefer using 'string.Equals(string, StringComparison)' to perform a case-insensitive comparison + + Remove redundant call 중복 호출 제거 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 a844516d82..2718f0469f 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf @@ -2333,11 +2333,6 @@ Konwersje poszerzane i zdefiniowane przez użytkownika nie są obsługiwane w pr Użyj obciążenia „string.{0}(string, StringComparison)” - - Prefer using 'StringComparer' to perform case-insensitive string comparisons - Preferuj używanie elementu „StringComparer” do wykonywania porównań ciągów bez uwzględniania wielkości liter - - Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons because they lead to an allocation. Instead, prefer calling the method overloads of 'Contains', 'IndexOf' and 'StartsWith' that take a 'StringComparison' enum value to perform case-insensitive comparisons. Unikaj wywoływania elementów „ToLower”, „ToUpper”, „ToLowerInvariant” i „ToUpperInvariant”, aby przeprowadzać porównania ciągów bez uwzględniania wielkości liter, ponieważ prowadzą do alokacji. Zamiast tego preferuj wywoływanie przeciążeń metod „Contains”, „IndexOf” i „StartsWith”, które przyjmują wartość wyliczenia „StringComparison”, aby wykonać porównania bez uwzględniania wielkości liter. @@ -2353,6 +2348,21 @@ Konwersje poszerzane i zdefiniowane przez użytkownika nie są obsługiwane w pr Preferuj przeciążenia metody „StringComparison”, aby wykonywać porównania ciągów bez uwzględniania wielkości liter + + Use 'string.Equals(string, StringComparison)' + Use 'string.Equals(string, StringComparison)' + + + + Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons, as in 'string.ToLower() == string.ToLower()', because they lead to an allocation. Instead, use 'string.Equals(string, StringComparison)' to perform case-insensitive comparisons. + Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons, as in 'string.ToLower() == string.ToLower()', because they lead to an allocation. Instead, use 'string.Equals(string, StringComparison)' to perform case-insensitive comparisons. + + + + Prefer using 'string.Equals(string, StringComparison)' to perform a case-insensitive comparison + Prefer using 'string.Equals(string, StringComparison)' to perform a case-insensitive comparison + + Remove redundant call Usuń nadmiarowe wywołanie 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 ae43150a2f..cc1e7ed31e 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 @@ -2333,11 +2333,6 @@ Ampliação e conversões definidas pelo usuário não são compatíveis com tip Use a 'cadeia de caracteres.{0}(cadeia de caracteres, StringComparison)' sobrecarga - - Prefer using 'StringComparer' to perform case-insensitive string comparisons - Prefira usar 'StringComparer' para realizar comparações de cadeia de caracteres que não diferenciam maiúsculas de minúsculas - - Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons because they lead to an allocation. Instead, prefer calling the method overloads of 'Contains', 'IndexOf' and 'StartsWith' that take a 'StringComparison' enum value to perform case-insensitive comparisons. Evite chamar 'ToLower', 'ToUpper', 'ToLowerInvariant' e 'ToUpperInvariant' para executar comparações de cadeia de caracteres que não diferenciam maiúsculas de minúsculas porque elas levam a uma alocação. Em vez disso, prefira chamar as sobrecargas de método de 'Contains', 'IndexOf' e 'StartsWith' que usam um valor de enumeração 'StringComparison' para executar comparações que não diferenciam maiúsculas de minúsculas. @@ -2353,6 +2348,21 @@ Ampliação e conversões definidas pelo usuário não são compatíveis com tip Prefira as sobrecargas do método 'StringComparison' para executar comparações de cadeia de caracteres que não diferenciam maiúsculas de minúsculas + + Use 'string.Equals(string, StringComparison)' + Use 'string.Equals(string, StringComparison)' + + + + Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons, as in 'string.ToLower() == string.ToLower()', because they lead to an allocation. Instead, use 'string.Equals(string, StringComparison)' to perform case-insensitive comparisons. + Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons, as in 'string.ToLower() == string.ToLower()', because they lead to an allocation. Instead, use 'string.Equals(string, StringComparison)' to perform case-insensitive comparisons. + + + + Prefer using 'string.Equals(string, StringComparison)' to perform a case-insensitive comparison + Prefer using 'string.Equals(string, StringComparison)' to perform a case-insensitive comparison + + Remove redundant call Remova a chamada redundante 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 c7d55c78d0..23a69d0d78 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf @@ -2333,11 +2333,6 @@ Widening and user defined conversions are not supported with generic types.Используйте перегрузку "string.{0}(string, StringComparison)" - - Prefer using 'StringComparer' to perform case-insensitive string comparisons - Используйте StringComparer для сравнения строк без учета регистра - - Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons because they lead to an allocation. Instead, prefer calling the method overloads of 'Contains', 'IndexOf' and 'StartsWith' that take a 'StringComparison' enum value to perform case-insensitive comparisons. Избегайте вызова ToLower, ToUpper, ToLowerInvariant и ToUpperInvariant для сравнения строк без учета регистра, так как они приведут к выделению. Вместо этого вызывайте перегрузки методов Contains, IndexOf и StartsWith, которые принимают значение перечисления StringComparison для сравнения без учета регистра. @@ -2353,6 +2348,21 @@ Widening and user defined conversions are not supported with generic types.Используйте перегрузки метода StringComparison для сравнения строк без учета регистра + + Use 'string.Equals(string, StringComparison)' + Use 'string.Equals(string, StringComparison)' + + + + Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons, as in 'string.ToLower() == string.ToLower()', because they lead to an allocation. Instead, use 'string.Equals(string, StringComparison)' to perform case-insensitive comparisons. + Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons, as in 'string.ToLower() == string.ToLower()', because they lead to an allocation. Instead, use 'string.Equals(string, StringComparison)' to perform case-insensitive comparisons. + + + + Prefer using 'string.Equals(string, StringComparison)' to perform a case-insensitive comparison + Prefer using 'string.Equals(string, StringComparison)' to perform a case-insensitive comparison + + Remove redundant call Удалить избыточный вызов 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 6c361a5864..82f53e8703 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf @@ -2333,11 +2333,6 @@ Genel türlerde genişletme ve kullanıcı tanımlı dönüştürmeler desteklen 'string.{0}(string, StringComparison)' aşırı yüklemesini kullan - - Prefer using 'StringComparer' to perform case-insensitive string comparisons - Büyük/küçük harfe duyarsız dize karşılaştırmaları gerçekleştirmek için 'StringComparer' kullanmayı tercih edin - - Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons because they lead to an allocation. Instead, prefer calling the method overloads of 'Contains', 'IndexOf' and 'StartsWith' that take a 'StringComparison' enum value to perform case-insensitive comparisons. Bir ayırmaya yol açtıkları için büyük/küçük harfe duyarsız dize karşılaştırmaları gerçekleştirmek için 'ToLower', 'ToUpper', 'ToLowerInvariant' ve 'ToUpperInvariant' aramaktan kaçının. Bunun yerine, büyük/küçük harfe duyarsız karşılaştırmalar gerçekleştirmek için bir 'StringComparison' enum değeri alan 'Contains', 'IndexOf' ve 'StartsWith' yöntem aşırı yüklemelerini aramayı tercih edin. @@ -2353,6 +2348,21 @@ Genel türlerde genişletme ve kullanıcı tanımlı dönüştürmeler desteklen Büyük/küçük harfe duyarsız dize karşılaştırmaları gerçekleştirmek için 'StringComparison' metodu aşırı yüklemelerini tercih edin + + Use 'string.Equals(string, StringComparison)' + Use 'string.Equals(string, StringComparison)' + + + + Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons, as in 'string.ToLower() == string.ToLower()', because they lead to an allocation. Instead, use 'string.Equals(string, StringComparison)' to perform case-insensitive comparisons. + Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons, as in 'string.ToLower() == string.ToLower()', because they lead to an allocation. Instead, use 'string.Equals(string, StringComparison)' to perform case-insensitive comparisons. + + + + Prefer using 'string.Equals(string, StringComparison)' to perform a case-insensitive comparison + Prefer using 'string.Equals(string, StringComparison)' to perform a case-insensitive comparison + + Remove redundant call Gereksiz çağrıyı kaldır 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 42c966a160..da07dd20b0 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 @@ -2333,11 +2333,6 @@ Enumerable.OfType<T> 使用的泛型类型检查 (C# 'is' operator/IL 'isi 使用 "string.{0}(string, StringComparison)" 重载 - - Prefer using 'StringComparer' to perform case-insensitive string comparisons - 首选使用 "StringComparer" 执行不区分大小写的字符串比较 - - Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons because they lead to an allocation. Instead, prefer calling the method overloads of 'Contains', 'IndexOf' and 'StartsWith' that take a 'StringComparison' enum value to perform case-insensitive comparisons. 避免调用 "ToLower"、"ToUpper"、"ToLowerInvariant" 和 "ToUpperInvariant" 来执行不区分大小写的字符串比较,因为它们会导致分配。相反,首选调用采用 "StringComparison" 枚举值的 "Contains"、"IndexOf" 和 "StartsWith" 的方法重载来执行不区分大小写的比较。 @@ -2353,6 +2348,21 @@ Enumerable.OfType<T> 使用的泛型类型检查 (C# 'is' operator/IL 'isi 首选 "StringComparison" 方法重载来执行不区分大小写的字符串比较 + + Use 'string.Equals(string, StringComparison)' + Use 'string.Equals(string, StringComparison)' + + + + Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons, as in 'string.ToLower() == string.ToLower()', because they lead to an allocation. Instead, use 'string.Equals(string, StringComparison)' to perform case-insensitive comparisons. + Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons, as in 'string.ToLower() == string.ToLower()', because they lead to an allocation. Instead, use 'string.Equals(string, StringComparison)' to perform case-insensitive comparisons. + + + + Prefer using 'string.Equals(string, StringComparison)' to perform a case-insensitive comparison + Prefer using 'string.Equals(string, StringComparison)' to perform a case-insensitive comparison + + Remove redundant call 删除冗余的调用 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 aa2bb17278..1994ad85ab 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 @@ -2333,11 +2333,6 @@ Enumerable.OfType<T> 使用的一般型別檢查 (C# 'is' operator/IL 'isi 使用 'string.{0}(string, StringComparison)' 多載 - - Prefer using 'StringComparer' to perform case-insensitive string comparisons - 偏好使用 'StringComparer' 來執行不區分大小寫的字串比較 - - Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons because they lead to an allocation. Instead, prefer calling the method overloads of 'Contains', 'IndexOf' and 'StartsWith' that take a 'StringComparison' enum value to perform case-insensitive comparisons. 避免呼叫 'ToLower'、'ToUpper'、'ToLowerInvariant' 和 'ToUpperInvariant' 來執行不區分大小寫的字串比較,因為它們會導致配置。相反地,偏好呼叫採用 'StringComparison' 列舉值的 'Contains'、'IndexOf' 和 'StartsWith' 方法多載,以執行不區分大小寫的比較。 @@ -2353,6 +2348,21 @@ Enumerable.OfType<T> 使用的一般型別檢查 (C# 'is' operator/IL 'isi 偏好使用 'StringComparison' 方法多載來執行不區分大小寫的字串比較 + + Use 'string.Equals(string, StringComparison)' + Use 'string.Equals(string, StringComparison)' + + + + Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons, as in 'string.ToLower() == string.ToLower()', because they lead to an allocation. Instead, use 'string.Equals(string, StringComparison)' to perform case-insensitive comparisons. + Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons, as in 'string.ToLower() == string.ToLower()', because they lead to an allocation. Instead, use 'string.Equals(string, StringComparison)' to perform case-insensitive comparisons. + + + + Prefer using 'string.Equals(string, StringComparison)' to perform a case-insensitive comparison + Prefer using 'string.Equals(string, StringComparison)' to perform a case-insensitive comparison + + Remove redundant call 移除冗餘的呼叫 diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md index accfe817af..5f48683ba3 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md @@ -1704,9 +1704,9 @@ Constant arrays passed as arguments are not reused when called repeatedly, which |CodeFix|True| --- -## [CA1862](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1862): Prefer using 'StringComparer' to perform case-insensitive string comparisons +## [CA1862](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1862): Prefer the 'StringComparison' method overloads to perform case-insensitive string comparisons -Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons when using 'CompareTo', because they lead to an allocation. Instead, use 'StringComparer' to perform case-insensitive comparisons. +Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons, as in 'string.ToLower() == string.ToLower()', because they lead to an allocation. Instead, use 'string.Equals(string, StringComparison)' to perform case-insensitive comparisons. |Item|Value| |-|-| diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif index 73af3cdaa8..7220e83c64 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif @@ -3158,8 +3158,8 @@ }, "CA1862": { "id": "CA1862", - "shortDescription": "Prefer using 'StringComparer' to perform case-insensitive string comparisons", - "fullDescription": "Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons when using 'CompareTo', because they lead to an allocation. Instead, use 'StringComparer' to perform case-insensitive comparisons.", + "shortDescription": "Prefer the 'StringComparison' method overloads to perform case-insensitive string comparisons", + "fullDescription": "Avoid calling 'ToLower', 'ToUpper', 'ToLowerInvariant' and 'ToUpperInvariant' to perform case-insensitive string comparisons, as in 'string.ToLower() == string.ToLower()', because they lead to an allocation. Instead, use 'string.Equals(string, StringComparison)' to perform case-insensitive comparisons.", "defaultLevel": "note", "helpUri": "https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1862", "properties": { diff --git a/src/NetAnalyzers/RulesMissingDocumentation.md b/src/NetAnalyzers/RulesMissingDocumentation.md index 0cd066184e..d9e8ac315b 100644 --- a/src/NetAnalyzers/RulesMissingDocumentation.md +++ b/src/NetAnalyzers/RulesMissingDocumentation.md @@ -8,7 +8,7 @@ CA1512 | | Use ObjectDisposedException throw helper | CA1856 | | Incorrect usage of ConstantExpected attribute | CA1857 | | A constant is expected for the parameter | -CA1862 | | Prefer using 'StringComparer' to perform case-insensitive string comparisons | +CA1862 | | Prefer the 'StringComparison' method overloads to perform case-insensitive string comparisons | CA1863 | | Use 'CompositeFormat' | CA2021 | | Do not call Enumerable.Cast\ or Enumerable.OfType\ with incompatible types | CA2261 | | Do not use ConfigureAwaitOptions.SuppressThrowing with Task\ | diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/RecommendCaseInsensitiveStringComparison.Base.Tests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/RecommendCaseInsensitiveStringComparison.Base.Tests.cs index 57433e4a8a..20db0a3391 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/RecommendCaseInsensitiveStringComparison.Base.Tests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/RecommendCaseInsensitiveStringComparison.Base.Tests.cs @@ -7,15 +7,25 @@ namespace Microsoft.NetCore.Analyzers.Performance.UnitTests { public abstract class RecommendCaseInsensitiveStringComparison_Base_Tests { - private static readonly Tuple[] Cultures = new[] { - Tuple.Create("ToLower", "CurrentCultureIgnoreCase"), - Tuple.Create("ToUpper", "CurrentCultureIgnoreCase"), - Tuple.Create("ToLowerInvariant", "InvariantCultureIgnoreCase"), - Tuple.Create("ToUpperInvariant", "InvariantCultureIgnoreCase") + private static readonly (string, string)[] Cultures = new[] { + ("ToLower", "CurrentCultureIgnoreCase"), + ("ToUpper", "CurrentCultureIgnoreCase"), + ("ToLowerInvariant", "InvariantCultureIgnoreCase"), + ("ToUpperInvariant", "InvariantCultureIgnoreCase") }; private static readonly string[] ContainsStartsWith = new[] { "Contains", "StartsWith" }; private static readonly string[] UnnamedArgs = new[] { "", ", 1", ", 1, 1" }; + + private static readonly (string, string)[] CSharpComparisonOperators = new[] { + ("==", ""), + ("!=", "!") + }; + private static readonly (string, string)[] VisualBasicComparisonOperators = new[] { + ("=", ""), + ("<>", "Not ") + }; + private const string CSharpSeparator = ": "; private const string VisualBasicSeparator = ":="; @@ -319,6 +329,47 @@ private static IEnumerable DiagnosedAndFixedParenthesizedNamedInverted } } + public static IEnumerable CSharpDiagnosedAndFixedEqualityToEqualsData() => + DiagnosedAndFixedEqualityToEqualsData(CSharpComparisonOperators); + public static IEnumerable VisualBasicDiagnosedAndFixedEqualityToEqualsData() => + DiagnosedAndFixedEqualityToEqualsData(VisualBasicComparisonOperators); + private static IEnumerable DiagnosedAndFixedEqualityToEqualsData(ValueTuple[] comparisonOperators) + { +#pragma warning disable format + foreach (string casing in new[]{ "Lower", "Upper" }) + { + foreach ((string before, string after) in comparisonOperators) + { + yield return new object[] { $"a.To{casing}() {before} b.To{casing}()", $"{after}a.Equals(b, StringComparison.CurrentCultureIgnoreCase)" }; + yield return new object[] { $"a.To{casing}() {before} b.To{casing}Invariant()", $"{after}a.Equals(b, StringComparison.CurrentCultureIgnoreCase)" }; + yield return new object[] { $"a.To{casing}() {before} \"abc\"", $"{after}a.Equals(\"abc\", StringComparison.CurrentCultureIgnoreCase)" }; + yield return new object[] { $"a.To{casing}() {before} b", $"{after}a.Equals(b, StringComparison.CurrentCultureIgnoreCase)" }; + + yield return new object[] { $"a.To{casing}Invariant() {before} b.To{casing}Invariant()", $"{after}a.Equals(b, StringComparison.InvariantCultureIgnoreCase)" }; + yield return new object[] { $"a.To{casing}Invariant() {before} b.To{casing}()", $"{after}a.Equals(b, StringComparison.CurrentCultureIgnoreCase)" }; + yield return new object[] { $"a.To{casing}Invariant() {before} \"abc\"", $"{after}a.Equals(\"abc\", StringComparison.InvariantCultureIgnoreCase)" }; + yield return new object[] { $"a.To{casing}Invariant() {before} b", $"{after}a.Equals(b, StringComparison.InvariantCultureIgnoreCase)" }; + + yield return new object[] { $"a {before} b.To{casing}()", $"{after}a.Equals(b, StringComparison.CurrentCultureIgnoreCase)" }; + yield return new object[] { $"a {before} b.To{casing}Invariant()", $"{after}a.Equals(b, StringComparison.InvariantCultureIgnoreCase)" }; + + yield return new object[] { $"\"abc\" {before} b.To{casing}()", $"{after}\"abc\".Equals(b, StringComparison.CurrentCultureIgnoreCase)" }; + yield return new object[] { $"\"abc\" {before} b.To{casing}Invariant()", $"{after}\"abc\".Equals(b, StringComparison.InvariantCultureIgnoreCase)" }; + yield return new object[] { $"\"abc\".To{casing}() {before} b.To{casing}()", $"{after}\"abc\".Equals(b, StringComparison.CurrentCultureIgnoreCase)" }; + yield return new object[] { $"\"abc\".To{casing}() {before} b.To{casing}Invariant()", $"{after}\"abc\".Equals(b, StringComparison.CurrentCultureIgnoreCase)" }; + yield return new object[] { $"\"abc\".To{casing}Invariant() {before} b.To{casing}Invariant()", $"{after}\"abc\".Equals(b, StringComparison.InvariantCultureIgnoreCase)" }; + + yield return new object[] { $"GetString().To{casing}() {before} a.To{casing}()", $"{after}GetString().Equals(a, StringComparison.CurrentCultureIgnoreCase)" }; + yield return new object[] { $"GetString().To{casing}() {before} a.To{casing}Invariant()", $"{after}GetString().Equals(a, StringComparison.CurrentCultureIgnoreCase)" }; + yield return new object[] { $"GetString().To{casing}Invariant() {before} a.To{casing}()", $"{after}GetString().Equals(a, StringComparison.CurrentCultureIgnoreCase)" }; + yield return new object[] { $"GetString().To{casing}() {before} a", $"{after}GetString().Equals(a, StringComparison.CurrentCultureIgnoreCase)" }; + yield return new object[] { $"GetString().To{casing}Invariant() {before} a", $"{after}GetString().Equals(a, StringComparison.InvariantCultureIgnoreCase)" }; + yield return new object[] { $"GetString().To{casing}Invariant() {before} \"abc\"", $"{after}GetString().Equals(\"abc\", StringComparison.InvariantCultureIgnoreCase)" }; + } + } +#pragma warning restore format + } + public static IEnumerable NoDiagnosticData() { // Test needs to define a char ch and an object obj @@ -340,6 +391,7 @@ public static IEnumerable NoDiagnosticData() yield return new object[] { $"\"aBc\".ToLowerInvariant().{method}(ch, StringComparison.CurrentCulture)" }; } + // CompareTo yield return new object[] { "\"aBc\".CompareTo(obj)" }; yield return new object[] { "\"aBc\".ToLower().CompareTo(obj)" }; yield return new object[] { "\"aBc\".CompareTo(\"cDe\")" }; @@ -357,6 +409,25 @@ public static IEnumerable DiagnosticNoFixCompareToData() } } + public static IEnumerable CSharpDiagnosticNoFixEqualsData() => DiagnosticNoFixEqualsData(CSharpComparisonOperators); + public static IEnumerable VisualBasicDiagnosticNoFixEqualsData() => DiagnosticNoFixEqualsData(VisualBasicComparisonOperators); + + private static IEnumerable DiagnosticNoFixEqualsData(ValueTuple[] ops) + { + foreach ((string op, _) in ops) + { + yield return new object[] { $"\"aBc\".ToLower() {op} \"cDe\".ToLowerInvariant()" }; + yield return new object[] { $"\"aBc\".ToLower() {op} \"cDe\".ToUpperInvariant()" }; + yield return new object[] { $"\"aBc\".ToUpper() {op} \"cDe\".ToLowerInvariant()" }; + yield return new object[] { $"\"aBc\".ToUpper() {op} \"cDe\".ToUpperInvariant()" }; + + yield return new object[] { $"\"aBc\".ToLowerInvariant() {op} \"cDe\".ToLower()" }; + yield return new object[] { $"\"aBc\".ToLowerInvariant() {op} \"cDe\".ToUpper()" }; + yield return new object[] { $"\"aBc\".ToUpperInvariant() {op} \"cDe\".ToLower()" }; + yield return new object[] { $"\"aBc\".ToUpperInvariant() {op} \"cDe\".ToUpper()" }; + } + } + public static IEnumerable DiagnosticNoFixCompareToInvertedData() { // Tests need to define strings a, b, and methods GetStringA, GetStringB diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/RecommendCaseInsensitiveStringComparison.CSharp.Tests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/RecommendCaseInsensitiveStringComparison.CSharp.Tests.cs index 3f43cb3b41..e068602645 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/RecommendCaseInsensitiveStringComparison.CSharp.Tests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/RecommendCaseInsensitiveStringComparison.CSharp.Tests.cs @@ -211,6 +211,70 @@ string M() await VerifyFixCSharpAsync(originalCode, fixedCode); } + [Theory] + [MemberData(nameof(CSharpDiagnosedAndFixedEqualityToEqualsData))] + public async Task Diagnostic_Equality_To_Equals(string diagnosedLine, string fixedLine) + { + string originalCode = $@"using System; +class C +{{ + string GetString() => ""cde""; + bool M(string a, string b) + {{ + bool result = [|{diagnosedLine}|]; + if ([|{diagnosedLine}|]) return result; + return [|{diagnosedLine}|]; + }} +}}"; + string fixedCode = $@"using System; +class C +{{ + string GetString() => ""cde""; + bool M(string a, string b) + {{ + bool result = {fixedLine}; + if ({fixedLine}) return result; + return {fixedLine}; + }} +}}"; + await VerifyFixCSharpAsync(originalCode, fixedCode); + } + + [Fact] + public async Task Diagnostic_Equality_To_Equals_Trivia() + { + string originalCode = $@"using System; +class C +{{ + bool M(string a, string b) + {{ + // Trivia + bool /* Trivia */ result = /* Trivia */ [|a.ToLower() // Trivia + == /* Trivia */ b.ToLowerInvariant()|] /* Trivia */; // Trivia + if (/* Trivia */ [|a.ToLowerInvariant() /* Trivia */ != /* Trivia */ b.ToLower()|] /* Trivia */) // Trivia + return /* Trivia */ [|b /* Trivia */ != /* Trivia */ a.ToLowerInvariant()|] /* Trivia */; // Trivia + return // Trivia + [|""abc"" /* Trivia */ == /* Trivia */ a.ToUpperInvariant()|] /* Trivia */; // Trivia + // Trivia + }} +}}"; + string fixedCode = $@"using System; +class C +{{ + bool M(string a, string b) + {{ + // Trivia + bool /* Trivia */ result = /* Trivia */ a.Equals(b, StringComparison.CurrentCultureIgnoreCase) /* Trivia */; // Trivia + if (/* Trivia */ !a.Equals(b, StringComparison.CurrentCultureIgnoreCase) /* Trivia */) // Trivia + return /* Trivia */ !b /* Trivia */ .Equals /* Trivia */ (a, StringComparison.InvariantCultureIgnoreCase) /* Trivia */; // Trivia + return // Trivia + ""abc"" /* Trivia */ .Equals /* Trivia */ (a, StringComparison.InvariantCultureIgnoreCase) /* Trivia */; // Trivia + // Trivia + }} +}}"; + await VerifyFixCSharpAsync(originalCode, fixedCode); + } + [Theory] [MemberData(nameof(NoDiagnosticData))] [InlineData("\"aBc\".CompareTo(null)")] @@ -254,6 +318,22 @@ int M() await VerifyDiagnosticOnlyCSharpAsync(originalCode); } + [Theory] + [MemberData(nameof(CSharpDiagnosticNoFixEqualsData))] + public async Task Diagnostic_NoFix_Equals(string diagnosedLine) + { + string originalCode = $@"using System; +class C +{{ + bool M() + {{ + return [|{diagnosedLine}|]; + }} +}}"; + + await VerifyDiagnosticOnlyCSharpAsync(originalCode); + } + private async Task VerifyNoDiagnosticCSharpAsync(string originalSource) { VerifyCS.Test test = new() diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/RecommendCaseInsensitiveStringComparison.VisualBasic.Tests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/RecommendCaseInsensitiveStringComparison.VisualBasic.Tests.cs index b1ce5b0fb6..e9cc5e6f0b 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/RecommendCaseInsensitiveStringComparison.VisualBasic.Tests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/RecommendCaseInsensitiveStringComparison.VisualBasic.Tests.cs @@ -218,6 +218,77 @@ End Function await VerifyFixVisualBasicAsync(originalCode, fixedCode); } + [Theory] + [MemberData(nameof(VisualBasicDiagnosedAndFixedEqualityToEqualsData))] + public async Task Diagnostic_Equality_To_Equals(string diagnosedLine, string fixedLine) + { + string originalCode = $@"Imports System +Class C + Function GetString() As String + Return ""cde"" + End Function + Function M(a As String, b As String) As Boolean + Dim result As Boolean = [|{diagnosedLine}|] + If [|{diagnosedLine}|] Then + Return result + End If + Return [|{diagnosedLine}|] + End Function +End Class"; + string fixedCode = $@"Imports System +Class C + Function GetString() As String + Return ""cde"" + End Function + Function M(a As String, b As String) As Boolean + Dim result As Boolean = {fixedLine} + If {fixedLine} Then + Return result + End If + Return {fixedLine} + End Function +End Class"; + await VerifyFixVisualBasicAsync(originalCode, fixedCode); + } + + [Fact] + public async Task Diagnostic_Equality_To_Equals_Trivia() + { + string originalCode = $@"Imports System +Class C + Function M(a As String, b As String) As Boolean + ' Trivia1 + Dim result As Boolean = [|a.ToLower() = b.ToLowerInvariant()|] ' Trivia2 + ' Trivia3 + If [|a.ToLowerInvariant() <> b.ToLower()|] Then ' Trivia4 + ' Trivia5 + Return [|b <> a.ToLowerInvariant()|] ' Trivia6 + ' Trivia7 + End If + ' Trivia8 + Return [|""abc"" = a.ToUpperInvariant()|] ' Trivia9 + ' Trivia10 + End Function +End Class"; + string fixedCode = $@"Imports System +Class C + Function M(a As String, b As String) As Boolean + ' Trivia1 + Dim result As Boolean = a.Equals(b, StringComparison.CurrentCultureIgnoreCase) ' Trivia2 + ' Trivia3 + If Not a.Equals(b, StringComparison.CurrentCultureIgnoreCase) Then ' Trivia4 + ' Trivia5 + Return Not b.Equals(a, StringComparison.InvariantCultureIgnoreCase) ' Trivia6 + ' Trivia7 + End If + ' Trivia8 + Return ""abc"".Equals(a, StringComparison.InvariantCultureIgnoreCase) ' Trivia9 + ' Trivia10 + End Function +End Class"; + await VerifyFixVisualBasicAsync(originalCode, fixedCode); + } + [Theory] [MemberData(nameof(NoDiagnosticData))] [InlineData("\"aBc\".CompareTo(Nothing)")] @@ -261,6 +332,20 @@ End Function await VerifyDiagnosticOnlyVisualBasicAsync(originalCode); } + [Theory] + [MemberData(nameof(VisualBasicDiagnosticNoFixEqualsData))] + public async Task Diagnostic_NoFix_Equals(string diagnosedLine) + { + string originalCode = $@"Imports System +Class C + Public Function M() As Boolean + Return [|{diagnosedLine}|] + End Function +End Class"; + + await VerifyDiagnosticOnlyVisualBasicAsync(originalCode); + } + private async Task VerifyNoDiagnosticVisualBasicAsync(string originalSource) { VerifyVB.Test test = new() diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Performance/BasicRecommendCaseInsensitiveStringComparisonFixer.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Performance/BasicRecommendCaseInsensitiveStringComparisonFixer.vb index 7a9a0a2de2..61f1b6c770 100644 --- a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Performance/BasicRecommendCaseInsensitiveStringComparisonFixer.vb +++ b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Performance/BasicRecommendCaseInsensitiveStringComparisonFixer.vb @@ -14,8 +14,9 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Performance Public NotInheritable Class BasicRecommendCaseInsensitiveStringComparisonFixer Inherits RecommendCaseInsensitiveStringComparisonFixer - Protected Overrides Function GetNewArguments(generator As SyntaxGenerator, mainInvocationOperation As IInvocationOperation, - stringComparisonType As INamedTypeSymbol, ByRef mainInvocationInstance As SyntaxNode) As List(Of SyntaxNode) + Protected Overrides Function GetNewArgumentsForInvocation(generator As SyntaxGenerator, caseChangingApproachValue As String, + mainInvocationOperation As IInvocationOperation, stringComparisonType As INamedTypeSymbol, + ByRef mainInvocationInstance As SyntaxNode) As IEnumerable(Of SyntaxNode) Dim paramName As String = RecommendCaseInsensitiveStringComparisonAnalyzer.StringParameterName @@ -24,8 +25,8 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Performance Dim invocationExpression As InvocationExpressionSyntax = DirectCast(mainInvocationOperation.Syntax, InvocationExpressionSyntax) - Dim caseChangingApproachName As String = "" Dim isChangingCaseInArgument As Boolean = False + mainInvocationInstance = Nothing Dim memberAccessExpression As MemberAccessExpressionSyntax = TryCast(invocationExpression.Expression, MemberAccessExpressionSyntax) @@ -48,7 +49,6 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Performance If internalMemberAccessExpression IsNot Nothing Then mainInvocationInstance = internalMemberAccessExpression.Expression - caseChangingApproachName = GetCaseChangingApproach(internalMemberAccessExpression.Name.Identifier.ValueText) Else mainInvocationInstance = memberAccessExpression.Expression isChangingCaseInArgument = True @@ -72,9 +72,6 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Performance If argumentInvocationExpression IsNot Nothing Then argumentMemberAccessExpression = TryCast(argumentInvocationExpression.Expression, MemberAccessExpressionSyntax) - If argumentMemberAccessExpression IsNot Nothing Then - caseChangingApproachName = GetCaseChangingApproach(argumentMemberAccessExpression.Name.Identifier.ValueText) - End If End If Dim newArgumentNode As SyntaxNode @@ -96,14 +93,13 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Performance End If - arguments.Add(newArgumentNode) + arguments.Add(newArgumentNode.WithTriviaFrom(node)) Next - Debug.Assert(caseChangingApproachName IsNot Nothing) Debug.Assert(mainInvocationInstance IsNot Nothing) - Dim stringComparisonArgument As SyntaxNode = GetNewStringComparisonArgument(generator, stringComparisonType, caseChangingApproachName, isAnyArgumentNamed) + Dim stringComparisonArgument As SyntaxNode = GetNewStringComparisonArgument(generator, stringComparisonType, caseChangingApproachValue, isAnyArgumentNamed) arguments.Add(stringComparisonArgument) @@ -111,6 +107,15 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Performance End Function + Protected Overrides Function GetNewArgumentsForBinary(generator As SyntaxGenerator, rightNode As SyntaxNode, typeMemberAccess As SyntaxNode) As IEnumerable(Of SyntaxNode) + + Return New List(Of SyntaxNode) From + { + generator.Argument(rightNode.WithoutTrivia()),' Need To remove any trivia because otherwise an unexpected newline is added + generator.Argument(typeMemberAccess) + } + + End Function End Class End Namespace \ No newline at end of file