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

Add UseStringMethodCharOverloadWithSingleCharacters analyzer and fixer #6799

Merged
merged 25 commits into from Jul 26, 2023
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -0,0 +1,81 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.

using System.Composition;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.Operations;
using Microsoft.NetCore.Analyzers.Performance;

namespace Microsoft.NetCore.CSharp.Analyzers.Performance
{
[ExportCodeFixProvider(LanguageNames.CSharp), Shared]
public sealed class CSharpUseStringMethodCharOverloadWithSingleCharactersFixer : UseStringMethodCharOverloadWithSingleCharactersFixer
{
protected override bool TryGetChar(SemanticModel model, SyntaxNode argumentListNode, out char c)
{
c = default;

if (argumentListNode is not ArgumentListSyntax argumentList)
{
return false;
}

ArgumentSyntax? stringArgumentNode = null;
foreach (var argument in argumentList.Arguments)
{
var argumentOperation = model.GetOperation(argument) as IArgumentOperation;
if (argumentOperation?.Parameter != null && argumentOperation.Parameter.Ordinal == 0)
{
stringArgumentNode = argument;
break;
}
}

if (stringArgumentNode != null &&
stringArgumentNode.Expression is LiteralExpressionSyntax containedLiteralExpressionSyntax)
{
return TryGetCharFromLiteralExpressionSyntax(containedLiteralExpressionSyntax, out c);
}

return false;

static bool TryGetCharFromLiteralExpressionSyntax(LiteralExpressionSyntax sourceLiteralExpressionSyntax, out char parsedCharLiteral)
{
parsedCharLiteral = default;
if (sourceLiteralExpressionSyntax.Token.Value is string sourceLiteralValue && char.TryParse(sourceLiteralValue, out parsedCharLiteral))
{
return true;
}

return false;
}
}

protected override CodeAction CreateCodeAction(Document document, SyntaxNode argumentListNode, char sourceCharLiteral)
{
return new CSharpReplaceStringLiteralWithCharLiteralCodeAction(document, argumentListNode, sourceCharLiteral);
}

private sealed class CSharpReplaceStringLiteralWithCharLiteralCodeAction : ReplaceStringLiteralWithCharLiteralCodeAction
{
public CSharpReplaceStringLiteralWithCharLiteralCodeAction(Document document, SyntaxNode argumentListNode, char sourceCharLiteral) : base(document, argumentListNode, sourceCharLiteral)
{
}

protected override void ApplyFix(
DocumentEditor editor,
SyntaxNode oldArgumentListNode,
char c)
{
var argumentNode = editor.Generator.Argument(editor.Generator.LiteralExpression(c));
var argumentListNode = SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(new[] { argumentNode }));
mrahhal marked this conversation as resolved.
Show resolved Hide resolved

editor.ReplaceNode(oldArgumentListNode, argumentListNode);
}
}
}
}
@@ -0,0 +1,18 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.

using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.NetCore.Analyzers.Performance;

namespace Microsoft.CodeAnalysis.CSharp.NetAnalyzers.Microsoft.NetCore.Analyzers.Performance
{
/// <inheritdoc/>
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class CSharpUseStringMethodCharOverloadWithSingleCharacters : UseStringMethodCharOverloadWithSingleCharacters
{
protected override SyntaxNode? GetArgumentList(SyntaxNode argumentNode)
{
return argumentNode.FirstAncestorOrSelf<ArgumentListSyntax>();
}
}
}
3 changes: 3 additions & 0 deletions src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md
Expand Up @@ -4,6 +4,9 @@

Rule ID | Category | Severity | Notes
--------|----------|----------|-------
CA1865 | Performance | Info | UseStringMethodCharOverloadWithSingleCharacters, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1865)
CA1866 | Performance | Info | UseStringMethodCharOverloadWithSingleCharacters, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1866)
CA1867 | Performance | Disabled | UseStringMethodCharOverloadWithSingleCharacters, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1867)
CA2261 | Usage | Warning | DoNotUseConfigureAwaitWithSuppressThrowing, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2250)
CA1510 | Maintainability | Info | UseExceptionThrowHelpers, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1510)
CA1511 | Maintainability | Info | UseExceptionThrowHelpers, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1511)
Expand Down
Expand Up @@ -2105,4 +2105,13 @@ Widening and user defined conversions are not supported with generic types.</val
<data name="UseCompositeFormatDescription" xml:space="preserve">
<value>Cache and use a 'CompositeFormat' instance as the argument to this formatting operation, rather than passing in the original format string. This reduces the cost of the formatting operation.</value>
</data>
<data name="UseStringMethodCharOverloadWithSingleCharactersDescription" xml:space="preserve">
<value>The char overload is a better performing overload for a string with a single char.</value>
</data>
<data name="UseStringMethodCharOverloadWithSingleCharactersMessage" xml:space="preserve">
<value>Use '{0}' instead of '{1}' when you have a string with a single char</value>
</data>
<data name="UseStringMethodCharOverloadWithSingleCharactersTitle" xml:space="preserve">
<value>Use char overload</value>
</data>
</root>
@@ -0,0 +1,69 @@
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information.

using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Analyzer.Utilities;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.Editing;

namespace Microsoft.NetCore.Analyzers.Performance
{
public abstract class UseStringMethodCharOverloadWithSingleCharactersFixer : CodeFixProvider
{
public override ImmutableArray<string> FixableDiagnosticIds { get; } = ImmutableArray.Create(
UseStringMethodCharOverloadWithSingleCharacters.SafeTransformationRule.Id);

public override async Task RegisterCodeFixesAsync(CodeFixContext context)
{
var root = await context.Document.GetRequiredSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false);
var argumentListNode = root.FindNode(context.Span, getInnermostNodeForTie: true);

var model = await context.Document.GetRequiredSemanticModelAsync(context.CancellationToken).ConfigureAwait(false);
if (TryGetChar(model, argumentListNode, out var c))
{
context.RegisterCodeFix(CreateCodeAction(context.Document, argumentListNode, c), context.Diagnostics);
}
}

public override FixAllProvider GetFixAllProvider()
{
return WellKnownFixAllProviders.BatchFixer;
}

protected abstract bool TryGetChar(SemanticModel model, SyntaxNode argumentListNode, out char c);

protected abstract CodeAction CreateCodeAction(Document document, SyntaxNode argumentListNode, char sourceCharLiteral);

protected abstract class ReplaceStringLiteralWithCharLiteralCodeAction : CodeAction
{
private readonly Document _document;
private readonly SyntaxNode _argumentListNode;
private readonly char _sourceCharLiteral;

protected ReplaceStringLiteralWithCharLiteralCodeAction(Document document, SyntaxNode argumentListNode, char sourceCharLiteral)
{
_document = document;
_argumentListNode = argumentListNode;
_sourceCharLiteral = sourceCharLiteral;
}

public override string Title => MicrosoftNetCoreAnalyzersResources.ReplaceStringLiteralWithCharLiteralCodeActionTitle;

public override string EquivalenceKey => nameof(ReplaceStringLiteralWithCharLiteralCodeAction);

protected abstract void ApplyFix(DocumentEditor editor, SyntaxNode oldArgumentListNode, char c);

protected override async Task<Document> GetChangedDocumentAsync(CancellationToken cancellationToken)
{
var editor = await DocumentEditor.CreateAsync(_document, cancellationToken).ConfigureAwait(false);

ApplyFix(editor, _argumentListNode, _sourceCharLiteral);

return editor.GetChangedDocument();
}
}
}
}