Skip to content

Commit

Permalink
Recommend using Equals with StringComparison instead of string.ToLowe…
Browse files Browse the repository at this point in the history
…r() == otherString.ToLower() (#6720)

* Analyzer: Use string.Format with CultureInfo for title.

* Almost, only needs to handle inequality

* Fix inequality case, fix trivia, still need to add C# test

* Add C# tests for trivia and string method

* Add tests for diagnostic-only, no fix, for Equals that don't match variant/invariant.

* Use ValueTuple in tests

* Register operation actions separately.

* Fix recent nullability changes

* Use simplified valuetuple creation in tests

* Add extra rule and resource messages for the 'string.Method() == string.Method()' comparison case.

* msbuild /t:pack

* Address suggestion
  • Loading branch information
carlossanlop authored Jul 26, 2023
1 parent 1121367 commit f23133f
Show file tree
Hide file tree
Showing 24 changed files with 749 additions and 181 deletions.
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
Expand Up @@ -2066,14 +2066,20 @@ Widening and user defined conversions are not supported with generic types.</val
<data name="RecommendCaseInsensitiveStringComparerStringComparisonCodeFixTitle" xml:space="preserve">
<value>Use the 'string.{0}(string, StringComparison)' overload</value>
</data>
<data name="RecommendCaseInsensitiveStringComparerTitle" xml:space="preserve">
<value>Prefer using 'StringComparer' to perform case-insensitive string comparisons</value>
<data name="RecommendCaseInsensitiveStringEqualsCodeFixTitle" xml:space="preserve">
<value>Use 'string.Equals(string, StringComparison)'</value>
</data>
<data name="RecommendCaseInsensitiveStringComparerDescription" xml:space="preserve">
<value>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.</value>
</data>
<data name="RecommendCaseInsensitiveStringComparerMessage" xml:space="preserve">
<value>Prefer using 'StringComparer' to perform a case-insensitive comparison</value>
</data>
<data name="RecommendCaseInsensitiveStringEqualsDescription" xml:space="preserve">
<value>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.</value>
</data>
<data name="RecommendCaseInsensitiveStringEqualsMessage" xml:space="preserve">
<value>Prefer using 'string.Equals(string, StringComparison)' to perform a case-insensitive comparison</value>
</data>
<data name="RecommendCaseInsensitiveStringComparisonTitle" xml:space="preserve">
<value>Prefer the 'StringComparison' method overloads to perform case-insensitive string comparisons</value>
Expand Down
Loading

0 comments on commit f23133f

Please sign in to comment.