Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recommend using Equals with StringComparison instead of string.ToLower() == otherString.ToLower() #6720

Merged
merged 12 commits into from
Jul 26, 2023
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -17,30 +17,27 @@ namespace Microsoft.NetCore.CSharp.Analyzers.Performance
[ExportCodeFixProvider(LanguageNames.CSharp), Shared]
public sealed class CSharpRecommendCaseInsensitiveStringComparisonFixer : RecommendCaseInsensitiveStringComparisonFixer
{
protected override List<SyntaxNode> GetNewArguments(SyntaxGenerator generator, IInvocationOperation mainInvocationOperation,
protected override IEnumerable<SyntaxNode> GetNewArgumentsForInvocation(SyntaxGenerator generator, string caseChangingApproachValue, IInvocationOperation mainInvocationOperation,
INamedTypeSymbol stringComparisonType, out SyntaxNode? mainInvocationInstance)
{
List<SyntaxNode> arguments = new();
bool isAnyArgumentNamed = false;

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;

if (internalExpression is InvocationExpressionSyntax internalInvocationExpression &&
internalInvocationExpression.Expression is MemberAccessExpressionSyntax internalMemberAccessExpression)
{
mainInvocationInstance = internalMemberAccessExpression.Expression;
caseChangingApproachName = GetCaseChangingApproach(internalMemberAccessExpression.Name.Identifier.ValueText);
}
else
{
Expand All @@ -59,10 +56,9 @@ protected override List<SyntaxNode> 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;
Expand All @@ -84,18 +80,23 @@ protected override List<SyntaxNode> 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<SyntaxNode> GetNewArgumentsForBinary(SyntaxGenerator generator, SyntaxNode rightNode, SyntaxNode typeMemberAccess) =>
new List<SyntaxNode>()
{
generator.Argument(rightNode),
generator.Argument(typeMemberAccess)
};
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -115,18 +123,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<IMethodSymbol>().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

Expand All @@ -153,13 +155,26 @@ 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)
{
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)
Expand Down Expand Up @@ -188,51 +203,172 @@ 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))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems these toLowerParameterlessMethod, toLowerInvariantParameterlessMethod, toUpperParameterlessMethod, toUpperInvariantParameterlessMethod symbols not used anymore, could we remove the rows populating them?

IMethodSymbol? toLowerParameterlessMethod = stringType.GetMembers(StringToLowerMethodName).OfType<IMethodSymbol>().GetFirstOrDefaultMemberWithParameterInfos();
if (toLowerParameterlessMethod == null)
{
return;
}
IMethodSymbol? toLowerInvariantParameterlessMethod = stringType.GetMembers(StringToLowerInvariantMethodName).OfType<IMethodSymbol>().GetFirstOrDefaultMemberWithParameterInfos();
if (toLowerInvariantParameterlessMethod == null)
{
return;
}
IMethodSymbol? toUpperParameterlessMethod = stringType.GetMembers(StringToUpperMethodName).OfType<IMethodSymbol>().GetFirstOrDefaultMemberWithParameterInfos();
if (toUpperParameterlessMethod == null)
{
return;
}
IMethodSymbol? toUpperInvariantParameterlessMethod = stringType.GetMembers(StringToUpperInvariantMethodName).OfType<IMethodSymbol>().GetFirstOrDefaultMemberWithParameterInfos();
if (toUpperInvariantParameterlessMethod == null)
{
return;
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we need to exit early if we don't find the symbol?

Copy link
Contributor

@buyaa-n buyaa-n Jul 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was actually part of the question, these symbols are not used anywhere, should we keep loading them and exit early? Or they required to be existed to proceed further analysis?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are used here to exit early if the symbols aren't found.

{
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<string, string?> dict = new Dictionary<string, string?>()
{
{ CaseChangingApproachName, caseChangingApproach }
}.ToImmutableDictionary();
Comment on lines +250 to +253
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT:

Suggested change
ImmutableDictionary<string, string?> dict = new Dictionary<string, string?>()
{
{ CaseChangingApproachName, caseChangingApproach }
}.ToImmutableDictionary();
ImmutableDictionary<string, string> dict = ImmutableDictionary.CreateRange(
new Dictionary<string, string> { { CaseChangingApproachName, caseChangingApproach } });

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is that actually better? I am still allocating a dictionary with the suggestion, so I'm not sure if there's an improvement.
BTW the second typeparam should be a string?, otherwise it complains.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if there's an improvement.

I would expect it to be better, though do not know for sure, that is why I put NIT


IMethodSymbol diagnosableMethod = diagnosableInvocation.TargetMethod;

if (diagnosableMethod.Equals(containsStringMethod) ||
diagnosableMethod.Equals(startsWithStringMethod) ||
diagnosableMethod.Equals(indexOfStringMethod) ||
diagnosableMethod.Equals(indexOfStringInt32Method) ||
diagnosableMethod.Equals(indexOfStringInt32Int32Method))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems IMethodSymbol containsStringMethod, IMethodSymbol startsWithStringMethod, IMethodSymbol indexOfStringMethod, IMethodSymbol indexOfStringInt32Method, IMethodSymbol indexOfStringInt32Int32Method all these IMethodSymbols created and used similarly, could we add them into a ImmutableDictionary<IMethodSymbol> and check if it contains the diagnosableMethod?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you show me how to pass an ImmutableDictionary<IMethodSymbol>? I am not sure if any of the available CreateDiagnostic method overloads would let me do such thing.

Copy link
Contributor

@buyaa-n buyaa-n Jul 17, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, sorry I meant ImmutableArray not ImmutableDictionary, I meant populate them once and add them into a ImmutableArray<IMethodSymbol> list and instead of these multiple ORs check if (list.Contains(diagnosableMethod))

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can save some space by not allocating a new ImmutableList.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even though it would be faster for lookup, and it will be cleaner to use without passing so many parameters. Though my concern was more about that all symbols are loaded one by one using Linq, they could have populated with one or 2 loops, even without adding them into a ImmutableArray.

if (!context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemStringComparison, out INamedTypeSymbol? stringComparisonType))
{
return;
}
if (!context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemStringComparer, out INamedTypeSymbol? stringComparerType))
{
return;
}
// Retrieve the offending parameterless methods: ToLower, ToLowerInvariant, ToUpper, ToUpperInvariant
IMethodSymbol? toLowerParameterlessMethod = stringType.GetMembers(StringToLowerMethodName).OfType<IMethodSymbol>().GetFirstOrDefaultMemberWithParameterInfos();
if (toLowerParameterlessMethod == null)
{
return;
}
IMethodSymbol? toLowerInvariantParameterlessMethod = stringType.GetMembers(StringToLowerInvariantMethodName).OfType<IMethodSymbol>().GetFirstOrDefaultMemberWithParameterInfos();
if (toLowerInvariantParameterlessMethod == null)
{
return;
}
IMethodSymbol? toUpperParameterlessMethod = stringType.GetMembers(StringToUpperMethodName).OfType<IMethodSymbol>().GetFirstOrDefaultMemberWithParameterInfos();
if (toUpperParameterlessMethod == null)
{
return;
}
IMethodSymbol? toUpperInvariantParameterlessMethod = stringType.GetMembers(StringToUpperInvariantMethodName).OfType<IMethodSymbol>().GetFirstOrDefaultMemberWithParameterInfos();
if (toUpperInvariantParameterlessMethod == null)
{
return;
}
// Create the different expected parameter combinations
ParameterInfo[] stringParameter = new[]
{
ParameterInfo.GetParameterInfo(stringType)
};
// Equals(string)
IMethodSymbol? stringEqualsStringMethod = stringType.GetMembers(StringEqualsMethodName).OfType<IMethodSymbol>().GetFirstOrDefaultMemberWithParameterInfos(stringParameter);
if (stringEqualsStringMethod == null)
{
return;
}
// Retrieve the diagnosable string overload methods: Contains, IndexOf (3 overloads), StartsWith, CompareTo
// Contains(string)
IMethodSymbol? containsStringMethod = stringType.GetMembers(StringContainsMethodName).OfType<IMethodSymbol>().GetFirstOrDefaultMemberWithParameterInfos(stringParameter);
if (containsStringMethod == null)
{
return;
}
// StartsWith(string)
IMethodSymbol? startsWithStringMethod = stringType.GetMembers(StringStartsWithMethodName).OfType<IMethodSymbol>().GetFirstOrDefaultMemberWithParameterInfos(stringParameter);
if (startsWithStringMethod == null)
{
return;
}
IEnumerable<IMethodSymbol> indexOfMethods = stringType.GetMembers(StringIndexOfMethodName).OfType<IMethodSymbol>();
// IndexOf(string)
IMethodSymbol? indexOfStringMethod = indexOfMethods.GetFirstOrDefaultMemberWithParameterInfos(stringParameter);
if (indexOfStringMethod == null)
{
return;
}
ParameterInfo[] stringInt32Parameters = new[]
{
ParameterInfo.GetParameterInfo(stringType),
ParameterInfo.GetParameterInfo(int32Type)
};
// IndexOf(string, int startIndex)
IMethodSymbol? indexOfStringInt32Method = indexOfMethods.GetFirstOrDefaultMemberWithParameterInfos(stringInt32Parameters);
if (indexOfStringInt32Method == null)
{
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)
{
return;
}
// CompareTo(string)
IMethodSymbol? compareToStringMethod = stringType.GetMembers(StringCompareToMethodName).OfType<IMethodSymbol>().GetFirstOrDefaultMemberWithParameterInfos(stringParameter);
if (compareToStringMethod == null)
{
return;
}
// Retrieve the StringComparer properties that need to be flagged: CurrentCultureIgnoreCase, InvariantCultureIgnoreCase
IEnumerable<IPropertySymbol> ccicPropertyGroup = stringComparerType.GetMembers(StringComparisonCurrentCultureIgnoreCaseName).OfType<IPropertySymbol>();
if (!ccicPropertyGroup.Any())
{
return;
}
IEnumerable<IPropertySymbol> icicPropertyGroup = stringComparerType.GetMembers(StringComparisonInvariantCultureIgnoreCaseName).OfType<IPropertySymbol>();
if (!icicPropertyGroup.Any())
{
return;
}

But as that logic was not added with this PR so its not have to be handled in this PR

{
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<string, string?> dict = new Dictionary<string, string?>()
{
context.ReportDiagnostic(diagnosableInvocation.CreateDiagnostic(RecommendCaseInsensitiveStringComparerRule));
}
}, OperationKind.Invocation);
{ CaseChangingApproachName, caseChangingApproachValue }
}.ToImmutableDictionary();

context.ReportDiagnostic(
binaryOperation.CreateDiagnostic(RecommendCaseInsensitiveStringComparerRule, 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;
}
}
}
Loading