-
Notifications
You must be signed in to change notification settings - Fork 470
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add CA1872: Prefer 'Convert.ToHexString' over 'BitConverter.ToString' (…
…#6967) * Add CA1872: Prefer 'Convert.ToHexString' over 'BitConverter.ToString' This analyzer detects the usage of the pattern `BitConverter.ToString(bytes).Replace("-", "")` to convert an array of bytes to an uppercase hex string (without hyphens in between) and replaces it with a call to `Convert.ToHexString(bytes)`. The analyzer will also try to preserve chaining `ToLower*` in between for a lowercase hex string. * Use span overload when replacing call with two arguments * Use Convert.ToHexStringLower if available * Improve resource strings * Improve description resource string * Remove redundant helper methods * Change invocation analyze order and remove duplicate work in fixer * Remove temporary Net90 reference assembly
- Loading branch information
Showing
23 changed files
with
2,272 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
126 changes: 126 additions & 0 deletions
126
...Microsoft.NetCore.Analyzers/Performance/PreferConvertToHexStringOverBitConverter.Fixer.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
// 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.Immutable; | ||
using System.Composition; | ||
using System.Globalization; | ||
using System.Linq; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using Analyzer.Utilities; | ||
using Analyzer.Utilities.Extensions; | ||
using Microsoft.CodeAnalysis; | ||
using Microsoft.CodeAnalysis.CodeActions; | ||
using Microsoft.CodeAnalysis.CodeFixes; | ||
using Microsoft.CodeAnalysis.Editing; | ||
using Microsoft.CodeAnalysis.Operations; | ||
using Microsoft.CodeAnalysis.Text; | ||
|
||
namespace Microsoft.NetCore.Analyzers.Performance | ||
{ | ||
using static MicrosoftNetCoreAnalyzersResources; | ||
|
||
/// <summary> | ||
/// CA1872: <inheritdoc cref="PreferConvertToHexStringOverBitConverterTitle"/> | ||
/// </summary> | ||
[ExportCodeFixProvider(LanguageNames.CSharp, LanguageNames.VisualBasic), Shared] | ||
public sealed class PreferConvertToHexStringOverBitConverterFixer : CodeFixProvider | ||
{ | ||
private static readonly SyntaxAnnotation s_asSpanSymbolAnnotation = new("SymbolId", WellKnownTypeNames.SystemMemoryExtensions); | ||
|
||
public sealed override ImmutableArray<string> FixableDiagnosticIds { get; } = | ||
ImmutableArray.Create(PreferConvertToHexStringOverBitConverterAnalyzer.RuleId); | ||
|
||
public sealed override FixAllProvider GetFixAllProvider() | ||
{ | ||
return WellKnownFixAllProviders.BatchFixer; | ||
} | ||
|
||
public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) | ||
{ | ||
var diagnostic = context.Diagnostics.FirstOrDefault(); | ||
|
||
if (diagnostic is not { AdditionalLocations.Count: > 0, Properties.Count: 1 } || | ||
!diagnostic.Properties.TryGetValue(PreferConvertToHexStringOverBitConverterAnalyzer.ReplacementPropertiesKey, out var convertToHexStringName) || | ||
convertToHexStringName is null) | ||
{ | ||
return; | ||
} | ||
|
||
var root = await context.Document.GetRequiredSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); | ||
var semanticModel = await context.Document.GetRequiredSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); | ||
|
||
var bitConverterInvocation = GetInvocationFromTextSpan(diagnostic.AdditionalLocations[0].SourceSpan); | ||
var outerInvocation = GetInvocationFromTextSpan(context.Span); | ||
|
||
if (bitConverterInvocation is null || outerInvocation is null) | ||
{ | ||
return; | ||
} | ||
|
||
var toLowerInvocation = diagnostic.AdditionalLocations.Count == 2 | ||
? GetInvocationFromTextSpan(diagnostic.AdditionalLocations[1].SourceSpan) | ||
: null; | ||
|
||
var codeAction = CodeAction.Create( | ||
string.Format(CultureInfo.CurrentCulture, PreferConvertToHexStringOverBitConverterCodeFixTitle, convertToHexStringName), | ||
ReplaceWithConvertToHexStringCall, | ||
nameof(PreferConvertToHexStringOverBitConverterCodeFixTitle)); | ||
|
||
context.RegisterCodeFix(codeAction, context.Diagnostics); | ||
|
||
IInvocationOperation? GetInvocationFromTextSpan(TextSpan span) | ||
{ | ||
var node = root.FindNode(span, getInnermostNodeForTie: true); | ||
|
||
if (node is null) | ||
{ | ||
return null; | ||
} | ||
|
||
return semanticModel.GetOperation(node, context.CancellationToken) as IInvocationOperation; | ||
} | ||
|
||
async Task<Document> ReplaceWithConvertToHexStringCall(CancellationToken cancellationToken) | ||
{ | ||
var editor = await DocumentEditor.CreateAsync(context.Document, cancellationToken).ConfigureAwait(false); | ||
var generator = editor.Generator; | ||
var bitConverterArgumentsInParameterOrder = bitConverterInvocation.Arguments.GetArgumentsInParameterOrder(); | ||
|
||
var typeExpression = generator.DottedName(WellKnownTypeNames.SystemConvert); | ||
var methodExpression = generator.MemberAccessExpression(typeExpression, convertToHexStringName); | ||
var methodInvocation = bitConverterArgumentsInParameterOrder.Length switch | ||
{ | ||
// BitConverter.ToString(data).Replace("-", "") => Convert.ToHexString(data) | ||
1 => generator.InvocationExpression(methodExpression, bitConverterArgumentsInParameterOrder[0].Value.Syntax), | ||
// BitConverter.ToString(data, start).Replace("-", "") => Convert.ToHexString(data.AsSpan().Slice(start)) | ||
2 => generator.InvocationExpression( | ||
methodExpression, | ||
generator.InvocationExpression(generator.MemberAccessExpression( | ||
generator.InvocationExpression(generator.MemberAccessExpression( | ||
bitConverterArgumentsInParameterOrder[0].Value.Syntax, | ||
nameof(MemoryExtensions.AsSpan))), | ||
WellKnownMemberNames.SliceMethodName), | ||
bitConverterArgumentsInParameterOrder[1].Value.Syntax)) | ||
.WithAddImportsAnnotation() | ||
.WithAdditionalAnnotations(s_asSpanSymbolAnnotation), | ||
// BitConverter.ToString(data, start, length).Replace("-", "") => Convert.ToHexString(data, start, length) | ||
3 => generator.InvocationExpression(methodExpression, bitConverterArgumentsInParameterOrder.Select(a => a.Value.Syntax).ToArray()), | ||
_ => throw new NotImplementedException() | ||
}; | ||
|
||
// This branch is hit when string.ToLower* is used and Convert.ToHexStringLower is not available. | ||
if (toLowerInvocation is not null) | ||
{ | ||
methodInvocation = generator.InvocationExpression( | ||
generator.MemberAccessExpression(methodInvocation, toLowerInvocation.TargetMethod.Name), | ||
toLowerInvocation.Arguments.Select(a => a.Value.Syntax).ToArray()); | ||
} | ||
|
||
editor.ReplaceNode(outerInvocation.Syntax, methodInvocation.WithTriviaFrom(outerInvocation.Syntax)); | ||
|
||
return context.Document.WithSyntaxRoot(editor.GetChangedRoot()); | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.