From 0c86a45a4472d2bb5aa19e0c47f63a2e133180df Mon Sep 17 00:00:00 2001 From: Mario Pistrich Date: Tue, 19 Mar 2024 21:39:22 +0100 Subject: [PATCH] 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 --- .../Core/AnalyzerReleases.Unshipped.md | 1 + .../MicrosoftNetCoreAnalyzersResources.resx | 12 + ...onvertToHexStringOverBitConverter.Fixer.cs | 126 ++ ...referConvertToHexStringOverBitConverter.cs | 357 ++++ .../MicrosoftNetCoreAnalyzersResources.cs.xlf | 20 + .../MicrosoftNetCoreAnalyzersResources.de.xlf | 20 + .../MicrosoftNetCoreAnalyzersResources.es.xlf | 20 + .../MicrosoftNetCoreAnalyzersResources.fr.xlf | 20 + .../MicrosoftNetCoreAnalyzersResources.it.xlf | 20 + .../MicrosoftNetCoreAnalyzersResources.ja.xlf | 20 + .../MicrosoftNetCoreAnalyzersResources.ko.xlf | 20 + .../MicrosoftNetCoreAnalyzersResources.pl.xlf | 20 + ...crosoftNetCoreAnalyzersResources.pt-BR.xlf | 20 + .../MicrosoftNetCoreAnalyzersResources.ru.xlf | 20 + .../MicrosoftNetCoreAnalyzersResources.tr.xlf | 20 + ...osoftNetCoreAnalyzersResources.zh-Hans.xlf | 20 + ...osoftNetCoreAnalyzersResources.zh-Hant.xlf | 20 + .../Microsoft.CodeAnalysis.NetAnalyzers.md | 12 + .../Microsoft.CodeAnalysis.NetAnalyzers.sarif | 20 + src/NetAnalyzers/RulesMissingDocumentation.md | 1 + ...ConvertToHexStringOverBitConverterTests.cs | 1481 +++++++++++++++++ .../DiagnosticCategoryAndIdRanges.txt | 2 +- src/Utilities/Compiler/WellKnownTypeNames.cs | 1 + 23 files changed, 2272 insertions(+), 1 deletion(-) create mode 100644 src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/PreferConvertToHexStringOverBitConverter.Fixer.cs create mode 100644 src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/PreferConvertToHexStringOverBitConverter.cs create mode 100644 src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/PreferConvertToHexStringOverBitConverterTests.cs diff --git a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md index 0f07608a16..0dd5e1879d 100644 --- a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md +++ b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md @@ -7,6 +7,7 @@ Rule ID | Category | Severity | Notes CA1514 | Maintainability | Info | AvoidLengthCheckWhenSlicingToEndAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1514) CA1515 | Maintainability | Disabled | MakeTypesInternal, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1515) CA1871 | Performance | Info | DoNotPassNonNullableValueToArgumentNullExceptionThrowIfNull, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1871) +CA1872 | Performance | Info | PreferConvertToHexStringOverBitConverterAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1872) CA2262 | Usage | Info | ProvideHttpClientHandlerMaxResponseHeaderLengthValueCorrectly, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2262) CA2263 | Usage | Info | PreferGenericOverloadsAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2263) CA2264 | Usage | Warning | DoNotPassNonNullableValueToArgumentNullExceptionThrowIfNull, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2264) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx index aa3ec90377..e998d4bee2 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx @@ -195,6 +195,18 @@ Do not use Count() or LongCount() when Any() can be used + + Prefer 'Convert.ToHexString' and 'Convert.ToHexStringLower' over call chains based on 'BitConverter.ToString' + + + Use 'Convert.ToHexString' or 'Convert.ToHexStringLower' when encoding bytes to a hexadecimal string representation. These methods are more efficient and allocation-friendly than using 'BitConverter.ToString' in combination with 'String.Replace' to replace dashes and 'String.ToLower'. + + + Prefer '{0}' over call chains based on '{1}' + + + Replace with 'Convert.{0}' + Do not use timers that prevent power state changes diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/PreferConvertToHexStringOverBitConverter.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/PreferConvertToHexStringOverBitConverter.Fixer.cs new file mode 100644 index 0000000000..7a493710de --- /dev/null +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/PreferConvertToHexStringOverBitConverter.Fixer.cs @@ -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; + + /// + /// CA1872: + /// + [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 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 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()); + } + } + } +} diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/PreferConvertToHexStringOverBitConverter.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/PreferConvertToHexStringOverBitConverter.cs new file mode 100644 index 0000000000..00483d6d2d --- /dev/null +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Performance/PreferConvertToHexStringOverBitConverter.cs @@ -0,0 +1,357 @@ +// 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.Diagnostics.CodeAnalysis; +using System.Linq; +using Analyzer.Utilities; +using Analyzer.Utilities.Extensions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; + +namespace Microsoft.NetCore.Analyzers.Performance +{ + using static MicrosoftNetCoreAnalyzersResources; + + /// + /// CA1872: + /// + [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] + public sealed class PreferConvertToHexStringOverBitConverterAnalyzer : DiagnosticAnalyzer + { + internal const string RuleId = "CA1872"; + internal const string ReplacementPropertiesKey = nameof(ReplacementPropertiesKey); + + private const string Empty = nameof(Empty); + private const string Replace = nameof(Replace); + private const string ToHexString = nameof(ToHexString); + private const string ToHexStringLower = nameof(ToHexStringLower); + private const string ToLower = nameof(ToLower); + private const string ToLowerInvariant = nameof(ToLowerInvariant); + + internal static readonly DiagnosticDescriptor Rule = DiagnosticDescriptorHelper.Create( + RuleId, + CreateLocalizableResourceString(nameof(PreferConvertToHexStringOverBitConverterTitle)), + CreateLocalizableResourceString(nameof(PreferConvertToHexStringOverBitConverterMessage)), + DiagnosticCategory.Performance, + RuleLevel.IdeSuggestion, + CreateLocalizableResourceString(nameof(PreferConvertToHexStringOverBitConverterDescription)), + isPortedFxCopRule: false, + isDataflowRule: false); + + public sealed override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); + + public sealed override void Initialize(AnalysisContext context) + { + context.EnableConcurrentExecution(); + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + context.RegisterCompilationStartAction(OnCompilationStart); + } + + private void OnCompilationStart(CompilationStartAnalysisContext context) + { + if (!RequiredSymbols.TryGetSymbols(context.Compilation, out var symbols)) + { + return; + } + + context.RegisterOperationAction(AnalyzeInvocation, OperationKind.Invocation); + + void AnalyzeInvocation(OperationAnalysisContext context) + { + var invocation = (IInvocationOperation)context.Operation; + + if (!symbols.TryGetBitConverterInvocationChain( + invocation, + out var bitConverterInvocation, + out var outerInvocation, + out var toLowerInvocation, + out var convertToHexStringReplacementMethod)) + { + return; + } + + var additionalLocationsBuilder = ImmutableArray.CreateBuilder(); + additionalLocationsBuilder.Add(bitConverterInvocation.Syntax.GetLocation()); + + if (toLowerInvocation is not null) + { + additionalLocationsBuilder.Add(toLowerInvocation.Syntax.GetLocation()); + } + + context.ReportDiagnostic(outerInvocation.CreateDiagnostic( + Rule, + additionalLocations: additionalLocationsBuilder.ToImmutable(), + properties: ImmutableDictionary.Empty.Add(ReplacementPropertiesKey, convertToHexStringReplacementMethod.Name), + args:[convertToHexStringReplacementMethod.ToDisplayString(), bitConverterInvocation.TargetMethod.ToDisplayString()])); + } + } + + private sealed class RequiredSymbols + { + private RequiredSymbols( + ImmutableArray stringReplaceMethods, + ImmutableArray stringToLowerMethods, + IFieldSymbol? stringEmptyField, + ImmutableDictionary bitConverterReplacements, + ImmutableDictionary bitConverterReplacementsToLower) + { + _stringReplaceMethods = stringReplaceMethods; + _stringToLowerMethods = stringToLowerMethods; + _stringEmptyField = stringEmptyField; + _bitConverterReplacements = bitConverterReplacements; + _bitConverterReplacementsToLower = bitConverterReplacementsToLower; + } + + public static bool TryGetSymbols(Compilation compilation, [NotNullWhen(true)] out RequiredSymbols? symbols) + { + symbols = default; + + var bitConverterType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemBitConverter); + var convertType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemConvert); + + if (bitConverterType is null || convertType is null) + { + return false; + } + + var byteType = compilation.GetSpecialType(SpecialType.System_Byte); + var byteArrayType = compilation.CreateArrayTypeSymbol(byteType); + var int32Type = compilation.GetSpecialType(SpecialType.System_Int32); + var stringType = compilation.GetSpecialType(SpecialType.System_String); + var rosByteType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemReadOnlySpan1)?.Construct(byteType); + + var stringReplaceMethods = stringType.GetMembers(Replace) + .OfType() + .WhereAsArray(FilterStringReplaceMethods); + + if (stringReplaceMethods.IsEmpty) + { + return false; + } + + var stringToLowerMethods = stringType.GetMembers(ToLower) + .AddRange(stringType.GetMembers(ToLowerInvariant)) + .OfType() + .ToImmutableArray(); + var stringEmptyField = stringType.GetMembers(Empty) + .OfType() + .FirstOrDefault(); + + var bitConverterToStringMethods = bitConverterType.GetMembers(WellKnownMemberNames.ObjectToString) + .OfType() + .ToImmutableArray(); + + var bitConverterToString = bitConverterToStringMethods.GetFirstOrDefaultMemberWithParameterTypes(byteArrayType); + var bitConverterToStringStart = bitConverterToStringMethods.GetFirstOrDefaultMemberWithParameterTypes(byteArrayType, int32Type); + var bitConverterToStringStartLength = bitConverterToStringMethods.GetFirstOrDefaultMemberWithParameterTypes(byteArrayType, int32Type, int32Type); + + var convertToHexStringMethods = convertType.GetMembers(ToHexString) + .OfType() + .ToImmutableArray(); + + var convertToHexString = convertToHexStringMethods.GetFirstOrDefaultMemberWithParameterTypes(byteArrayType); + var convertToHexStringRos = rosByteType is not null ? convertToHexStringMethods.GetFirstOrDefaultMemberWithParameterTypes(rosByteType) : null; + var convertToHexStringStartLength = convertToHexStringMethods.GetFirstOrDefaultMemberWithParameterTypes(byteArrayType, int32Type, int32Type); + + var bitConverterReplacementsBuilder = ImmutableDictionary.CreateBuilder(); + // BitConverter.ToString(data).Replace("-", "") => Convert.ToHexString(data) + bitConverterReplacementsBuilder.AddKeyValueIfNotNull(bitConverterToString, convertToHexString); + // BitConverter.ToString(data, start).Replace("-", "") => Convert.ToHexString(data.AsSpan().Slice(start)) + bitConverterReplacementsBuilder.AddKeyValueIfNotNull(bitConverterToStringStart, convertToHexStringRos); + // BitConverter.ToString(data, start, length).Replace("-", "") => Convert.ToHexString(data, start, length) + bitConverterReplacementsBuilder.AddKeyValueIfNotNull(bitConverterToStringStartLength, convertToHexStringStartLength); + var bitConverterReplacements = bitConverterReplacementsBuilder.ToImmutableDictionary(); + + // Bail out if we have no valid replacement pair from BitConverter.ToString to Convert.ToHexString. + if (bitConverterReplacements.IsEmpty) + { + return false; + } + + var convertToHexStringLowerMethods = convertType.GetMembers(ToHexStringLower) + .OfType() + .ToImmutableArray(); + + var convertToHexStringLower = convertToHexStringLowerMethods.GetFirstOrDefaultMemberWithParameterTypes(byteArrayType); + var convertToHexStringLowerRos = rosByteType is not null ? convertToHexStringLowerMethods.GetFirstOrDefaultMemberWithParameterTypes(rosByteType) : null; + var convertToHexStringLowerStartLength = convertToHexStringLowerMethods.GetFirstOrDefaultMemberWithParameterTypes(byteArrayType, int32Type, int32Type); + + // The following replacements are optional: Convert.ToHexStringLower is available as of .NET 9. + var bitConverterReplacementsToLowerBuilder = ImmutableDictionary.CreateBuilder(); + // BitConverter.ToString(data).Replace("-", "").ToLower() => Convert.ToHexStringLower(data) + bitConverterReplacementsToLowerBuilder.AddKeyValueIfNotNull(bitConverterToString, convertToHexStringLower); + // BitConverter.ToString(data, start).Replace("-", "").ToLower() => Convert.ToHexStringLower(data.AsSpan().Slice(start)) + bitConverterReplacementsToLowerBuilder.AddKeyValueIfNotNull(bitConverterToStringStart, convertToHexStringLowerRos); + // BitConverter.ToString(data, start, length).Replace("-", "").ToLower() => Convert.ToHexStringLower(data, start, length) + bitConverterReplacementsToLowerBuilder.AddKeyValueIfNotNull(bitConverterToStringStartLength, convertToHexStringLowerStartLength); + var bitConverterReplacementsToLower = bitConverterReplacementsToLowerBuilder.ToImmutableDictionary(); + + symbols = new RequiredSymbols(stringReplaceMethods, stringToLowerMethods, + stringEmptyField, bitConverterReplacements, bitConverterReplacementsToLower); + + return true; + + bool FilterStringReplaceMethods(IMethodSymbol stringReplaceMethod) + { + return stringReplaceMethod.Parameters.Length >= 2 && + SymbolEqualityComparer.Default.Equals(stringReplaceMethod.Parameters[0].Type, stringType) && + SymbolEqualityComparer.Default.Equals(stringReplaceMethod.Parameters[1].Type, stringType); + } + } + + /// + /// Attempts to obtain a complete BitConverter.ToString invocation chain that can be replaced with a call to Convert.ToHexString*. + /// To do this, the following steps are performed: + /// 1. Check if invocation is a BitConverter.ToString invocation and abort if it is not. + /// 2. Continue with the parent invocation or abort if none exists. + /// 3. Check if invocation is a string.ToLower* invocation. + /// If so, continue with the parent invocation or abort if none exists. + /// 4. Check if invocation is a string.Replace("-", "") invocation and abort if it is not. + /// If so, continue with the parent invocation or skip the next step if none exists. + /// 5. Check if invocation is a string.ToLower* invocation. + /// 6. Get the appropriate replacement method (from BitConverter.ToString to Convert.ToHexString or Convert.ToHexStringLower) or abort if none exists. + /// Note that is only set if a string.ToLower* invocation is found and Convert.ToHexStringLower is not available. + /// + /// The starting invocation to analyze (must be BitConverter.ToString for this method to return true) + /// The extracted BitConverter.ToString invocation, or null if unsuccessful + /// The outer invocation that is used for creating the diagnostic (and that will be replaced in the fixer) + /// The extracted string.ToLower* invocation, or null if none exists or Convert.ToHexStringLower is used as a replacement + /// The Convert.ToHexString* method to use as a replacement + /// + public bool TryGetBitConverterInvocationChain( + IInvocationOperation invocation, + [NotNullWhen(true)] out IInvocationOperation? bitConverterInvocation, + [NotNullWhen(true)] out IInvocationOperation? outerInvocation, + out IInvocationOperation? toLowerInvocation, + [NotNullWhen(true)] out IMethodSymbol? replacementMethod) + { + bitConverterInvocation = default; + outerInvocation = default; + toLowerInvocation = default; + replacementMethod = default; + + // Bail out if the invocation is not a BitConverter.ToString invocation. + if (!IsAnyBitConverterMethod(invocation.TargetMethod)) + { + return false; + } + + bitConverterInvocation = invocation; + + // Bail out if there is no parent invocation. + if (!TryGetParentInvocation(invocation, out invocation!)) + { + return false; + } + + // Check for an optional string.ToLower invocation, e.g. in the case of + // BitConverter.ToString(data).ToLower().Replace("-", "") + if (IsAnyStringToLowerMethod(invocation.TargetMethod)) + { + toLowerInvocation = invocation; + + // Bail out if there is no parent invocation as we still need the string.Replace invocation. + if (!TryGetParentInvocation(invocation, out invocation!)) + { + return false; + } + } + + // Bail out if there is no appropriate string.Replace invocation. + if (!IsAnyStringReplaceMethod(invocation.TargetMethod) || + !HasStringReplaceArgumentsToRemoveHyphen(invocation.Arguments)) + { + return false; + } + + outerInvocation = invocation; + + // Check for an optional string.ToLower invocation, e.g. in the case of + // BitConverter.ToString(data).Replace("-", "").ToLower() + if (TryGetParentInvocation(invocation, out invocation!)) + { + if (IsAnyStringToLowerMethod(invocation.TargetMethod)) + { + toLowerInvocation = invocation; + outerInvocation = invocation; + } + } + + // At this point we have a complete BitConverter.ToString invocation chain. + // Get the appropriate replacement method from BitConverter.ToString to Convert.ToHexStringLower or Convert.ToHexString. + if (toLowerInvocation is not null && + _bitConverterReplacementsToLower.TryGetValue(bitConverterInvocation.TargetMethod, out var replacementToLower)) + { + replacementMethod = replacementToLower; + // Reset toLowerInvocation as we no longer need a string.ToLower after using Convert.ToHexStringLower. + toLowerInvocation = default; + + return true; + } + else if (_bitConverterReplacements.TryGetValue(bitConverterInvocation.TargetMethod, out var replacement)) + { + replacementMethod = replacement; + + return true; + } + + // No replacement was found. + return false; + + static bool TryGetParentInvocation(IInvocationOperation invocation, [NotNullWhen(true)] out IInvocationOperation? parentInvocation) + { + parentInvocation = invocation.Parent as IInvocationOperation; + + return parentInvocation is not null; + } + } + + private bool IsAnyBitConverterMethod(IMethodSymbol method) + { + return _bitConverterReplacements.ContainsKey(method) || _bitConverterReplacementsToLower.ContainsKey(method); + } + + private bool IsAnyStringReplaceMethod(IMethodSymbol? method) + { + return _stringReplaceMethods.Any(m => SymbolEqualityComparer.Default.Equals(m, method)); + } + + private bool IsAnyStringToLowerMethod(IMethodSymbol? method) + { + return _stringToLowerMethods.Any(m => SymbolEqualityComparer.Default.Equals(m, method)); + } + + private bool IsStringEmptyField(IOperation operation) + { + return operation is IFieldReferenceOperation fieldReferenceOperation && + SymbolEqualityComparer.Default.Equals(fieldReferenceOperation.Field, _stringEmptyField); + } + + private bool HasStringReplaceArgumentsToRemoveHyphen(ImmutableArray arguments) + { + var argumentsInParameterOrder = arguments.GetArgumentsInParameterOrder(); + var oldValue = argumentsInParameterOrder[0].Value; + var newValue = argumentsInParameterOrder[1].Value; + + bool oldValueIsConstantHyphenString = oldValue.ConstantValue.HasValue && + oldValue.ConstantValue.Value is string oldValueString && + oldValueString.Equals("-", StringComparison.Ordinal); + + bool newValueIsConstantNullOrEmptyString = newValue.ConstantValue.HasValue && + newValue.ConstantValue.Value is string newValueString && + string.IsNullOrEmpty(newValueString); + + return oldValueIsConstantHyphenString && + (newValueIsConstantNullOrEmptyString || newValue.HasNullConstantValue() || IsStringEmptyField(newValue)); + } + + private readonly ImmutableArray _stringReplaceMethods; + private readonly ImmutableArray _stringToLowerMethods; + private readonly IFieldSymbol? _stringEmptyField; + private readonly ImmutableDictionary _bitConverterReplacements; + private readonly ImmutableDictionary _bitConverterReplacementsToLower; + } + } +} 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 ddaad600e0..753404b3b3 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf @@ -2018,6 +2018,26 @@ Obecné přetypování (IL unbox.any) používané sekvencí vrácenou metodou E Zvažte možnost použít metodu StringBuilder.Append(char) tam, kde je to možné + + Replace with 'Convert.{0}' + Replace with 'Convert.{0}' + + + + Use 'Convert.ToHexString' or 'Convert.ToHexStringLower' when encoding bytes to a hexadecimal string representation. These methods are more efficient and allocation-friendly than using 'BitConverter.ToString' in combination with 'String.Replace' to replace dashes and 'String.ToLower'. + Use 'Convert.ToHexString' or 'Convert.ToHexStringLower' when encoding bytes to a hexadecimal string representation. These methods are more efficient and allocation-friendly than using 'BitConverter.ToString' in combination with 'String.Replace' to replace dashes and 'String.ToLower'. + + + + Prefer '{0}' over call chains based on '{1}' + Prefer '{0}' over call chains based on '{1}' + + + + Prefer 'Convert.ToHexString' and 'Convert.ToHexStringLower' over call chains based on 'BitConverter.ToString' + Prefer 'Convert.ToHexString' and 'Convert.ToHexStringLower' over call chains based on 'BitConverter.ToString' + + Use 'Count' check instead of 'Any()' K ověření použijte metodu Count místo Any() 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 754626a53a..458e33fad5 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf @@ -2018,6 +2018,26 @@ Erweiterungen und benutzerdefinierte Konvertierungen werden bei generischen Type Verwendung von "StringBuilder.Append(char)" erwägen + + Replace with 'Convert.{0}' + Replace with 'Convert.{0}' + + + + Use 'Convert.ToHexString' or 'Convert.ToHexStringLower' when encoding bytes to a hexadecimal string representation. These methods are more efficient and allocation-friendly than using 'BitConverter.ToString' in combination with 'String.Replace' to replace dashes and 'String.ToLower'. + Use 'Convert.ToHexString' or 'Convert.ToHexStringLower' when encoding bytes to a hexadecimal string representation. These methods are more efficient and allocation-friendly than using 'BitConverter.ToString' in combination with 'String.Replace' to replace dashes and 'String.ToLower'. + + + + Prefer '{0}' over call chains based on '{1}' + Prefer '{0}' over call chains based on '{1}' + + + + Prefer 'Convert.ToHexString' and 'Convert.ToHexStringLower' over call chains based on 'BitConverter.ToString' + Prefer 'Convert.ToHexString' and 'Convert.ToHexStringLower' over call chains based on 'BitConverter.ToString' + + Use 'Count' check instead of 'Any()' „Count“ anstelle von „Any()“ verwenden 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 d1bd1814df..f945606df4 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf @@ -2018,6 +2018,26 @@ La ampliación y las conversiones definidas por el usuario no se admiten con tip Puede usar "StringBuilder.Append(char)" cuando sea aplicable + + Replace with 'Convert.{0}' + Replace with 'Convert.{0}' + + + + Use 'Convert.ToHexString' or 'Convert.ToHexStringLower' when encoding bytes to a hexadecimal string representation. These methods are more efficient and allocation-friendly than using 'BitConverter.ToString' in combination with 'String.Replace' to replace dashes and 'String.ToLower'. + Use 'Convert.ToHexString' or 'Convert.ToHexStringLower' when encoding bytes to a hexadecimal string representation. These methods are more efficient and allocation-friendly than using 'BitConverter.ToString' in combination with 'String.Replace' to replace dashes and 'String.ToLower'. + + + + Prefer '{0}' over call chains based on '{1}' + Prefer '{0}' over call chains based on '{1}' + + + + Prefer 'Convert.ToHexString' and 'Convert.ToHexStringLower' over call chains based on 'BitConverter.ToString' + Prefer 'Convert.ToHexString' and 'Convert.ToHexStringLower' over call chains based on 'BitConverter.ToString' + + Use 'Count' check instead of 'Any()' Usar la comprobación "Count" en lugar de "Any()" 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 9453a73efe..3f329aeeae 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf @@ -2018,6 +2018,26 @@ Les conversions étendues et définies par l’utilisateur ne sont pas prises en Utilisez 'StringBuilder.Append(char)' le cas échéant + + Replace with 'Convert.{0}' + Replace with 'Convert.{0}' + + + + Use 'Convert.ToHexString' or 'Convert.ToHexStringLower' when encoding bytes to a hexadecimal string representation. These methods are more efficient and allocation-friendly than using 'BitConverter.ToString' in combination with 'String.Replace' to replace dashes and 'String.ToLower'. + Use 'Convert.ToHexString' or 'Convert.ToHexStringLower' when encoding bytes to a hexadecimal string representation. These methods are more efficient and allocation-friendly than using 'BitConverter.ToString' in combination with 'String.Replace' to replace dashes and 'String.ToLower'. + + + + Prefer '{0}' over call chains based on '{1}' + Prefer '{0}' over call chains based on '{1}' + + + + Prefer 'Convert.ToHexString' and 'Convert.ToHexStringLower' over call chains based on 'BitConverter.ToString' + Prefer 'Convert.ToHexString' and 'Convert.ToHexStringLower' over call chains based on 'BitConverter.ToString' + + Use 'Count' check instead of 'Any()' Utilisez 'Count' case activée au lieu de 'Any()' 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 f47fc5744a..a104a1acf8 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf @@ -2018,6 +2018,26 @@ L'ampliamento e le conversioni definite dall'utente non sono supportate con tipi Se applicabile, provare a usare 'StringBuilder.Append(char)' + + Replace with 'Convert.{0}' + Replace with 'Convert.{0}' + + + + Use 'Convert.ToHexString' or 'Convert.ToHexStringLower' when encoding bytes to a hexadecimal string representation. These methods are more efficient and allocation-friendly than using 'BitConverter.ToString' in combination with 'String.Replace' to replace dashes and 'String.ToLower'. + Use 'Convert.ToHexString' or 'Convert.ToHexStringLower' when encoding bytes to a hexadecimal string representation. These methods are more efficient and allocation-friendly than using 'BitConverter.ToString' in combination with 'String.Replace' to replace dashes and 'String.ToLower'. + + + + Prefer '{0}' over call chains based on '{1}' + Prefer '{0}' over call chains based on '{1}' + + + + Prefer 'Convert.ToHexString' and 'Convert.ToHexStringLower' over call chains based on 'BitConverter.ToString' + Prefer 'Convert.ToHexString' and 'Convert.ToHexStringLower' over call chains based on 'BitConverter.ToString' + + Use 'Count' check instead of 'Any()' Usare il controllo 'Count' anziché 'Any()' 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 a6b58303c1..4500cc4b82 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf @@ -2018,6 +2018,26 @@ Enumerable.OfType<T> で使用されるジェネリック型チェック ( 該当する場合は 'StringBuilder.Append(char)' の使用を検討 + + Replace with 'Convert.{0}' + Replace with 'Convert.{0}' + + + + Use 'Convert.ToHexString' or 'Convert.ToHexStringLower' when encoding bytes to a hexadecimal string representation. These methods are more efficient and allocation-friendly than using 'BitConverter.ToString' in combination with 'String.Replace' to replace dashes and 'String.ToLower'. + Use 'Convert.ToHexString' or 'Convert.ToHexStringLower' when encoding bytes to a hexadecimal string representation. These methods are more efficient and allocation-friendly than using 'BitConverter.ToString' in combination with 'String.Replace' to replace dashes and 'String.ToLower'. + + + + Prefer '{0}' over call chains based on '{1}' + Prefer '{0}' over call chains based on '{1}' + + + + Prefer 'Convert.ToHexString' and 'Convert.ToHexStringLower' over call chains based on 'BitConverter.ToString' + Prefer 'Convert.ToHexString' and 'Convert.ToHexStringLower' over call chains based on 'BitConverter.ToString' + + Use 'Count' check instead of 'Any()' 'Any()' の代わりに 'Count' チェックを使用してください 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 81c593fd49..9c8a9e78bf 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf @@ -2018,6 +2018,26 @@ Enumerable.OfType<T>에서 사용하는 제네릭 형식 검사(C# 'is' 해당하는 경우 'StringBuilder.Append(char)'를 사용하는 것이 좋음 + + Replace with 'Convert.{0}' + Replace with 'Convert.{0}' + + + + Use 'Convert.ToHexString' or 'Convert.ToHexStringLower' when encoding bytes to a hexadecimal string representation. These methods are more efficient and allocation-friendly than using 'BitConverter.ToString' in combination with 'String.Replace' to replace dashes and 'String.ToLower'. + Use 'Convert.ToHexString' or 'Convert.ToHexStringLower' when encoding bytes to a hexadecimal string representation. These methods are more efficient and allocation-friendly than using 'BitConverter.ToString' in combination with 'String.Replace' to replace dashes and 'String.ToLower'. + + + + Prefer '{0}' over call chains based on '{1}' + Prefer '{0}' over call chains based on '{1}' + + + + Prefer 'Convert.ToHexString' and 'Convert.ToHexStringLower' over call chains based on 'BitConverter.ToString' + Prefer 'Convert.ToHexString' and 'Convert.ToHexStringLower' over call chains based on 'BitConverter.ToString' + + Use 'Count' check instead of 'Any()' 'Any()' 대신 'Count' 확인 사용 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 3324d945e0..1a00c35eba 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf @@ -2018,6 +2018,26 @@ Konwersje poszerzane i zdefiniowane przez użytkownika nie są obsługiwane w pr Rozważ użycie elementu „StringBuilder.Append(char)”, jeśli ma to zastosowanie + + Replace with 'Convert.{0}' + Replace with 'Convert.{0}' + + + + Use 'Convert.ToHexString' or 'Convert.ToHexStringLower' when encoding bytes to a hexadecimal string representation. These methods are more efficient and allocation-friendly than using 'BitConverter.ToString' in combination with 'String.Replace' to replace dashes and 'String.ToLower'. + Use 'Convert.ToHexString' or 'Convert.ToHexStringLower' when encoding bytes to a hexadecimal string representation. These methods are more efficient and allocation-friendly than using 'BitConverter.ToString' in combination with 'String.Replace' to replace dashes and 'String.ToLower'. + + + + Prefer '{0}' over call chains based on '{1}' + Prefer '{0}' over call chains based on '{1}' + + + + Prefer 'Convert.ToHexString' and 'Convert.ToHexStringLower' over call chains based on 'BitConverter.ToString' + Prefer 'Convert.ToHexString' and 'Convert.ToHexStringLower' over call chains based on 'BitConverter.ToString' + + Use 'Count' check instead of 'Any()' Użyj sprawdzania „Count” zamiast „Any()” 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 92981429a1..f68472183c 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 @@ -2018,6 +2018,26 @@ Ampliação e conversões definidas pelo usuário não são compatíveis com tip Considere o uso de 'StringBuilder.Append(char)' quando aplicável + + Replace with 'Convert.{0}' + Replace with 'Convert.{0}' + + + + Use 'Convert.ToHexString' or 'Convert.ToHexStringLower' when encoding bytes to a hexadecimal string representation. These methods are more efficient and allocation-friendly than using 'BitConverter.ToString' in combination with 'String.Replace' to replace dashes and 'String.ToLower'. + Use 'Convert.ToHexString' or 'Convert.ToHexStringLower' when encoding bytes to a hexadecimal string representation. These methods are more efficient and allocation-friendly than using 'BitConverter.ToString' in combination with 'String.Replace' to replace dashes and 'String.ToLower'. + + + + Prefer '{0}' over call chains based on '{1}' + Prefer '{0}' over call chains based on '{1}' + + + + Prefer 'Convert.ToHexString' and 'Convert.ToHexStringLower' over call chains based on 'BitConverter.ToString' + Prefer 'Convert.ToHexString' and 'Convert.ToHexStringLower' over call chains based on 'BitConverter.ToString' + + Use 'Count' check instead of 'Any()' Usar a verificação 'Count' em vez de 'Any()' 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 f980ebf090..161b4fb122 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf @@ -2018,6 +2018,26 @@ Widening and user defined conversions are not supported with generic types.Рекомендация использовать метод "StringBuilder.Append(char)" по мере возможности + + Replace with 'Convert.{0}' + Replace with 'Convert.{0}' + + + + Use 'Convert.ToHexString' or 'Convert.ToHexStringLower' when encoding bytes to a hexadecimal string representation. These methods are more efficient and allocation-friendly than using 'BitConverter.ToString' in combination with 'String.Replace' to replace dashes and 'String.ToLower'. + Use 'Convert.ToHexString' or 'Convert.ToHexStringLower' when encoding bytes to a hexadecimal string representation. These methods are more efficient and allocation-friendly than using 'BitConverter.ToString' in combination with 'String.Replace' to replace dashes and 'String.ToLower'. + + + + Prefer '{0}' over call chains based on '{1}' + Prefer '{0}' over call chains based on '{1}' + + + + Prefer 'Convert.ToHexString' and 'Convert.ToHexStringLower' over call chains based on 'BitConverter.ToString' + Prefer 'Convert.ToHexString' and 'Convert.ToHexStringLower' over call chains based on 'BitConverter.ToString' + + Use 'Count' check instead of 'Any()' Используйте проверку 'Count' вместо 'Any()' 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 74e8725e5a..8bf1ba3899 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf @@ -2018,6 +2018,26 @@ Genel türlerde genişletme ve kullanıcı tanımlı dönüştürmeler desteklen Uygun olduğunda 'StringBuilder.Append(char)' kullanabilirsiniz + + Replace with 'Convert.{0}' + Replace with 'Convert.{0}' + + + + Use 'Convert.ToHexString' or 'Convert.ToHexStringLower' when encoding bytes to a hexadecimal string representation. These methods are more efficient and allocation-friendly than using 'BitConverter.ToString' in combination with 'String.Replace' to replace dashes and 'String.ToLower'. + Use 'Convert.ToHexString' or 'Convert.ToHexStringLower' when encoding bytes to a hexadecimal string representation. These methods are more efficient and allocation-friendly than using 'BitConverter.ToString' in combination with 'String.Replace' to replace dashes and 'String.ToLower'. + + + + Prefer '{0}' over call chains based on '{1}' + Prefer '{0}' over call chains based on '{1}' + + + + Prefer 'Convert.ToHexString' and 'Convert.ToHexStringLower' over call chains based on 'BitConverter.ToString' + Prefer 'Convert.ToHexString' and 'Convert.ToHexStringLower' over call chains based on 'BitConverter.ToString' + + Use 'Count' check instead of 'Any()' 'Any()' yerine 'Count' denetimi kullanın 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 e264bdaa2c..0dc3ce0b86 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 @@ -2018,6 +2018,26 @@ Enumerable.OfType<T> 使用的泛型类型检查 (C# 'is' operator/IL 'isi 若适用,请考虑使用 "StringBuilder.Append(char)" + + Replace with 'Convert.{0}' + Replace with 'Convert.{0}' + + + + Use 'Convert.ToHexString' or 'Convert.ToHexStringLower' when encoding bytes to a hexadecimal string representation. These methods are more efficient and allocation-friendly than using 'BitConverter.ToString' in combination with 'String.Replace' to replace dashes and 'String.ToLower'. + Use 'Convert.ToHexString' or 'Convert.ToHexStringLower' when encoding bytes to a hexadecimal string representation. These methods are more efficient and allocation-friendly than using 'BitConverter.ToString' in combination with 'String.Replace' to replace dashes and 'String.ToLower'. + + + + Prefer '{0}' over call chains based on '{1}' + Prefer '{0}' over call chains based on '{1}' + + + + Prefer 'Convert.ToHexString' and 'Convert.ToHexStringLower' over call chains based on 'BitConverter.ToString' + Prefer 'Convert.ToHexString' and 'Convert.ToHexStringLower' over call chains based on 'BitConverter.ToString' + + Use 'Count' check instead of 'Any()' 使用 'Count' 检查而不是 'Any()' 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 f5cf12d34c..1f96176960 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 @@ -2018,6 +2018,26 @@ Enumerable.OfType<T> 使用的一般型別檢查 (C# 'is' operator/IL 'isi 請在適當時考慮使用 'StringBuilder.Append(char)' + + Replace with 'Convert.{0}' + Replace with 'Convert.{0}' + + + + Use 'Convert.ToHexString' or 'Convert.ToHexStringLower' when encoding bytes to a hexadecimal string representation. These methods are more efficient and allocation-friendly than using 'BitConverter.ToString' in combination with 'String.Replace' to replace dashes and 'String.ToLower'. + Use 'Convert.ToHexString' or 'Convert.ToHexStringLower' when encoding bytes to a hexadecimal string representation. These methods are more efficient and allocation-friendly than using 'BitConverter.ToString' in combination with 'String.Replace' to replace dashes and 'String.ToLower'. + + + + Prefer '{0}' over call chains based on '{1}' + Prefer '{0}' over call chains based on '{1}' + + + + Prefer 'Convert.ToHexString' and 'Convert.ToHexStringLower' over call chains based on 'BitConverter.ToString' + Prefer 'Convert.ToHexString' and 'Convert.ToHexStringLower' over call chains based on 'BitConverter.ToString' + + Use 'Count' check instead of 'Any()' 使用 'Count' 檢查而非 'Any()' diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md index f59b872d00..29f229828f 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md @@ -1848,6 +1848,18 @@ Using a cached 'SearchValues' instance is more efficient than passing values to |CodeFix|True| --- +## [CA1872](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1872): Prefer 'Convert.ToHexString' and 'Convert.ToHexStringLower' over call chains based on 'BitConverter.ToString' + +Use 'Convert.ToHexString' or 'Convert.ToHexStringLower' when encoding bytes to a hexadecimal string representation. These methods are more efficient and allocation-friendly than using 'BitConverter.ToString' in combination with 'String.Replace' to replace dashes and 'String.ToLower'. + +|Item|Value| +|-|-| +|Category|Performance| +|Enabled|True| +|Severity|Info| +|CodeFix|True| +--- + ## [CA2000](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000): Dispose objects before losing scope If a disposable object is not explicitly disposed before all references to it are out of scope, the object will be disposed at some indeterminate time when the garbage collector runs the finalizer of the object. Because an exceptional event might occur that will prevent the finalizer of the object from running, the object should be explicitly disposed instead. diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif index 809fd9b8be..6da7d6bf86 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif @@ -3408,6 +3408,26 @@ ] } }, + "CA1872": { + "id": "CA1872", + "shortDescription": "Prefer 'Convert.ToHexString' and 'Convert.ToHexStringLower' over call chains based on 'BitConverter.ToString'", + "fullDescription": "Use 'Convert.ToHexString' or 'Convert.ToHexStringLower' when encoding bytes to a hexadecimal string representation. These methods are more efficient and allocation-friendly than using 'BitConverter.ToString' in combination with 'String.Replace' to replace dashes and 'String.ToLower'.", + "defaultLevel": "note", + "helpUri": "https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1872", + "properties": { + "category": "Performance", + "isEnabledByDefault": true, + "typeName": "PreferConvertToHexStringOverBitConverterAnalyzer", + "languages": [ + "C#", + "Visual Basic" + ], + "tags": [ + "Telemetry", + "EnabledRuleInAggressiveMode" + ] + } + }, "CA2000": { "id": "CA2000", "shortDescription": "Dispose objects before losing scope", diff --git a/src/NetAnalyzers/RulesMissingDocumentation.md b/src/NetAnalyzers/RulesMissingDocumentation.md index f830403300..d20851e050 100644 --- a/src/NetAnalyzers/RulesMissingDocumentation.md +++ b/src/NetAnalyzers/RulesMissingDocumentation.md @@ -3,5 +3,6 @@ Rule ID | Missing Help Link | Title | --------|-------------------|-------| CA1871 | | Do not pass a nullable struct to 'ArgumentNullException.ThrowIfNull' | +CA1872 | | Prefer 'Convert.ToHexString' and 'Convert.ToHexStringLower' over call chains based on 'BitConverter.ToString' | CA2264 | | Do not pass a non-nullable value to 'ArgumentNullException.ThrowIfNull' | CA2265 | | Do not compare Span\ to 'null' or 'default' | diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/PreferConvertToHexStringOverBitConverterTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/PreferConvertToHexStringOverBitConverterTests.cs new file mode 100644 index 0000000000..a8cd80b0d5 --- /dev/null +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Performance/PreferConvertToHexStringOverBitConverterTests.cs @@ -0,0 +1,1481 @@ +// 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.Threading.Tasks; +using Microsoft.CodeAnalysis.Testing; +using Xunit; + +using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< + Microsoft.NetCore.Analyzers.Performance.PreferConvertToHexStringOverBitConverterAnalyzer, + Microsoft.NetCore.Analyzers.Performance.PreferConvertToHexStringOverBitConverterFixer>; +using VerifyVB = Test.Utilities.VisualBasicCodeFixVerifier< + Microsoft.NetCore.Analyzers.Performance.PreferConvertToHexStringOverBitConverterAnalyzer, + Microsoft.NetCore.Analyzers.Performance.PreferConvertToHexStringOverBitConverterFixer>; + +namespace Microsoft.NetCore.Analyzers.Performance.UnitTests +{ + public class PreferConvertToHexStringOverBitConverterTests + { + [Fact] + public async Task DataOnly_OffersFixer_CS() + { + string source = """ + using System; + + class C + { + void M(string s, byte[] data) + { + s = [|BitConverter.ToString(data).Replace("-", "")|]; + s = [|BitConverter.ToString(data).Replace("-", string.Empty)|]; + s = [|BitConverter.ToString(data).Replace("-", null)|]; + } + } + """; + + string fixedSource = """ + using System; + + class C + { + void M(string s, byte[] data) + { + s = Convert.ToHexString(data); + s = Convert.ToHexString(data); + s = Convert.ToHexString(data); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, fixedSource); + } + + [Theory] + [CombinatorialData] + public async Task DataOnlyReplaceWithStringComparison_OffersFixer_CS(StringComparison stringComparison) + { + string source = $$""" + using System; + + class C + { + void M(string s, byte[] data) + { + s = [|BitConverter.ToString(data).Replace("-", "", StringComparison.{{stringComparison}})|]; + s = [|BitConverter.ToString(data).Replace("-", string.Empty, StringComparison.{{stringComparison}})|]; + s = [|BitConverter.ToString(data).Replace("-", null, StringComparison.{{stringComparison}})|]; + } + } + """; + + string fixedSource = """ + using System; + + class C + { + void M(string s, byte[] data) + { + s = Convert.ToHexString(data); + s = Convert.ToHexString(data); + s = Convert.ToHexString(data); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task DataOnlyReplaceWithBoolAndCultureInfo_OffersFixer_CS() + { + string source = """ + using System; + + class C + { + void M(string s, byte[] data) + { + s = [|BitConverter.ToString(data).Replace("-", "", true, null)|]; + s = [|BitConverter.ToString(data).Replace("-", "", false, null)|]; + s = [|BitConverter.ToString(data).Replace("-", string.Empty, true, null)|]; + s = [|BitConverter.ToString(data).Replace("-", string.Empty, false, null)|]; + s = [|BitConverter.ToString(data).Replace("-", null, true, null)|]; + s = [|BitConverter.ToString(data).Replace("-", null, false, null)|]; + } + } + """; + + string fixedSource = """ + using System; + + class C + { + void M(string s, byte[] data) + { + s = Convert.ToHexString(data); + s = Convert.ToHexString(data); + s = Convert.ToHexString(data); + s = Convert.ToHexString(data); + s = Convert.ToHexString(data); + s = Convert.ToHexString(data); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task DataWithStart_OffersFixer_CS() + { + string source = """ + using System; + + class C + { + void M(string s, byte[] data, int start) + { + s = [|BitConverter.ToString(data, start).Replace("-", "")|]; + s = [|BitConverter.ToString(data, start).Replace("-", string.Empty)|]; + s = [|BitConverter.ToString(data, start).Replace("-", null)|]; + } + } + """; + + string fixedSource = """ + using System; + + class C + { + void M(string s, byte[] data, int start) + { + s = Convert.ToHexString(data.AsSpan().Slice(start)); + s = Convert.ToHexString(data.AsSpan().Slice(start)); + s = Convert.ToHexString(data.AsSpan().Slice(start)); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task DataWithStartNoSystemImport_OffersFixer_CS() + { + string source = """ + class C + { + void M(string s, byte[] data, int start) + { + s = [|System.BitConverter.ToString(data, start).Replace("-", "")|]; + } + } + """; + + string fixedSource = """ + using System; + + class C + { + void M(string s, byte[] data, int start) + { + s = Convert.ToHexString(data.AsSpan().Slice(start)); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, fixedSource); + } + + [Theory] + [CombinatorialData] + public async Task DataWithStartReplaceWithStringComparison_OffersFixer_CS(StringComparison stringComparison) + { + string source = $$""" + using System; + + class C + { + void M(string s, byte[] data, int start) + { + s = [|BitConverter.ToString(data, start).Replace("-", "", StringComparison.{{stringComparison}})|]; + s = [|BitConverter.ToString(data, start).Replace("-", string.Empty, StringComparison.{{stringComparison}})|]; + s = [|BitConverter.ToString(data, start).Replace("-", null, StringComparison.{{stringComparison}})|]; + } + } + """; + + string fixedSource = """ + using System; + + class C + { + void M(string s, byte[] data, int start) + { + s = Convert.ToHexString(data.AsSpan().Slice(start)); + s = Convert.ToHexString(data.AsSpan().Slice(start)); + s = Convert.ToHexString(data.AsSpan().Slice(start)); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task DataWithStartReplaceWithBoolAndCultureInfo_OffersFixer_CS() + { + string source = """ + using System; + + class C + { + void M(string s, byte[] data, int start) + { + s = [|BitConverter.ToString(data, start).Replace("-", "", true, null)|]; + s = [|BitConverter.ToString(data, start).Replace("-", "", false, null)|]; + s = [|BitConverter.ToString(data, start).Replace("-", string.Empty, true, null)|]; + s = [|BitConverter.ToString(data, start).Replace("-", string.Empty, false, null)|]; + s = [|BitConverter.ToString(data, start).Replace("-", null, true, null)|]; + s = [|BitConverter.ToString(data, start).Replace("-", null, false, null)|]; + } + } + """; + + string fixedSource = """ + using System; + + class C + { + void M(string s, byte[] data, int start) + { + s = Convert.ToHexString(data.AsSpan().Slice(start)); + s = Convert.ToHexString(data.AsSpan().Slice(start)); + s = Convert.ToHexString(data.AsSpan().Slice(start)); + s = Convert.ToHexString(data.AsSpan().Slice(start)); + s = Convert.ToHexString(data.AsSpan().Slice(start)); + s = Convert.ToHexString(data.AsSpan().Slice(start)); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task DataWithStartAndLength_OffersFixer_CS() + { + string source = """ + using System; + + class C + { + void M(string s, byte[] data, int start, int length) + { + s = [|BitConverter.ToString(data, start, length).Replace("-", "")|]; + s = [|BitConverter.ToString(data, start, length).Replace("-", string.Empty)|]; + s = [|BitConverter.ToString(data, start, length).Replace("-", null)|]; + } + } + """; + + string fixedSource = """ + using System; + + class C + { + void M(string s, byte[] data, int start, int length) + { + s = Convert.ToHexString(data, start, length); + s = Convert.ToHexString(data, start, length); + s = Convert.ToHexString(data, start, length); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, fixedSource); + } + + [Theory] + [CombinatorialData] + public async Task DataWithStartAndLengthReplaceWithStringComparison_OffersFixer_CS(StringComparison stringComparison) + { + string source = $$""" + using System; + + class C + { + void M(string s, byte[] data, int start, int length) + { + s = [|BitConverter.ToString(data, start, length).Replace("-", "", StringComparison.{{stringComparison}})|]; + s = [|BitConverter.ToString(data, start, length).Replace("-", string.Empty, StringComparison.{{stringComparison}})|]; + s = [|BitConverter.ToString(data, start, length).Replace("-", null, StringComparison.{{stringComparison}})|]; + } + } + """; + + string fixedSource = """ + using System; + + class C + { + void M(string s, byte[] data, int start, int length) + { + s = Convert.ToHexString(data, start, length); + s = Convert.ToHexString(data, start, length); + s = Convert.ToHexString(data, start, length); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task DataWithStartAndLengthReplaceWithBoolAndCultureInfo_OffersFixer_CS() + { + string source = """ + using System; + + class C + { + void M(string s, byte[] data, int start, int length) + { + s = [|BitConverter.ToString(data, start, length).Replace("-", "", true, null)|]; + s = [|BitConverter.ToString(data, start, length).Replace("-", "", false, null)|]; + s = [|BitConverter.ToString(data, start, length).Replace("-", string.Empty, true, null)|]; + s = [|BitConverter.ToString(data, start, length).Replace("-", string.Empty, false, null)|]; + s = [|BitConverter.ToString(data, start, length).Replace("-", null, true, null)|]; + s = [|BitConverter.ToString(data, start, length).Replace("-", null, false, null)|]; + } + } + """; + + string fixedSource = """ + using System; + + class C + { + void M(string s, byte[] data, int start, int length) + { + s = Convert.ToHexString(data, start, length); + s = Convert.ToHexString(data, start, length); + s = Convert.ToHexString(data, start, length); + s = Convert.ToHexString(data, start, length); + s = Convert.ToHexString(data, start, length); + s = Convert.ToHexString(data, start, length); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task ToLowerAfterReplaceIsPreservedWhenToHexStringLowerIsNotAvailable_OffersFixer_CS() + { + string source = """ + using System; + + class C + { + void M(string s, byte[] data) + { + s = [|BitConverter.ToString(data).Replace("-", "").ToLower()|]; + s = [|BitConverter.ToString(data).Replace("-", "").ToLower(null)|]; + s = [|BitConverter.ToString(data).Replace("-", "").ToLowerInvariant()|]; + } + } + """; + + string fixedSource = """ + using System; + + class C + { + void M(string s, byte[] data) + { + s = Convert.ToHexString(data).ToLower(); + s = Convert.ToHexString(data).ToLower(null); + s = Convert.ToHexString(data).ToLowerInvariant(); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task ToLowerBeforeReplaceIsPreservedWhenToHexStringLowerIsNotAvailable_OffersFixer_CS() + { + string source = """ + using System; + + class C + { + void M(string s, byte[] data) + { + s = [|BitConverter.ToString(data).ToLower().Replace("-", "")|]; + s = [|BitConverter.ToString(data).ToLower(null).Replace("-", "")|]; + s = [|BitConverter.ToString(data).ToLowerInvariant().Replace("-", "")|]; + } + } + """; + + string fixedSource = """ + using System; + + class C + { + void M(string s, byte[] data) + { + s = Convert.ToHexString(data).ToLower(); + s = Convert.ToHexString(data).ToLower(null); + s = Convert.ToHexString(data).ToLowerInvariant(); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task ToLowerAfterReplaceIsReplacedWithToHexStringLower_OffersFixer_CS() + { + string source = """ + using System; + + class C + { + void M(string s, byte[] data, int start, int length) + { + s = [|BitConverter.ToString(data).Replace("-", "").ToLower()|]; + s = [|BitConverter.ToString(data).Replace("-", "").ToLower(null)|]; + s = [|BitConverter.ToString(data).Replace("-", "").ToLowerInvariant()|]; + s = [|BitConverter.ToString(data, start).Replace("-", "").ToLower()|]; + s = [|BitConverter.ToString(data, start).Replace("-", "").ToLower(null)|]; + s = [|BitConverter.ToString(data, start).Replace("-", "").ToLowerInvariant()|]; + s = [|BitConverter.ToString(data, start, length).Replace("-", "").ToLower()|]; + s = [|BitConverter.ToString(data, start, length).Replace("-", "").ToLower(null)|]; + s = [|BitConverter.ToString(data, start, length).Replace("-", "").ToLowerInvariant()|]; + } + } + """; + + string fixedSource = """ + using System; + + class C + { + void M(string s, byte[] data, int start, int length) + { + s = Convert.ToHexStringLower(data); + s = Convert.ToHexStringLower(data); + s = Convert.ToHexStringLower(data); + s = Convert.ToHexStringLower(data.AsSpan().Slice(start)); + s = Convert.ToHexStringLower(data.AsSpan().Slice(start)); + s = Convert.ToHexStringLower(data.AsSpan().Slice(start)); + s = Convert.ToHexStringLower(data, start, length); + s = Convert.ToHexStringLower(data, start, length); + s = Convert.ToHexStringLower(data, start, length); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, fixedSource, ReferenceAssemblies.Net.Net90); + } + + [Fact] + public async Task ToLowerBeforeReplaceIsReplacedWithToHexStringLower_OffersFixer_CS() + { + string source = """ + using System; + + class C + { + void M(string s, byte[] data, int start, int length) + { + s = [|BitConverter.ToString(data).ToLower().Replace("-", "")|]; + s = [|BitConverter.ToString(data).ToLower(null).Replace("-", "")|]; + s = [|BitConverter.ToString(data).ToLowerInvariant().Replace("-", "")|]; + s = [|BitConverter.ToString(data, start).ToLower().Replace("-", "")|]; + s = [|BitConverter.ToString(data, start).ToLower(null).Replace("-", "")|]; + s = [|BitConverter.ToString(data, start).ToLowerInvariant().Replace("-", "")|]; + s = [|BitConverter.ToString(data, start, length).ToLower().Replace("-", "")|]; + s = [|BitConverter.ToString(data, start, length).ToLower(null).Replace("-", "")|]; + s = [|BitConverter.ToString(data, start, length).ToLowerInvariant().Replace("-", "")|]; + } + } + """; + + string fixedSource = """ + using System; + + class C + { + void M(string s, byte[] data, int start, int length) + { + s = Convert.ToHexStringLower(data); + s = Convert.ToHexStringLower(data); + s = Convert.ToHexStringLower(data); + s = Convert.ToHexStringLower(data.AsSpan().Slice(start)); + s = Convert.ToHexStringLower(data.AsSpan().Slice(start)); + s = Convert.ToHexStringLower(data.AsSpan().Slice(start)); + s = Convert.ToHexStringLower(data, start, length); + s = Convert.ToHexStringLower(data, start, length); + s = Convert.ToHexStringLower(data, start, length); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, fixedSource, ReferenceAssemblies.Net.Net90); + } + + [Fact] + public async Task NamedArguments_OffersFixer_CS() + { + string source = """ + using System; + + class C + { + void M(string s, byte[] data, int start, int length) + { + s = [|BitConverter.ToString(value: data).Replace("-", "")|]; + s = [|BitConverter.ToString(value: data, startIndex: start).Replace("-", "")|]; + s = [|BitConverter.ToString(value: data, startIndex: start, length: length).Replace("-", "")|]; + } + } + """; + + string fixedSource = """ + using System; + + class C + { + void M(string s, byte[] data, int start, int length) + { + s = Convert.ToHexString(data); + s = Convert.ToHexString(data.AsSpan().Slice(start)); + s = Convert.ToHexString(data, start, length); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task NamedArgumentsSwapped_OffersFixer_CS() + { + string source = """ + using System; + + class C + { + void M(string s, byte[] data, int start, int length) + { + s = [|BitConverter.ToString(startIndex: start, value: data).Replace("-", "")|]; + s = [|BitConverter.ToString(value: data, length: length, startIndex: start).Replace("-", "")|]; + s = [|BitConverter.ToString(startIndex: start, value: data, length: length).Replace("-", "")|]; + s = [|BitConverter.ToString(startIndex: start, length: length, value: data).Replace("-", "")|]; + s = [|BitConverter.ToString(length: length, value: data, startIndex: start).Replace("-", "")|]; + s = [|BitConverter.ToString(length: length, startIndex: start, value: data).Replace("-", "")|]; + } + } + """; + + string fixedSource = """ + using System; + + class C + { + void M(string s, byte[] data, int start, int length) + { + s = Convert.ToHexString(data.AsSpan().Slice(start)); + s = Convert.ToHexString(data, start, length); + s = Convert.ToHexString(data, start, length); + s = Convert.ToHexString(data, start, length); + s = Convert.ToHexString(data, start, length); + s = Convert.ToHexString(data, start, length); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task TriviaIsPreserved_OffersFixer_CS() + { + string source = """ + using System; + + class C + { + void M(string s, byte[] data) + { + // reticulates the splines + s = [|BitConverter.ToString(data).Replace("-", "")|]; + } + } + """; + + string fixedSource = """ + using System; + + class C + { + void M(string s, byte[] data) + { + // reticulates the splines + s = Convert.ToHexString(data); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task NotAStringReplaceMethod_NoDiagnostic_CS() + { + string source = """ + using System; + + public static class StringExtensions + { + public static string MyReplace(this string s, string oldValue, string newValue) + { + return s.Replace(oldValue, newValue); + } + } + + class C + { + void M(string s, byte[] data) + { + s = BitConverter.ToString(data).MyReplace("-", ""); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task StringReplaceCharMethod_NoDiagnostic_CS() + { + string source = """ + using System; + + class C + { + void M(string s, byte[] data) + { + s = BitConverter.ToString(data).Replace('-', ' '); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task InstanceIsNoInvocation_NoDiagnostic_CS() + { + string source = """ + using System; + + class C + { + void M(string s, byte[] data) + { + string temp = BitConverter.ToString(data); + s = temp.Replace("-", ""); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task NotASystemBitConverterToStringMethod_NoDiagnostic_CS() + { + string source = """ + public static class BitConverter + { + public static string ToString(byte[] value) + { + return string.Empty; + } + } + + class C + { + void M(string s, byte[] data) + { + s = BitConverter.ToString(data).Replace("-", ""); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task ReplaceOldValueIsNoSingleHyphenStringConstant_NoDiagnostic_CS() + { + string source = """ + using System; + + class C + { + void M(string s, byte[] data) + { + s = BitConverter.ToString(data).Replace("not a single hyphen", ""); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task ReplaceNewValueIsNoEmptyOrNullStringConstant_NoDiagnostic_CS() + { + string source = """ + using System; + + class C + { + void M(string s, byte[] data) + { + s = BitConverter.ToString(data).Replace("-", "not empty or null"); + } + } + """; + + await VerifyCSharpCodeFixAsync(source, source); + } + + [Fact] + public async Task MissingConvertToHexStringSymbol_NoDiagnostic_CS() + { + string source = """ + using System; + + class C + { + void M(string s, byte[] data) + { + s = BitConverter.ToString(data).Replace("-", ""); + } + } + """; + + // .NET Core 3.1 does not have access to Convert.ToHexString + await VerifyCSharpCodeFixAsync(source, source, ReferenceAssemblies.NetCore.NetCoreApp31); + } + + [Fact] + public async Task DataOnly_OffersFixer_VB() + { + string source = """ + Imports System + + Class C + Sub M(s As String, data As Byte()) + s = [|BitConverter.ToString(data).Replace("-", "")|] + s = [|BitConverter.ToString(data).Replace("-", String.Empty)|] + s = [|BitConverter.ToString(data).Replace("-", Nothing)|] + End Sub + End Class + """; + + string fixedSource = """ + Imports System + + Class C + Sub M(s As String, data As Byte()) + s = Convert.ToHexString(data) + s = Convert.ToHexString(data) + s = Convert.ToHexString(data) + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, fixedSource); + } + + [Theory] + [CombinatorialData] + public async Task DataOnlyReplaceWithStringComparison_OffersFixer_VB(StringComparison stringComparison) + { + string source = $""" + Imports System + + Class C + Sub M(s As String, data As Byte()) + s = [|BitConverter.ToString(data).Replace("-", "", StringComparison.{stringComparison})|] + s = [|BitConverter.ToString(data).Replace("-", String.Empty, StringComparison.{stringComparison})|] + s = [|BitConverter.ToString(data).Replace("-", Nothing, StringComparison.{stringComparison})|] + End Sub + End Class + """; + + string fixedSource = """ + Imports System + + Class C + Sub M(s As String, data As Byte()) + s = Convert.ToHexString(data) + s = Convert.ToHexString(data) + s = Convert.ToHexString(data) + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task DataOnlyReplaceWithBoolAndCultureInfo_OffersFixer_VB() + { + string source = """ + Imports System + + Class C + Sub M(s As String, data As Byte()) + s = [|BitConverter.ToString(data).Replace("-", "", true, Nothing)|] + s = [|BitConverter.ToString(data).Replace("-", "", false, Nothing)|] + s = [|BitConverter.ToString(data).Replace("-", String.Empty, true, Nothing)|] + s = [|BitConverter.ToString(data).Replace("-", String.Empty, false, Nothing)|] + s = [|BitConverter.ToString(data).Replace("-", Nothing, true, Nothing)|] + s = [|BitConverter.ToString(data).Replace("-", Nothing, false, Nothing)|] + End Sub + End Class + """; + + string fixedSource = """ + Imports System + + Class C + Sub M(s As String, data As Byte()) + s = Convert.ToHexString(data) + s = Convert.ToHexString(data) + s = Convert.ToHexString(data) + s = Convert.ToHexString(data) + s = Convert.ToHexString(data) + s = Convert.ToHexString(data) + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task DataWithStart_OffersFixer_VB() + { + string source = """ + Imports System + + Class C + Sub M(s As String, data As Byte(), start as Integer) + s = [|BitConverter.ToString(data, start).Replace("-", "")|] + s = [|BitConverter.ToString(data, start).Replace("-", String.Empty)|] + s = [|BitConverter.ToString(data, start).Replace("-", Nothing)|] + End Sub + End Class + """; + + string fixedSource = """ + Imports System + + Class C + Sub M(s As String, data As Byte(), start as Integer) + s = Convert.ToHexString(data.AsSpan().Slice(start)) + s = Convert.ToHexString(data.AsSpan().Slice(start)) + s = Convert.ToHexString(data.AsSpan().Slice(start)) + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task DataWithStartNoSystemImport_OffersFixer_VB() + { + string source = """ + Class C + Sub M(s As String, data As Byte(), start As Integer) + s = [|System.BitConverter.ToString(data, start).Replace("-", "")|] + End Sub + End Class + """; + + string fixedSource = """ + Imports System + + Class C + Sub M(s As String, data As Byte(), start As Integer) + s = Convert.ToHexString(data.AsSpan().Slice(start)) + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, fixedSource); + } + + [Theory] + [CombinatorialData] + public async Task DataWithStartReplaceWithStringComparison_OffersFixer_VB(StringComparison stringComparison) + { + string source = $""" + Imports System + + Class C + Sub M(s As String, data As Byte(), start as Integer) + s = [|BitConverter.ToString(data, start).Replace("-", "", StringComparison.{stringComparison})|] + s = [|BitConverter.ToString(data, start).Replace("-", String.Empty, StringComparison.{stringComparison})|] + s = [|BitConverter.ToString(data, start).Replace("-", Nothing, StringComparison.{stringComparison})|] + End Sub + End Class + """; + + string fixedSource = """ + Imports System + + Class C + Sub M(s As String, data As Byte(), start as Integer) + s = Convert.ToHexString(data.AsSpan().Slice(start)) + s = Convert.ToHexString(data.AsSpan().Slice(start)) + s = Convert.ToHexString(data.AsSpan().Slice(start)) + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task DataWithStartReplaceWithBoolAndCultureInfo_OffersFixer_VB() + { + string source = """ + Imports System + + Class C + Sub M(s As String, data As Byte(), start as Integer) + s = [|BitConverter.ToString(data, start).Replace("-", "", true, Nothing)|] + s = [|BitConverter.ToString(data, start).Replace("-", "", false, Nothing)|] + s = [|BitConverter.ToString(data, start).Replace("-", String.Empty, true, Nothing)|] + s = [|BitConverter.ToString(data, start).Replace("-", String.Empty, false, Nothing)|] + s = [|BitConverter.ToString(data, start).Replace("-", Nothing, true, Nothing)|] + s = [|BitConverter.ToString(data, start).Replace("-", Nothing, false, Nothing)|] + End Sub + End Class + """; + + string fixedSource = """ + Imports System + + Class C + Sub M(s As String, data As Byte(), start as Integer) + s = Convert.ToHexString(data.AsSpan().Slice(start)) + s = Convert.ToHexString(data.AsSpan().Slice(start)) + s = Convert.ToHexString(data.AsSpan().Slice(start)) + s = Convert.ToHexString(data.AsSpan().Slice(start)) + s = Convert.ToHexString(data.AsSpan().Slice(start)) + s = Convert.ToHexString(data.AsSpan().Slice(start)) + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task DataWithStartAndLength_OffersFixer_VB() + { + string source = """ + Imports System + + Class C + Sub M(s As String, data As Byte(), start as Integer, length as Integer) + s = [|BitConverter.ToString(data, start, length).Replace("-", "")|] + s = [|BitConverter.ToString(data, start, length).Replace("-", String.Empty)|] + s = [|BitConverter.ToString(data, start, length).Replace("-", Nothing)|] + End Sub + End Class + """; + + string fixedSource = """ + Imports System + + Class C + Sub M(s As String, data As Byte(), start as Integer, length as Integer) + s = Convert.ToHexString(data, start, length) + s = Convert.ToHexString(data, start, length) + s = Convert.ToHexString(data, start, length) + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, fixedSource); + } + + [Theory] + [CombinatorialData] + public async Task DataWithStartAndLengthReplaceWithStringComparison_OffersFixer_VB(StringComparison stringComparison) + { + string source = $""" + Imports System + + Class C + Sub M(s As String, data As Byte(), start as Integer, length as Integer) + s = [|BitConverter.ToString(data, start, length).Replace("-", "", StringComparison.{stringComparison})|] + s = [|BitConverter.ToString(data, start, length).Replace("-", String.Empty, StringComparison.{stringComparison})|] + s = [|BitConverter.ToString(data, start, length).Replace("-", Nothing, StringComparison.{stringComparison})|] + End Sub + End Class + """; + + string fixedSource = """ + Imports System + + Class C + Sub M(s As String, data As Byte(), start as Integer, length as Integer) + s = Convert.ToHexString(data, start, length) + s = Convert.ToHexString(data, start, length) + s = Convert.ToHexString(data, start, length) + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task DataWithStartAndLengthReplaceWithBoolAndCultureInfo_OffersFixer_VB() + { + string source = """ + Imports System + + Class C + Sub M(s As String, data As Byte(), start as Integer, length as Integer) + s = [|BitConverter.ToString(data, start, length).Replace("-", "", true, Nothing)|] + s = [|BitConverter.ToString(data, start, length).Replace("-", "", false, Nothing)|] + s = [|BitConverter.ToString(data, start, length).Replace("-", String.Empty, true, Nothing)|] + s = [|BitConverter.ToString(data, start, length).Replace("-", String.Empty, false, Nothing)|] + s = [|BitConverter.ToString(data, start, length).Replace("-", Nothing, true, Nothing)|] + s = [|BitConverter.ToString(data, start, length).Replace("-", Nothing, false, Nothing)|] + End Sub + End Class + """; + + string fixedSource = """ + Imports System + + Class C + Sub M(s As String, data As Byte(), start as Integer, length as Integer) + s = Convert.ToHexString(data, start, length) + s = Convert.ToHexString(data, start, length) + s = Convert.ToHexString(data, start, length) + s = Convert.ToHexString(data, start, length) + s = Convert.ToHexString(data, start, length) + s = Convert.ToHexString(data, start, length) + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task ToLowerAfterReplaceIsPreservedWhenToHexStringLowerIsNotAvailable_OffersFixer_VB() + { + string source = """ + Imports System + + Class C + Sub M(s As String, data As Byte()) + s = [|BitConverter.ToString(data).Replace("-", "").ToLower()|] + s = [|BitConverter.ToString(data).Replace("-", "").ToLower(Nothing)|] + s = [|BitConverter.ToString(data).Replace("-", "").ToLowerInvariant()|] + End Sub + End Class + """; + + string fixedSource = """ + Imports System + + Class C + Sub M(s As String, data As Byte()) + s = Convert.ToHexString(data).ToLower() + s = Convert.ToHexString(data).ToLower(Nothing) + s = Convert.ToHexString(data).ToLowerInvariant() + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task ToLowerBeforeReplaceIsPreservedWhenToHexStringLowerIsNotAvailable_OffersFixer_VB() + { + string source = """ + Imports System + + Class C + Sub M(s As String, data As Byte()) + s = [|BitConverter.ToString(data).ToLower().Replace("-", "")|] + s = [|BitConverter.ToString(data).ToLower(Nothing).Replace("-", "")|] + s = [|BitConverter.ToString(data).ToLowerInvariant().Replace("-", "")|] + End Sub + End Class + """; + + string fixedSource = """ + Imports System + + Class C + Sub M(s As String, data As Byte()) + s = Convert.ToHexString(data).ToLower() + s = Convert.ToHexString(data).ToLower(Nothing) + s = Convert.ToHexString(data).ToLowerInvariant() + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task ToLowerAfterReplaceIsReplacedWithToHexStringLower_OffersFixer_VB() + { + string source = """ + Imports System + + Class C + Sub M(s As String, data As Byte(), start as Integer, length as Integer) + s = [|BitConverter.ToString(data).Replace("-", "").ToLower()|] + s = [|BitConverter.ToString(data).Replace("-", "").ToLower(Nothing)|] + s = [|BitConverter.ToString(data).Replace("-", "").ToLowerInvariant()|] + s = [|BitConverter.ToString(data, start).Replace("-", "").ToLower()|] + s = [|BitConverter.ToString(data, start).Replace("-", "").ToLower(Nothing)|] + s = [|BitConverter.ToString(data, start).Replace("-", "").ToLowerInvariant()|] + s = [|BitConverter.ToString(data, start, length).Replace("-", "").ToLower()|] + s = [|BitConverter.ToString(data, start, length).Replace("-", "").ToLower(Nothing)|] + s = [|BitConverter.ToString(data, start, length).Replace("-", "").ToLowerInvariant()|] + End Sub + End Class + """; + + string fixedSource = """ + Imports System + + Class C + Sub M(s As String, data As Byte(), start as Integer, length as Integer) + s = Convert.ToHexStringLower(data) + s = Convert.ToHexStringLower(data) + s = Convert.ToHexStringLower(data) + s = Convert.ToHexStringLower(data.AsSpan().Slice(start)) + s = Convert.ToHexStringLower(data.AsSpan().Slice(start)) + s = Convert.ToHexStringLower(data.AsSpan().Slice(start)) + s = Convert.ToHexStringLower(data, start, length) + s = Convert.ToHexStringLower(data, start, length) + s = Convert.ToHexStringLower(data, start, length) + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, fixedSource, ReferenceAssemblies.Net.Net90); + } + + [Fact] + public async Task ToLowerBeforeReplaceIsReplacedWithToHexStringLower_OffersFixer_VB() + { + string source = """ + Imports System + + Class C + Sub M(s As String, data As Byte(), start as Integer, length as Integer) + s = [|BitConverter.ToString(data).ToLower().Replace("-", "")|] + s = [|BitConverter.ToString(data).ToLower(Nothing).Replace("-", "")|] + s = [|BitConverter.ToString(data).ToLowerInvariant().Replace("-", "")|] + s = [|BitConverter.ToString(data, start).ToLower().Replace("-", "")|] + s = [|BitConverter.ToString(data, start).ToLower(Nothing).Replace("-", "")|] + s = [|BitConverter.ToString(data, start).ToLowerInvariant().Replace("-", "")|] + s = [|BitConverter.ToString(data, start, length).ToLower().Replace("-", "")|] + s = [|BitConverter.ToString(data, start, length).ToLower(Nothing).Replace("-", "")|] + s = [|BitConverter.ToString(data, start, length).ToLowerInvariant().Replace("-", "")|] + End Sub + End Class + """; + + string fixedSource = """ + Imports System + + Class C + Sub M(s As String, data As Byte(), start as Integer, length as Integer) + s = Convert.ToHexStringLower(data) + s = Convert.ToHexStringLower(data) + s = Convert.ToHexStringLower(data) + s = Convert.ToHexStringLower(data.AsSpan().Slice(start)) + s = Convert.ToHexStringLower(data.AsSpan().Slice(start)) + s = Convert.ToHexStringLower(data.AsSpan().Slice(start)) + s = Convert.ToHexStringLower(data, start, length) + s = Convert.ToHexStringLower(data, start, length) + s = Convert.ToHexStringLower(data, start, length) + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, fixedSource, ReferenceAssemblies.Net.Net90); + } + + [Fact] + public async Task NamedArguments_OffersFixer_VB() + { + string source = """ + Imports System + + Class C + Sub M(s As String, data As Byte(), start as Integer, length as Integer) + s = [|BitConverter.ToString(value:=data).Replace("-", "")|] + s = [|BitConverter.ToString(value:=data, startIndex:=start).Replace("-", "")|] + s = [|BitConverter.ToString(value:=data, startIndex:=start, length:=length).Replace("-", "")|] + End Sub + End Class + """; + + string fixedSource = """ + Imports System + + Class C + Sub M(s As String, data As Byte(), start as Integer, length as Integer) + s = Convert.ToHexString(data) + s = Convert.ToHexString(data.AsSpan().Slice(start)) + s = Convert.ToHexString(data, start, length) + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task NamedArgumentsSwapped_OffersFixer_VB() + { + string source = """ + Imports System + + Class C + Sub M(s As String, data As Byte(), start as Integer, length as Integer) + s = [|BitConverter.ToString(startIndex:=start, value:=data).Replace("-", "")|] + s = [|BitConverter.ToString(value:=data, length:=length, startIndex:=start).Replace("-", "")|] + s = [|BitConverter.ToString(startIndex:=start, value:=data, length:=length).Replace("-", "")|] + s = [|BitConverter.ToString(startIndex:=start, length:=length, value:=data).Replace("-", "")|] + s = [|BitConverter.ToString(length:=length, value:=data, startIndex:=start).Replace("-", "")|] + s = [|BitConverter.ToString(length:=length, startIndex:=start, value:=data).Replace("-", "")|] + End Sub + End Class + """; + + string fixedSource = """ + Imports System + + Class C + Sub M(s As String, data As Byte(), start as Integer, length as Integer) + s = Convert.ToHexString(data.AsSpan().Slice(start)) + s = Convert.ToHexString(data, start, length) + s = Convert.ToHexString(data, start, length) + s = Convert.ToHexString(data, start, length) + s = Convert.ToHexString(data, start, length) + s = Convert.ToHexString(data, start, length) + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task TriviaIsPreserved_OffersFixer_VB() + { + string source = """ + Imports System + + Class C + Sub M(s As String, data As Byte()) + ' reticulates the splines + s = [|BitConverter.ToString(data).Replace("-", "")|] + End Sub + End Class + """; + + string fixedSource = """ + Imports System + + Class C + Sub M(s As String, data As Byte()) + ' reticulates the splines + s = Convert.ToHexString(data) + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task NotAStringReplaceMethod_NoDiagnostic_VB() + { + string source = """ + Imports System + Imports System.Runtime.CompilerServices + + Module StringExtensions + + Public Function MyReplace(s As String, oldValue As String, newValue As String) As String + return s.Replace(oldValue, newValue) + End Function + End Module + + Class C + Sub M(s As String, data As Byte()) + s = BitConverter.ToString(data).MyReplace("-", "") + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Fact] + public async Task StringReplaceCharMethod_NoDiagnostic_VB() + { + string source = """ + Imports System + + Class C + Sub M(s As String, data As Byte()) + s = BitConverter.ToString(data).Replace("-"c, " "c) + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Fact] + public async Task InstanceIsNoInvocation_NoDiagnostic_VB() + { + string source = """ + Imports System + + Class C + Sub M(s As String, data As Byte()) + Dim temp = BitConverter.ToString(data) + s = temp.Replace("-", "") + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Fact] + public async Task NotASystemBitConverterToStringMethod_NoDiagnostic_VB() + { + string source = """ + Class BitConverter + Shared Function ToString(value As byte()) As String + return string.Empty + End Function + End Class + + Class C + Sub M(s As String, data As Byte()) + s = BitConverter.ToString(data).Replace("-", "") + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Fact] + public async Task ReplaceOldValueIsNoSingleHyphenStringConstant_NoDiagnostic_VB() + { + string source = """ + Imports System + + Class C + Sub M(s As String, data As Byte()) + s = BitConverter.ToString(data).Replace("not a single hyphen", "") + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Fact] + public async Task ReplaceNewValueIsNoEmptyOrNullStringConstant_NoDiagnostic_VB() + { + string source = """ + Imports System + + Class C + Sub M(s As String, data As Byte()) + s = BitConverter.ToString(data).Replace("-", "not empty or null") + End Sub + End Class + """; + + await VerifyBasicCodeFixAsync(source, source); + } + + [Fact] + public async Task MissingConvertToHexStringSymbol_NoDiagnostic_VB() + { + string source = """ + Imports System + + Class C + Sub M(s As String, data As Byte()) + s = BitConverter.ToString(data).Replace("-", "") + End Sub + End Class + """; + + // .NET Core 3.1 does not have access to Convert.ToHexString + await VerifyBasicCodeFixAsync(source, source, ReferenceAssemblies.NetCore.NetCoreApp31); + } + + private static async Task VerifyCSharpCodeFixAsync(string source, string fixedSource, ReferenceAssemblies referenceAssemblies = null) + { + await new VerifyCS.Test + { + TestCode = source, + FixedCode = fixedSource, + ReferenceAssemblies = referenceAssemblies ?? ReferenceAssemblies.Net.Net50 + }.RunAsync(); + } + + private static async Task VerifyBasicCodeFixAsync(string source, string fixedSource, ReferenceAssemblies referenceAssemblies = null) + { + await new VerifyVB.Test + { + TestCode = source, + FixedCode = fixedSource, + ReferenceAssemblies = referenceAssemblies ?? ReferenceAssemblies.Net.Net50 + }.RunAsync(); + } + } +} diff --git a/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt b/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt index bda26485a2..57eface7b5 100644 --- a/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt +++ b/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt @@ -12,7 +12,7 @@ Design: CA2210, CA1000-CA1070 Globalization: CA2101, CA1300-CA1311 Mobility: CA1600-CA1601 -Performance: HA, CA1800-CA1871 +Performance: HA, CA1800-CA1872 Security: CA2100-CA2153, CA2300-CA2330, CA3000-CA3147, CA5300-CA5405 Usage: CA1801, CA1806, CA1816, CA2200-CA2209, CA2211-CA2265 Naming: CA1700-CA1727 diff --git a/src/Utilities/Compiler/WellKnownTypeNames.cs b/src/Utilities/Compiler/WellKnownTypeNames.cs index 9433ed1078..9fa8662c79 100644 --- a/src/Utilities/Compiler/WellKnownTypeNames.cs +++ b/src/Utilities/Compiler/WellKnownTypeNames.cs @@ -120,6 +120,7 @@ internal static class WellKnownTypeNames public const string SystemAttribute = "System.Attribute"; public const string SystemAttributeTargets = "System.AttributeTargets"; public const string SystemAttributeUsageAttribute = "System.AttributeUsageAttribute"; + public const string SystemBitConverter = "System.BitConverter"; public const string SystemBoolean = "System.Boolean"; public const string SystemBuffer = "System.Buffer"; public const string SystemBuffersMemoryManager1 = "System.Buffers.MemoryManager`1";