From 39ccb5b7570c179a82aa604ab7c3712af94ef119 Mon Sep 17 00:00:00 2001 From: Mario Pistrich Date: Fri, 22 Sep 2023 23:00:35 +0200 Subject: [PATCH] Add CA1514: Remove redundant length argument (#6814) * Add CA1514: Remove redundant length argument This analyzer detects uses of Substring/Slice(start, length) that slice to the end of the buffer. In these cases, the length check is redundant, can be omitted, and the call can be replaced with Substring/Slice(start). The analyzer also detects if the evaluation of the instance or arguments can lead to side effects and does not flag these cases. * Rename LengthCheck to LengthCalculation --- .../Core/AnalyzerReleases.Unshipped.md | 1 + ...LengthCalculationWhenSlicingToEnd.Fixer.cs | 80 + .../AvoidLengthCalculationWhenSlicingToEnd.cs | 290 +++ ...icrosoftCodeQualityAnalyzersResources.resx | 12 + ...rosoftCodeQualityAnalyzersResources.cs.xlf | 20 + ...rosoftCodeQualityAnalyzersResources.de.xlf | 20 + ...rosoftCodeQualityAnalyzersResources.es.xlf | 20 + ...rosoftCodeQualityAnalyzersResources.fr.xlf | 20 + ...rosoftCodeQualityAnalyzersResources.it.xlf | 20 + ...rosoftCodeQualityAnalyzersResources.ja.xlf | 20 + ...rosoftCodeQualityAnalyzersResources.ko.xlf | 20 + ...rosoftCodeQualityAnalyzersResources.pl.xlf | 20 + ...oftCodeQualityAnalyzersResources.pt-BR.xlf | 20 + ...rosoftCodeQualityAnalyzersResources.ru.xlf | 20 + ...rosoftCodeQualityAnalyzersResources.tr.xlf | 20 + ...tCodeQualityAnalyzersResources.zh-Hans.xlf | 20 + ...tCodeQualityAnalyzersResources.zh-Hant.xlf | 20 + .../Microsoft.CodeAnalysis.NetAnalyzers.md | 12 + .../Microsoft.CodeAnalysis.NetAnalyzers.sarif | 20 + src/NetAnalyzers/RulesMissingDocumentation.md | 2 +- ...dLengthCalculationWhenSlicingToEndTests.cs | 1648 +++++++++++++++++ .../DiagnosticCategoryAndIdRanges.txt | 2 +- 22 files changed, 2325 insertions(+), 2 deletions(-) create mode 100644 src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/Maintainability/AvoidLengthCalculationWhenSlicingToEnd.Fixer.cs create mode 100644 src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/Maintainability/AvoidLengthCalculationWhenSlicingToEnd.cs create mode 100644 src/NetAnalyzers/UnitTests/Microsoft.CodeQuality.Analyzers/Maintainability/AvoidLengthCalculationWhenSlicingToEndTests.cs diff --git a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md index af7096d1f6..c0945d156a 100644 --- a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md +++ b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md @@ -4,3 +4,4 @@ Rule ID | Category | Severity | Notes --------|----------|----------|------- +CA1514 | Maintainability | Info | AvoidLengthCheckWhenSlicingToEndAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1514) diff --git a/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/Maintainability/AvoidLengthCalculationWhenSlicingToEnd.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/Maintainability/AvoidLengthCalculationWhenSlicingToEnd.Fixer.cs new file mode 100644 index 0000000000..ec6c447842 --- /dev/null +++ b/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/Maintainability/AvoidLengthCalculationWhenSlicingToEnd.Fixer.cs @@ -0,0 +1,80 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Immutable; +using System.Composition; +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; + +namespace Microsoft.CodeQuality.Analyzers.Maintainability +{ + /// + /// CA1514: + /// + [ExportCodeFixProvider(LanguageNames.CSharp, LanguageNames.VisualBasic), Shared] + public sealed class AvoidLengthCalculationWhenSlicingToEndFixer : CodeFixProvider + { + public sealed override ImmutableArray FixableDiagnosticIds { get; } = ImmutableArray.Create(AvoidLengthCalculationWhenSlicingToEndAnalyzer.RuleId); + + public sealed override FixAllProvider GetFixAllProvider() + { + return WellKnownFixAllProviders.BatchFixer; + } + + public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + var root = await context.Document.GetRequiredSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + var node = root.FindNode(context.Span, getInnermostNodeForTie: true); + + if (node is null) + { + return; + } + + var semanticModel = await context.Document.GetRequiredSemanticModelAsync(context.CancellationToken).ConfigureAwait(false); + var operation = semanticModel.GetOperation(node, context.CancellationToken); + + if (operation is not IInvocationOperation invocationOperation || + invocationOperation.Instance is null || + invocationOperation.Arguments.Length != 2) + { + return; + } + + var codeAction = CodeAction.Create( + MicrosoftCodeQualityAnalyzersResources.AvoidLengthCalculationWhenSlicingToEndCodeFixTitle, + ct => ReplaceWithStartOnlyCall( + context.Document, + invocationOperation.Instance.Syntax, + invocationOperation.TargetMethod.Name, + invocationOperation.Arguments.GetArgumentsInParameterOrder()[0], + ct), + nameof(MicrosoftCodeQualityAnalyzersResources.AvoidLengthCalculationWhenSlicingToEndCodeFixTitle)); + + context.RegisterCodeFix(codeAction, context.Diagnostics); + + async Task ReplaceWithStartOnlyCall( + Document document, + SyntaxNode instance, + string methodName, + IArgumentOperation argument, + CancellationToken cancellationToken) + { + var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); + var generator = editor.Generator; + var methodExpression = generator.MemberAccessExpression(instance, methodName); + var methodInvocation = generator.InvocationExpression(methodExpression, argument.Syntax); + + editor.ReplaceNode(invocationOperation.Syntax, methodInvocation.WithTriviaFrom(invocationOperation.Syntax)); + + return document.WithSyntaxRoot(editor.GetChangedRoot()); + } + } + } +} diff --git a/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/Maintainability/AvoidLengthCalculationWhenSlicingToEnd.cs b/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/Maintainability/AvoidLengthCalculationWhenSlicingToEnd.cs new file mode 100644 index 0000000000..90545b98d2 --- /dev/null +++ b/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/Maintainability/AvoidLengthCalculationWhenSlicingToEnd.cs @@ -0,0 +1,290 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Collections.Generic; +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.CodeQuality.Analyzers.Maintainability +{ + using static MicrosoftCodeQualityAnalyzersResources; + + /// + /// CA1514: + /// + [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] + public sealed class AvoidLengthCalculationWhenSlicingToEndAnalyzer : DiagnosticAnalyzer + { + internal const string RuleId = "CA1514"; + + private const string Substring = nameof(Substring); + + internal static readonly DiagnosticDescriptor Rule = DiagnosticDescriptorHelper.Create( + RuleId, + CreateLocalizableResourceString(nameof(AvoidLengthCalculationWhenSlicingToEndTitle)), + CreateLocalizableResourceString(nameof(AvoidLengthCalculationWhenSlicingToEndMessage)), + DiagnosticCategory.Maintainability, + RuleLevel.IdeSuggestion, + CreateLocalizableResourceString(nameof(AvoidLengthCalculationWhenSlicingToEndDescription)), + 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.IsAnyStartLengthMethod(invocation.TargetMethod)) + { + return; + } + + var instance = invocation.Instance; + var argumentsInParameterOrder = invocation.Arguments.GetArgumentsInParameterOrder(); + var startArgument = argumentsInParameterOrder[0]; + var lengthArgument = argumentsInParameterOrder[1]; + + if (OperationHasSideEffects(instance) || + ArgumentHasSideEffects(startArgument) || + !symbols.HasLengthPropertyOnInstance(lengthArgument, instance, out var lengthProperty) || + !StartIsSubtractedFromLength(startArgument, lengthArgument, lengthProperty)) + { + return; + } + + context.ReportDiagnostic(invocation.CreateDiagnostic(Rule, invocation.TargetMethod.ToDisplayString())); + } + + // Fields, locals, parameters and instance references have no side effects. + // Properties have no side effects if they are auto-properties. + // For unary, binary and parenthesized operations it depends on the operand and whether the operator is supported. + bool OperationHasSideEffects(IOperation? operation) + { + return operation switch + { + IFieldReferenceOperation => false, + ILocalReferenceOperation => false, + IParameterReferenceOperation => false, + ILiteralOperation => false, + IInstanceReferenceOperation => false, + IPropertyReferenceOperation propertyReferenceOperation => !propertyReferenceOperation.Property.IsAutoProperty(), + IUnaryOperation { OperatorKind: UnaryOperatorKind.Plus or UnaryOperatorKind.Minus } unaryOperation + => OperationHasSideEffects(unaryOperation.Operand), + IBinaryOperation { OperatorKind: BinaryOperatorKind.Add or BinaryOperatorKind.Subtract } binaryOperation + => OperationHasSideEffects(binaryOperation.LeftOperand) || OperationHasSideEffects(binaryOperation.RightOperand), + IParenthesizedOperation parenthesizedOperation => OperationHasSideEffects(parenthesizedOperation.Operand), + _ => true + }; + } + + bool ArgumentHasSideEffects(IArgumentOperation argument) + { + return argument + .Descendants() + .OfType() + .Any(OperationHasSideEffects); + } + + bool StartIsSubtractedFromLength(IArgumentOperation startArgument, IArgumentOperation lengthArgument, IPropertyReferenceOperation lengthProperty) + { + // Keep track of constants: Add constants from start argument and subtract constants from the length argument. + // The first condition that the start argument is subtracted from the length argument is that the constant sum is zero. + int constantSum = 0; + + // Build two sets containing all symbols in the expressions that are part of the start and length argument. + // The second condition that the start argument is subtracted from the length argument is that the symbol sets are equal. + // For this reason, the symbols of the length argument start negated. + + var startArgumentSymbols = new HashSet(); + int constantsSign = 1; + AddExpressionPartToSet(startArgument.Value, isNegated: false, startArgumentSymbols); + + var lengthArgumentSymbols = new HashSet(); + constantsSign = -1; + AddExpressionPartToSet(lengthArgument.Value, isNegated: true, lengthArgumentSymbols); + + // Remove length property which is not present in start argument. + lengthArgumentSymbols.Remove(new SymbolWithNegation(lengthProperty.Member, true)); + + return startArgumentSymbols.SetEquals(lengthArgumentSymbols) && constantSum == 0; + + void AddExpressionPartToSet(IOperation operation, bool isNegated, HashSet set) + { + if (operation is IBinaryOperation binaryOperation) + { + AddExpressionPartToSet(binaryOperation.LeftOperand, isNegated, set); + AddExpressionPartToSet(binaryOperation.RightOperand, binaryOperation.OperatorKind == BinaryOperatorKind.Subtract ? !isNegated : isNegated, set); + } + else if (operation is IUnaryOperation unaryOperation) + { + AddExpressionPartToSet(unaryOperation.Operand, unaryOperation.OperatorKind == UnaryOperatorKind.Minus ? !isNegated : isNegated, set); + } + else if (operation is IParenthesizedOperation parenthesizedOperation) + { + AddExpressionPartToSet(parenthesizedOperation.Operand, isNegated, set); + } + else if (operation is ILiteralOperation { ConstantValue.Value: int } literalOperation) + { + constantSum += (int)literalOperation.ConstantValue.Value! * (isNegated ? -1 : 1) * constantsSign; + } + else + { + set.Add(new SymbolWithNegation(operation.GetReferencedMemberOrLocalOrParameter(), isNegated)); + } + } + } + } + + private readonly record struct SymbolWithNegation(ISymbol? Symbol, bool IsNegated); + + private sealed class RequiredSymbols + { + private RequiredSymbols( + IMethodSymbol? substringStartLength, + IMethodSymbol? spanSliceStartLength, + IMethodSymbol? readOnlySpanSliceStartLength, + IMethodSymbol? memorySliceStartLength, + IPropertySymbol? stringLength, + IPropertySymbol? spanLength, + IPropertySymbol? readOnlySpanLength, + IPropertySymbol? memoryLength) + { + SubstringStartLength = substringStartLength; + SpanSliceStartLength = spanSliceStartLength; + ReadOnlySpanSliceStartLength = readOnlySpanSliceStartLength; + MemorySliceStartLength = memorySliceStartLength; + StringLength = stringLength; + SpanLength = spanLength; + ReadOnlySpanLength = readOnlySpanLength; + MemoryLength = memoryLength; + } + + public static bool TryGetSymbols(Compilation compilation, [NotNullWhen(true)] out RequiredSymbols? symbols) + { + symbols = default; + + var int32Type = compilation.GetSpecialType(SpecialType.System_Int32); + var stringType = compilation.GetSpecialType(SpecialType.System_String); + var spanType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemSpan1); + var readOnlySpanType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemReadOnlySpan1); + var memoryType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemMemory1); + + // Bail out if we have no integer type or all target types are null + if (int32Type is null || + (stringType is null && spanType is null && readOnlySpanType is null && memoryType is null)) + { + return false; + } + + var int32ParamInfo = ParameterInfo.GetParameterInfo(int32Type); + + var substringMembers = stringType?.GetMembers(Substring).OfType(); + var substringStartLength = substringMembers.GetFirstOrDefaultMemberWithParameterInfos(int32ParamInfo, int32ParamInfo); + var stringLength = stringType?.GetMembers(WellKnownMemberNames.LengthPropertyName).OfType().FirstOrDefault(); + + var spanSliceMembers = spanType?.GetMembers(WellKnownMemberNames.SliceMethodName).OfType(); + var spanSliceStartLength = spanSliceMembers.GetFirstOrDefaultMemberWithParameterInfos(int32ParamInfo, int32ParamInfo); + var spanLength = spanType?.GetMembers(WellKnownMemberNames.LengthPropertyName).OfType().FirstOrDefault(); + + var readOnlySpanSliceMembers = readOnlySpanType?.GetMembers(WellKnownMemberNames.SliceMethodName).OfType(); + var readOnlySpanSliceStartLength = readOnlySpanSliceMembers.GetFirstOrDefaultMemberWithParameterInfos(int32ParamInfo, int32ParamInfo); + var readOnlySpanLength = readOnlySpanType?.GetMembers(WellKnownMemberNames.LengthPropertyName).OfType().FirstOrDefault(); + + var memorySliceMembers = memoryType?.GetMembers(WellKnownMemberNames.SliceMethodName).OfType(); + var memorySliceStartLength = memorySliceMembers.GetFirstOrDefaultMemberWithParameterInfos(int32ParamInfo, int32ParamInfo); + var memoryLength = memoryType?.GetMembers(WellKnownMemberNames.LengthPropertyName).OfType().FirstOrDefault(); + + // Bail out if we have no complete method pair + if ((substringStartLength is null || stringLength is null) && + (spanSliceStartLength is null || spanLength is null) && + (readOnlySpanSliceStartLength is null || readOnlySpanLength is null) && + (memorySliceStartLength is null || memoryLength is null)) + { + return false; + } + + symbols = new RequiredSymbols( + substringStartLength, spanSliceStartLength, readOnlySpanSliceStartLength, memorySliceStartLength, + stringLength, spanLength, readOnlySpanLength, memoryLength); + + return true; + } + + public IMethodSymbol? SubstringStartLength { get; } + public IMethodSymbol? SpanSliceStartLength { get; } + public IMethodSymbol? ReadOnlySpanSliceStartLength { get; } + public IMethodSymbol? MemorySliceStartLength { get; } + public IPropertySymbol? StringLength { get; } + public IPropertySymbol? SpanLength { get; } + public IPropertySymbol? ReadOnlySpanLength { get; } + public IPropertySymbol? MemoryLength { get; } + + public bool IsAnyStartLengthMethod(IMethodSymbol method) + { + return SymbolEqualityComparer.Default.Equals(method.OriginalDefinition, SubstringStartLength) || + SymbolEqualityComparer.Default.Equals(method.OriginalDefinition, SpanSliceStartLength) || + SymbolEqualityComparer.Default.Equals(method.OriginalDefinition, ReadOnlySpanSliceStartLength) || + SymbolEqualityComparer.Default.Equals(method.OriginalDefinition, MemorySliceStartLength); + } + + public bool IsAnyLengthProperty(IPropertySymbol property) + { + return SymbolEqualityComparer.Default.Equals(property.OriginalDefinition, StringLength) || + SymbolEqualityComparer.Default.Equals(property.OriginalDefinition, SpanLength) || + SymbolEqualityComparer.Default.Equals(property.OriginalDefinition, ReadOnlySpanLength) || + SymbolEqualityComparer.Default.Equals(property.OriginalDefinition, MemoryLength); + } + + public bool HasLengthPropertyOnInstance(IArgumentOperation argument, IOperation? instance, [NotNullWhen(true)] out IPropertyReferenceOperation? lengthProperty) + { + lengthProperty = argument + .DescendantsAndSelf() + .OfType() + .Where(p => IsAnyLengthProperty(p.Property)) + .FirstOrDefault(); + + if (lengthProperty is null) + { + return false; + } + + return AreSameInstance(lengthProperty.Instance, instance); + } + } + + private static bool AreSameInstance(IOperation? instance1, IOperation? instance2) + { + return (instance1, instance2) switch + { + (IFieldReferenceOperation fieldRef1, IFieldReferenceOperation fieldRef2) => fieldRef1.Member == fieldRef2.Member, + (IPropertyReferenceOperation propRef1, IPropertyReferenceOperation propRef2) => propRef1.Member == propRef2.Member, + (IParameterReferenceOperation paramRef1, IParameterReferenceOperation paramRef2) => paramRef1.Parameter == paramRef2.Parameter, + (ILocalReferenceOperation localRef1, ILocalReferenceOperation localRef2) => localRef1.Local == localRef2.Local, + (ILiteralOperation literalRef1, ILiteralOperation literalRef2) => literalRef1.ConstantValue.HasValue && literalRef1.ConstantValue.Value!.Equals(literalRef2.ConstantValue.Value), + _ => false, + }; + } + } +} diff --git a/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/MicrosoftCodeQualityAnalyzersResources.resx b/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/MicrosoftCodeQualityAnalyzersResources.resx index 50622c812c..7c03802175 100644 --- a/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/MicrosoftCodeQualityAnalyzersResources.resx +++ b/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/MicrosoftCodeQualityAnalyzersResources.resx @@ -929,6 +929,18 @@ {0} calls {1} but does not explicitly check whether the conversion succeeded. Either use the return value in a conditional statement or verify that the call site expects that the out argument will be set to the default value when the conversion fails. + + Avoid redundant length argument + + + Remove redundant length argument + + + An explicit length calculation can be error-prone and can be avoided when slicing to end of the buffer. + + + '{0}' uses a redundant length calculation that can be removed + Avoid uninstantiated internal classes diff --git a/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.cs.xlf b/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.cs.xlf index adc146e79f..09b3940e46 100644 --- a/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.cs.xlf +++ b/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.cs.xlf @@ -52,6 +52,26 @@ Zabránit nekonečné rekurzi + + Remove redundant length argument + Remove redundant length argument + + + + An explicit length calculation can be error-prone and can be avoided when slicing to end of the buffer. + An explicit length calculation can be error-prone and can be avoided when slicing to end of the buffer. + + + + '{0}' uses a redundant length calculation that can be removed + '{0}' uses a redundant length calculation that can be removed + + + + Avoid redundant length argument + Avoid redundant length argument + + Possible multiple enumerations of 'IEnumerable' collection. Consider using an implementation that avoids multiple enumerations. Je možných více výčtů kolekce IEnumerable. Zvažte použití implementace, která zabraňuje více výčtům. diff --git a/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.de.xlf b/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.de.xlf index 7dea49d786..7fa117508f 100644 --- a/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.de.xlf +++ b/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.de.xlf @@ -52,6 +52,26 @@ Endlosrekursion vermeiden + + Remove redundant length argument + Remove redundant length argument + + + + An explicit length calculation can be error-prone and can be avoided when slicing to end of the buffer. + An explicit length calculation can be error-prone and can be avoided when slicing to end of the buffer. + + + + '{0}' uses a redundant length calculation that can be removed + '{0}' uses a redundant length calculation that can be removed + + + + Avoid redundant length argument + Avoid redundant length argument + + Possible multiple enumerations of 'IEnumerable' collection. Consider using an implementation that avoids multiple enumerations. Mögliche mehrere Enumerationen der IEnumerable-Auflistung. Erwägen Sie die Verwendung einer Implementierung, die mehrere Enumerationen vermeidet. diff --git a/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.es.xlf b/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.es.xlf index d5c1da1c1f..a2dcc614d2 100644 --- a/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.es.xlf +++ b/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.es.xlf @@ -52,6 +52,26 @@ Evitar la recursividad infinita + + Remove redundant length argument + Remove redundant length argument + + + + An explicit length calculation can be error-prone and can be avoided when slicing to end of the buffer. + An explicit length calculation can be error-prone and can be avoided when slicing to end of the buffer. + + + + '{0}' uses a redundant length calculation that can be removed + '{0}' uses a redundant length calculation that can be removed + + + + Avoid redundant length argument + Avoid redundant length argument + + Possible multiple enumerations of 'IEnumerable' collection. Consider using an implementation that avoids multiple enumerations. Posibles enumeraciones múltiples de la colección “IEnumerable”. Considere la posibilidad de usar una implementación que evite varias enumeraciones. diff --git a/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.fr.xlf b/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.fr.xlf index f943caa998..5ac7c704ee 100644 --- a/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.fr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.fr.xlf @@ -52,6 +52,26 @@ Éviter la récursivité infinie + + Remove redundant length argument + Remove redundant length argument + + + + An explicit length calculation can be error-prone and can be avoided when slicing to end of the buffer. + An explicit length calculation can be error-prone and can be avoided when slicing to end of the buffer. + + + + '{0}' uses a redundant length calculation that can be removed + '{0}' uses a redundant length calculation that can be removed + + + + Avoid redundant length argument + Avoid redundant length argument + + Possible multiple enumerations of 'IEnumerable' collection. Consider using an implementation that avoids multiple enumerations. Possibilité d'énumérations multiples de la collection 'IEnumerable'. Envisagez d'utiliser une mise en œuvre qui évite les énumérations multiples. diff --git a/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.it.xlf b/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.it.xlf index 3736b5adf5..f0eb9cd2a7 100644 --- a/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.it.xlf +++ b/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.it.xlf @@ -52,6 +52,26 @@ Evitare la ricorsione infinita + + Remove redundant length argument + Remove redundant length argument + + + + An explicit length calculation can be error-prone and can be avoided when slicing to end of the buffer. + An explicit length calculation can be error-prone and can be avoided when slicing to end of the buffer. + + + + '{0}' uses a redundant length calculation that can be removed + '{0}' uses a redundant length calculation that can be removed + + + + Avoid redundant length argument + Avoid redundant length argument + + Possible multiple enumerations of 'IEnumerable' collection. Consider using an implementation that avoids multiple enumerations. Possibili enumerazioni multiple della raccolta 'IEnumerable'. Considerare l'utilizzo di un'implementazione che eviti enumerazioni multiple. diff --git a/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.ja.xlf b/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.ja.xlf index 56ae2a3168..5091be37c3 100644 --- a/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.ja.xlf +++ b/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.ja.xlf @@ -52,6 +52,26 @@ 無限再帰の回避 + + Remove redundant length argument + Remove redundant length argument + + + + An explicit length calculation can be error-prone and can be avoided when slicing to end of the buffer. + An explicit length calculation can be error-prone and can be avoided when slicing to end of the buffer. + + + + '{0}' uses a redundant length calculation that can be removed + '{0}' uses a redundant length calculation that can be removed + + + + Avoid redundant length argument + Avoid redundant length argument + + Possible multiple enumerations of 'IEnumerable' collection. Consider using an implementation that avoids multiple enumerations. 'IEnumerable' コレクションを複数列挙している可能性があります。複数列挙を回避する実装を検討してください。 diff --git a/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.ko.xlf b/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.ko.xlf index a99a30ceec..8cffae1fba 100644 --- a/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.ko.xlf +++ b/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.ko.xlf @@ -52,6 +52,26 @@ 무한 재귀 방지 + + Remove redundant length argument + Remove redundant length argument + + + + An explicit length calculation can be error-prone and can be avoided when slicing to end of the buffer. + An explicit length calculation can be error-prone and can be avoided when slicing to end of the buffer. + + + + '{0}' uses a redundant length calculation that can be removed + '{0}' uses a redundant length calculation that can be removed + + + + Avoid redundant length argument + Avoid redundant length argument + + Possible multiple enumerations of 'IEnumerable' collection. Consider using an implementation that avoids multiple enumerations. 'IEnumerable' 컬렉션에 대해 여러 개의 열거형이 가능합니다. 여러 열거형을 방지하는 구현을 사용하는 것이 좋습니다. diff --git a/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.pl.xlf b/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.pl.xlf index 4ee437fffc..87ab5c3c24 100644 --- a/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.pl.xlf +++ b/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.pl.xlf @@ -52,6 +52,26 @@ Unikaj nieskończonej rekursji + + Remove redundant length argument + Remove redundant length argument + + + + An explicit length calculation can be error-prone and can be avoided when slicing to end of the buffer. + An explicit length calculation can be error-prone and can be avoided when slicing to end of the buffer. + + + + '{0}' uses a redundant length calculation that can be removed + '{0}' uses a redundant length calculation that can be removed + + + + Avoid redundant length argument + Avoid redundant length argument + + Possible multiple enumerations of 'IEnumerable' collection. Consider using an implementation that avoids multiple enumerations. Możliwe wielokrotne wyliczenia kolekcji „IEnumerable”. Rozważ użycie implementacji, która pozwala uniknąć wielu wyliczeń. diff --git a/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.pt-BR.xlf b/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.pt-BR.xlf index 0ac5310dc0..e6e32c1f6f 100644 --- a/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.pt-BR.xlf +++ b/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.pt-BR.xlf @@ -52,6 +52,26 @@ Evitar a recursão infinita + + Remove redundant length argument + Remove redundant length argument + + + + An explicit length calculation can be error-prone and can be avoided when slicing to end of the buffer. + An explicit length calculation can be error-prone and can be avoided when slicing to end of the buffer. + + + + '{0}' uses a redundant length calculation that can be removed + '{0}' uses a redundant length calculation that can be removed + + + + Avoid redundant length argument + Avoid redundant length argument + + Possible multiple enumerations of 'IEnumerable' collection. Consider using an implementation that avoids multiple enumerations. Possíveis várias enumerações da coleção 'IEnumerable'. Considere usar uma implementação que evite várias enumerações. diff --git a/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.ru.xlf b/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.ru.xlf index 9f2d8f37f6..f49ee0198e 100644 --- a/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.ru.xlf +++ b/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.ru.xlf @@ -52,6 +52,26 @@ Избегайте бесконечной рекурсии + + Remove redundant length argument + Remove redundant length argument + + + + An explicit length calculation can be error-prone and can be avoided when slicing to end of the buffer. + An explicit length calculation can be error-prone and can be avoided when slicing to end of the buffer. + + + + '{0}' uses a redundant length calculation that can be removed + '{0}' uses a redundant length calculation that can be removed + + + + Avoid redundant length argument + Avoid redundant length argument + + Possible multiple enumerations of 'IEnumerable' collection. Consider using an implementation that avoids multiple enumerations. Возможно несколько перечислений коллекции IEnumerable. Рассмотрите возможность использования реализации без нескольких перечислений. diff --git a/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.tr.xlf b/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.tr.xlf index b744ce390b..18fbec8d01 100644 --- a/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.tr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.tr.xlf @@ -52,6 +52,26 @@ Sonsuz özyinelemeyi önleme + + Remove redundant length argument + Remove redundant length argument + + + + An explicit length calculation can be error-prone and can be avoided when slicing to end of the buffer. + An explicit length calculation can be error-prone and can be avoided when slicing to end of the buffer. + + + + '{0}' uses a redundant length calculation that can be removed + '{0}' uses a redundant length calculation that can be removed + + + + Avoid redundant length argument + Avoid redundant length argument + + Possible multiple enumerations of 'IEnumerable' collection. Consider using an implementation that avoids multiple enumerations. 'IEnumerable' koleksiyonunun birden çok numaralandırması olabilir. Birden çok numaralandırmayı önleyen bir uygulama kullanmayı düşünün. diff --git a/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.zh-Hans.xlf b/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.zh-Hans.xlf index 922bab9faf..c145d42480 100644 --- a/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.zh-Hans.xlf +++ b/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.zh-Hans.xlf @@ -52,6 +52,26 @@ 避免无限递归 + + Remove redundant length argument + Remove redundant length argument + + + + An explicit length calculation can be error-prone and can be avoided when slicing to end of the buffer. + An explicit length calculation can be error-prone and can be avoided when slicing to end of the buffer. + + + + '{0}' uses a redundant length calculation that can be removed + '{0}' uses a redundant length calculation that can be removed + + + + Avoid redundant length argument + Avoid redundant length argument + + Possible multiple enumerations of 'IEnumerable' collection. Consider using an implementation that avoids multiple enumerations. “IEnumerable” 集合可能的多个枚举。请考虑使用避免多个枚举的实现。 diff --git a/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.zh-Hant.xlf b/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.zh-Hant.xlf index 01a9abbdd9..333ca9e34a 100644 --- a/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.zh-Hant.xlf +++ b/src/NetAnalyzers/Core/Microsoft.CodeQuality.Analyzers/xlf/MicrosoftCodeQualityAnalyzersResources.zh-Hant.xlf @@ -52,6 +52,26 @@ 避免無限遞迴 + + Remove redundant length argument + Remove redundant length argument + + + + An explicit length calculation can be error-prone and can be avoided when slicing to end of the buffer. + An explicit length calculation can be error-prone and can be avoided when slicing to end of the buffer. + + + + '{0}' uses a redundant length calculation that can be removed + '{0}' uses a redundant length calculation that can be removed + + + + Avoid redundant length argument + Avoid redundant length argument + + Possible multiple enumerations of 'IEnumerable' collection. Consider using an implementation that avoids multiple enumerations. 'IEnumerable' 集合可能有多個列舉。請考慮避免使用多個列舉的實作。 diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md index 2256ed080a..cd5846c4f0 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md @@ -912,6 +912,18 @@ Throw helpers are simpler and more efficient than an if block constructing a new |CodeFix|True| --- +## [CA1514](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1514): Avoid redundant length argument + +An explicit length calculation can be error-prone and can be avoided when slicing to end of the buffer. + +|Item|Value| +|-|-| +|Category|Maintainability| +|Enabled|True| +|Severity|Info| +|CodeFix|True| +--- + ## [CA1700](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1700): Do not name enum values 'Reserved' This rule assumes that an enumeration member that has a name that contains "reserved" is not currently used but is a placeholder to be renamed or removed in a future version. Renaming or removing a member is a breaking change. diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif index e93719a3aa..067ec2e151 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif @@ -2106,6 +2106,26 @@ ] } }, + "CA1514": { + "id": "CA1514", + "shortDescription": "Avoid redundant length argument", + "fullDescription": "An explicit length calculation can be error-prone and can be avoided when slicing to end of the buffer.", + "defaultLevel": "note", + "helpUri": "https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1514", + "properties": { + "category": "Maintainability", + "isEnabledByDefault": true, + "typeName": "AvoidLengthCalculationWhenSlicingToEndAnalyzer", + "languages": [ + "C#", + "Visual Basic" + ], + "tags": [ + "Telemetry", + "EnabledRuleInAggressiveMode" + ] + } + }, "CA1700": { "id": "CA1700", "shortDescription": "Do not name enum values 'Reserved'", diff --git a/src/NetAnalyzers/RulesMissingDocumentation.md b/src/NetAnalyzers/RulesMissingDocumentation.md index 0d5aa2f027..0587796be1 100644 --- a/src/NetAnalyzers/RulesMissingDocumentation.md +++ b/src/NetAnalyzers/RulesMissingDocumentation.md @@ -6,6 +6,7 @@ CA1510 | | Use ArgumentException throw helper | CA1512 | | Use ArgumentOutOfRangeException throw helper | CA1513 | | Use ObjectDisposedException throw helper | +CA1514 | | Avoid redundant length argument | CA1856 | | Incorrect usage of ConstantExpected attribute | CA1857 | | A constant is expected for the parameter | CA1862 | | Use the 'StringComparison' method overloads to perform case-insensitive string comparisons | @@ -13,6 +14,5 @@ CA1863 | | Use char overload | CA1866 | | Use char overload | CA1867 | | Use char overload | -CA1870 | | Use a cached 'SearchValues' instance | CA2021 | | Do not call Enumerable.Cast\ or Enumerable.OfType\ with incompatible types | CA2261 | | Do not use ConfigureAwaitOptions.SuppressThrowing with Task\ | diff --git a/src/NetAnalyzers/UnitTests/Microsoft.CodeQuality.Analyzers/Maintainability/AvoidLengthCalculationWhenSlicingToEndTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.CodeQuality.Analyzers/Maintainability/AvoidLengthCalculationWhenSlicingToEndTests.cs new file mode 100644 index 0000000000..92061511ce --- /dev/null +++ b/src/NetAnalyzers/UnitTests/Microsoft.CodeQuality.Analyzers/Maintainability/AvoidLengthCalculationWhenSlicingToEndTests.cs @@ -0,0 +1,1648 @@ +// 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 Xunit; +using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< + Microsoft.CodeQuality.Analyzers.Maintainability.AvoidLengthCalculationWhenSlicingToEndAnalyzer, + Microsoft.CodeQuality.Analyzers.Maintainability.AvoidLengthCalculationWhenSlicingToEndFixer>; +using VerifyVB = Test.Utilities.VisualBasicCodeFixVerifier< + Microsoft.CodeQuality.Analyzers.Maintainability.AvoidLengthCalculationWhenSlicingToEndAnalyzer, + Microsoft.CodeQuality.Analyzers.Maintainability.AvoidLengthCalculationWhenSlicingToEndFixer>; + +namespace Microsoft.CodeQuality.Analyzers.Maintainability.UnitTests +{ + public class AvoidLengthCalculationWhenSlicingToEndTests + { + [Theory] + [InlineData("string")] + [InlineData("System.Span")] + [InlineData("System.ReadOnlySpan")] + [InlineData("System.Memory")] + public async Task ConstantLiteralSubtracted_OffersFixer_CS(string type) + { + string source = $$""" + class C + { + void M() + { + {{type}} x = {{InstantiateExpressionFromType_CS(type)}}; + [|x.{{TestMethodFromType(type)}}(5, x.Length - 5)|]; + } + } + """; + + string fixedSource = $$""" + class C + { + void M() + { + {{type}} x = {{InstantiateExpressionFromType_CS(type)}}; + x.{{TestMethodFromType(type)}}(5); + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Span")] + [InlineData("System.ReadOnlySpan")] + [InlineData("System.Memory")] + public async Task ConstantLiteralsSumEqualSubtracted_OffersFixer_CS(string type) + { + string source = $$""" + class C + { + void M() + { + {{type}} x = {{InstantiateExpressionFromType_CS(type)}}; + [|x.{{TestMethodFromType(type)}}(6, x.Length - 1 - 2 - 3)|]; + } + } + """; + + string fixedSource = $$""" + class C + { + void M() + { + {{type}} x = {{InstantiateExpressionFromType_CS(type)}}; + x.{{TestMethodFromType(type)}}(6); + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Span")] + [InlineData("System.ReadOnlySpan")] + [InlineData("System.Memory")] + public async Task ConstantSubtracted_OffersFixer_CS(string type) + { + string source = $$""" + class C + { + const int Constant = 2; + + void M() + { + {{type}} x = {{InstantiateExpressionFromType_CS(type)}}; + [|x.{{TestMethodFromType(type)}}(Constant, x.Length - Constant)|]; + } + } + """; + + string fixedSource = $$""" + class C + { + const int Constant = 2; + + void M() + { + {{type}} x = {{InstantiateExpressionFromType_CS(type)}}; + x.{{TestMethodFromType(type)}}(Constant); + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Span")] + [InlineData("System.ReadOnlySpan")] + [InlineData("System.Memory")] + public async Task FieldSubtracted_OffersFixer_CS(string type) + { + string source = $$""" + class C + { + private int Field = 3; + + void M() + { + {{type}} x = {{InstantiateExpressionFromType_CS(type)}}; + [|x.{{TestMethodFromType(type)}}(Field, x.Length - Field)|]; + } + } + """; + + string fixedSource = $$""" + class C + { + private int Field = 3; + + void M() + { + {{type}} x = {{InstantiateExpressionFromType_CS(type)}}; + x.{{TestMethodFromType(type)}}(Field); + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Span")] + [InlineData("System.ReadOnlySpan")] + [InlineData("System.Memory")] + public async Task FieldWithInstanceReferenceSubtracted_OffersFixer_CS(string type) + { + string source = $$""" + class C + { + private int Field = 3; + + void M() + { + {{type}} x = {{InstantiateExpressionFromType_CS(type)}}; + [|x.{{TestMethodFromType(type)}}(this.Field, x.Length - this.Field)|]; + } + } + """; + + string fixedSource = $$""" + class C + { + private int Field = 3; + + void M() + { + {{type}} x = {{InstantiateExpressionFromType_CS(type)}}; + x.{{TestMethodFromType(type)}}(this.Field); + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Span")] + [InlineData("System.ReadOnlySpan")] + [InlineData("System.Memory")] + public async Task LocalSubtracted_OffersFixer_CS(string type) + { + string source = $$""" + class C + { + void M() + { + int local = 2; + {{type}} x = {{InstantiateExpressionFromType_CS(type)}}; + [|x.{{TestMethodFromType(type)}}(local, x.Length - local)|]; + } + } + """; + + string fixedSource = $$""" + class C + { + void M() + { + int local = 2; + {{type}} x = {{InstantiateExpressionFromType_CS(type)}}; + x.{{TestMethodFromType(type)}}(local); + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Span")] + [InlineData("System.ReadOnlySpan")] + [InlineData("System.Memory")] + public async Task ParameterSubtracted_OffersFixer_CS(string type) + { + string source = $$""" + class C + { + void M(int parameter) + { + {{type}} x = {{InstantiateExpressionFromType_CS(type)}}; + [|x.{{TestMethodFromType(type)}}(parameter, x.Length - parameter)|]; + } + } + """; + + string fixedSource = $$""" + class C + { + void M(int parameter) + { + {{type}} x = {{InstantiateExpressionFromType_CS(type)}}; + x.{{TestMethodFromType(type)}}(parameter); + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Span")] + [InlineData("System.ReadOnlySpan")] + [InlineData("System.Memory")] + public async Task AutoPropertySubtracted_OffersFixer_CS(string type) + { + string source = $$""" + class C + { + public int AutoProperty { get; } + + void M() + { + {{type}} x = {{InstantiateExpressionFromType_CS(type)}}; + [|x.{{TestMethodFromType(type)}}(AutoProperty, x.Length - AutoProperty)|]; + } + } + """; + + string fixedSource = $$""" + class C + { + public int AutoProperty { get; } + + void M() + { + {{type}} x = {{InstantiateExpressionFromType_CS(type)}}; + x.{{TestMethodFromType(type)}}(AutoProperty); + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Span")] + [InlineData("System.ReadOnlySpan")] + [InlineData("System.Memory")] + public async Task AutoPropertyWithInstanceReferenceSubtracted_OffersFixer_CS(string type) + { + string source = $$""" + class C + { + public int AutoProperty { get; } + + void M() + { + {{type}} x = {{InstantiateExpressionFromType_CS(type)}}; + [|x.{{TestMethodFromType(type)}}(this.AutoProperty, x.Length - this.AutoProperty)|]; + } + } + """; + + string fixedSource = $$""" + class C + { + public int AutoProperty { get; } + + void M() + { + {{type}} x = {{InstantiateExpressionFromType_CS(type)}}; + x.{{TestMethodFromType(type)}}(this.AutoProperty); + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Span")] + [InlineData("System.ReadOnlySpan")] + [InlineData("System.Memory")] + public async Task ExplicitPropertySubtracted_NoDiagnostic_CS(string type) + { + string source = $$""" + class C + { + private readonly int Field; + + public int Property + { + get { return Field; } + } + + void M() + { + {{type}} x = {{InstantiateExpressionFromType_CS(type)}}; + x.{{TestMethodFromType(type)}}(Property, x.Length - Property); + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Span")] + [InlineData("System.ReadOnlySpan")] + [InlineData("System.Memory")] + public async Task MethodSubtracted_NoDiagnostic_CS(string type) + { + string source = $$""" + class C + { + int Method() + { + return 1; + } + + void M(int parameter) + { + {{type}} x = {{InstantiateExpressionFromType_CS(type)}}; + x.{{TestMethodFromType(type)}}(Method(), x.Length - Method()); + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Span")] + [InlineData("System.ReadOnlySpan")] + [InlineData("System.Memory")] + public async Task SupportedUnaryOperator_OffersFixer_CS(string type) + { + string source = $$""" + class C + { + void M() + { + {{type}} x = {{InstantiateExpressionFromType_CS(type)}}; + [|x.{{TestMethodFromType(type)}}(+5, x.Length + -5)|]; + } + } + """; + + string fixedSource = $$""" + class C + { + void M() + { + {{type}} x = {{InstantiateExpressionFromType_CS(type)}}; + x.{{TestMethodFromType(type)}}(+5); + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Span")] + [InlineData("System.ReadOnlySpan")] + [InlineData("System.Memory")] + public async Task UnsupportedUnaryOperator_NoDiagnostic_CS(string type) + { + string source = $$""" + class C + { + void M() + { + {{type}} x = {{InstantiateExpressionFromType_CS(type)}}; + x.{{TestMethodFromType(type)}}(~1, x.Length - ~1); + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Span")] + [InlineData("System.ReadOnlySpan")] + [InlineData("System.Memory")] + public async Task SupportedBinaryOperator_OffersFixer_CS(string type) + { + string source = $$""" + class C + { + private int Field; + + void M() + { + int local1 = 1; + int local2 = 2; + + {{type}} x = {{InstantiateExpressionFromType_CS(type)}}; + [|x.{{TestMethodFromType(type)}}(local1, x.Length - local1)|]; + [|x.{{TestMethodFromType(type)}}(local1 + local2, x.Length - local1 - local2)|]; + [|x.{{TestMethodFromType(type)}}(local1 + local2, x.Length - (local1 + local2))|]; + [|x.{{TestMethodFromType(type)}}(Field, x.Length - Field)|]; + [|x.{{TestMethodFromType(type)}}(Field + 5, x.Length - Field - 5)|]; + [|x.{{TestMethodFromType(type)}}(Field + 5, x.Length - (Field + 5))|]; + } + } + """; + + string fixedSource = $$""" + class C + { + private int Field; + + void M() + { + int local1 = 1; + int local2 = 2; + + {{type}} x = {{InstantiateExpressionFromType_CS(type)}}; + x.{{TestMethodFromType(type)}}(local1); + x.{{TestMethodFromType(type)}}(local1 + local2); + x.{{TestMethodFromType(type)}}(local1 + local2); + x.{{TestMethodFromType(type)}}(Field); + x.{{TestMethodFromType(type)}}(Field + 5); + x.{{TestMethodFromType(type)}}(Field + 5); + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Span")] + [InlineData("System.ReadOnlySpan")] + [InlineData("System.Memory")] + public async Task UnsupportedBinaryOperator_NoDiagnostic_CS(string type) + { + string source = $$""" + class C + { + void M() + { + {{type}} x = {{InstantiateExpressionFromType_CS(type)}}; + x.{{TestMethodFromType(type)}}(1 * 2, x.Length - 2); + x.{{TestMethodFromType(type)}}(6 / 3, x.Length - 2); + x.{{TestMethodFromType(type)}}(5 % 3, x.Length - 2); + x.{{TestMethodFromType(type)}}(1 << 1, x.Length - 2); + x.{{TestMethodFromType(type)}}(4 >> 1, x.Length - 2); + x.{{TestMethodFromType(type)}}(2 & 2, x.Length - 2); + x.{{TestMethodFromType(type)}}(2 | 0, x.Length - 2); + x.{{TestMethodFromType(type)}}(2 ^ 0, x.Length - 2); + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Span")] + [InlineData("System.ReadOnlySpan")] + [InlineData("System.Memory")] + public async Task DifferentConstantLiteralSubtracted_NoDiagnostic_CS(string type) + { + string source = $$""" + class C + { + void M() + { + {{type}} x = {{InstantiateExpressionFromType_CS(type)}}; + x.{{TestMethodFromType(type)}}(0, x.Length - 5); + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Span")] + [InlineData("System.ReadOnlySpan")] + [InlineData("System.Memory")] + public async Task DifferentConstantSubtracted_NoDiagnostic_CS(string type) + { + string source = $$""" + class C + { + const int Constant1 = 1; + const int Constant2 = 2; + + void M() + { + {{type}} x = {{InstantiateExpressionFromType_CS(type)}}; + x.{{TestMethodFromType(type)}}(Constant1, x.Length - Constant2); + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Span")] + [InlineData("System.ReadOnlySpan")] + [InlineData("System.Memory")] + public async Task DifferentFieldSubtracted_NoDiagnostic_CS(string type) + { + string source = $$""" + class C + { + private int Field1 = 1; + private int Field2 = 2; + + void M() + { + {{type}} x = {{InstantiateExpressionFromType_CS(type)}}; + x.{{TestMethodFromType(type)}}(Field1, x.Length - Field2); + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Span")] + [InlineData("System.ReadOnlySpan")] + [InlineData("System.Memory")] + public async Task DifferentLocalSubtracted_NoDiagnostic_CS(string type) + { + string source = $$""" + class C + { + void M() + { + int Local1 = 1; + int Local2 = 2; + {{type}} x = {{InstantiateExpressionFromType_CS(type)}}; + x.{{TestMethodFromType(type)}}(Local1, x.Length - Local2); + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Span")] + [InlineData("System.ReadOnlySpan")] + [InlineData("System.Memory")] + public async Task DifferentParameterSubtracted_NoDiagnostic_CS(string type) + { + string source = $$""" + class C + { + void M(int parameter1, int parameter2) + { + {{type}} x = {{InstantiateExpressionFromType_CS(type)}}; + x.{{TestMethodFromType(type)}}(parameter1, x.Length - parameter2); + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Span")] + [InlineData("System.ReadOnlySpan")] + [InlineData("System.Memory")] + public async Task DifferentAutoPropertySubtracted_NoDiagnostic_CS(string type) + { + string source = $$""" + class C + { + public int AutoProperty1 { get; } + public int AutoProperty2 { get; } + + void M() + { + {{type}} x = {{InstantiateExpressionFromType_CS(type)}}; + x.{{TestMethodFromType(type)}}(AutoProperty1, x.Length - AutoProperty2); + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Span")] + [InlineData("System.ReadOnlySpan")] + [InlineData("System.Memory")] + public async Task DifferentLengthPropertySubtracted_NoDiagnostic_CS(string type) + { + string source = $$""" + class C + { + void M() + { + {{type}} x = {{InstantiateExpressionFromType_CS(type)}}; + x.{{TestMethodFromType(type)}}(5, x.Length - System.Environment.NewLine.Length - 5); + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, source); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Span")] + [InlineData("System.ReadOnlySpan")] + [InlineData("System.Memory")] + public async Task ZeroAsStartNothingSubtracted_OffersFixer_CS(string type) + { + string source = $$""" + class C + { + void M() + { + {{type}} x = {{InstantiateExpressionFromType_CS(type)}}; + [|x.{{TestMethodFromType(type)}}(0, x.Length)|]; + } + } + """; + + string fixedSource = $$""" + class C + { + void M() + { + {{type}} x = {{InstantiateExpressionFromType_CS(type)}}; + x.{{TestMethodFromType(type)}}(0); + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Span")] + [InlineData("System.ReadOnlySpan")] + [InlineData("System.Memory")] + public async Task AdditionalParenthesis_OffersFixer_CS(string type) + { + string source = $$""" + class C + { + void M() + { + {{type}} x = {{InstantiateExpressionFromType_CS(type)}}; + [|x.{{TestMethodFromType(type)}}(((1 + 2)) + 3, x.Length - 1 - 2 - 3)|]; + } + } + """; + + string fixedSource = $$""" + class C + { + void M() + { + {{type}} x = {{InstantiateExpressionFromType_CS(type)}}; + x.{{TestMethodFromType(type)}}(((1 + 2)) + 3); + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Span")] + [InlineData("System.ReadOnlySpan")] + [InlineData("System.Memory")] + public async Task NestedConstantsSubtracted_OffersFixer_CS(string type) + { + string source = $$""" + class C + { + void M() + { + int local1 = 1; + int local2 = 1; + {{type}} x = {{InstantiateExpressionFromType_CS(type)}}; + [|x.{{TestMethodFromType(type)}}(1 + 2 - (3 + (4 - +5 - 6 + -7) + 8) - 9, x.Length - 1 - 2 + 3 + 4 - 5 - 6 - 7 + 8 + 9)|]; + } + } + """; + + string fixedSource = $$""" + class C + { + void M() + { + int local1 = 1; + int local2 = 1; + {{type}} x = {{InstantiateExpressionFromType_CS(type)}}; + x.{{TestMethodFromType(type)}}(1 + 2 - (3 + (4 - +5 - 6 + -7) + 8) - 9); + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Theory] + [InlineData("System.Span")] + [InlineData("System.ReadOnlySpan")] + [InlineData("System.Memory")] + public async Task NamedArguments_OffersFixer_CS(string type) + { + string source = $$""" + class C + { + void M() + { + {{type}} x = {{InstantiateExpressionFromType_CS(type)}}; + [|x.{{TestMethodFromType(type)}}(start: 1, length: x.Length - 1)|]; + } + } + """; + + string fixedSource = $$""" + class C + { + void M() + { + {{type}} x = {{InstantiateExpressionFromType_CS(type)}}; + x.{{TestMethodFromType(type)}}(start: 1); + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Theory] + [InlineData("System.Span")] + [InlineData("System.ReadOnlySpan")] + [InlineData("System.Memory")] + public async Task NamedArgumentsSwapped_OffersFixer_CS(string type) + { + string source = $$""" + class C + { + void M() + { + {{type}} x = {{InstantiateExpressionFromType_CS(type)}}; + [|x.{{TestMethodFromType(type)}}(length: x.Length - 1, start: 1)|]; + } + } + """; + + string fixedSource = $$""" + class C + { + void M() + { + {{type}} x = {{InstantiateExpressionFromType_CS(type)}}; + x.{{TestMethodFromType(type)}}(start: 1); + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Theory] + [InlineData("System.Span")] + [InlineData("System.ReadOnlySpan")] + [InlineData("System.Memory")] + public async Task TriviaIsPreserved_OffersFixer_CS(string type) + { + string source = $$""" + class C + { + void M() + { + {{type}} x = {{InstantiateExpressionFromType_CS(type)}}; + // reticulates the splines + [|x.{{TestMethodFromType(type)}}(1, x.Length - 1)|]; + } + } + """; + + string fixedSource = $$""" + class C + { + void M() + { + {{type}} x = {{InstantiateExpressionFromType_CS(type)}}; + // reticulates the splines + x.{{TestMethodFromType(type)}}(1); + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task StringLiteralSubstring_OffersFixer_CS() + { + string source = $$""" + class C + { + void M() + { + [|"test".Substring(2, "test".Length - 2)|]; + } + } + """; + + string fixedSource = $$""" + class C + { + void M() + { + "test".Substring(2); + } + } + """; + + await VerifyCS.VerifyCodeFixAsync(source, fixedSource); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Memory")] + public async Task ConstantLiteralSubtracted_OffersFixer_VB(string type) + { + string source = $$""" + Public Class C + Public Sub M() + Dim x as {{TypeForVB(type)}} = {{InstantiateExpressionFromType_VB(type)}} + [|x.{{TestMethodFromType(type)}}(5, x.Length - 5)|] + End Sub + End Class + """; + + string fixedSource = $$""" + Public Class C + Public Sub M() + Dim x as {{TypeForVB(type)}} = {{InstantiateExpressionFromType_VB(type)}} + x.{{TestMethodFromType(type)}}(5) + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, fixedSource); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Memory")] + public async Task ConstantLiteralsSumEqualSubtracted_OffersFixer_VB(string type) + { + string source = $$""" + Public Class C + Public Sub M() + Dim x as {{TypeForVB(type)}} = {{InstantiateExpressionFromType_VB(type)}} + [|x.{{TestMethodFromType(type)}}(6, x.Length - 1 - 2 - 3)|] + End Sub + End Class + """; + + string fixedSource = $$""" + Public Class C + Public Sub M() + Dim x as {{TypeForVB(type)}} = {{InstantiateExpressionFromType_VB(type)}} + x.{{TestMethodFromType(type)}}(6) + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, fixedSource); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Memory")] + public async Task ConstantSubtracted_OffersFixer_VB(string type) + { + string source = $$""" + Public Class C + Const Constant as Integer = 2 + + Public Sub M() + Dim x as {{TypeForVB(type)}} = {{InstantiateExpressionFromType_VB(type)}} + [|x.{{TestMethodFromType(type)}}(Constant, x.Length - Constant)|] + End Sub + End Class + """; + + string fixedSource = $$""" + Public Class C + Const Constant as Integer = 2 + + Public Sub M() + Dim x as {{TypeForVB(type)}} = {{InstantiateExpressionFromType_VB(type)}} + x.{{TestMethodFromType(type)}}(Constant) + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, fixedSource); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Memory")] + public async Task FieldSubtracted_OffersFixer_VB(string type) + { + string source = $$""" + Public Class C + Private ReadOnly Field as Integer = 3 + + Public Sub M() + Dim x as {{TypeForVB(type)}} = {{InstantiateExpressionFromType_VB(type)}} + [|x.{{TestMethodFromType(type)}}(Field, x.Length - Field)|] + End Sub + End Class + """; + + string fixedSource = $$""" + Public Class C + Private ReadOnly Field as Integer = 3 + + Public Sub M() + Dim x as {{TypeForVB(type)}} = {{InstantiateExpressionFromType_VB(type)}} + x.{{TestMethodFromType(type)}}(Field) + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, fixedSource); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Memory")] + public async Task FieldWithInstanceReferenceSubtracted_OffersFixer_VB(string type) + { + string source = $$""" + Public Class C + Private ReadOnly Field as Integer = 3 + + Public Sub M() + Dim x as {{TypeForVB(type)}} = {{InstantiateExpressionFromType_VB(type)}} + [|x.{{TestMethodFromType(type)}}(Me.Field, x.Length - Me.Field)|] + End Sub + End Class + """; + + string fixedSource = $$""" + Public Class C + Private ReadOnly Field as Integer = 3 + + Public Sub M() + Dim x as {{TypeForVB(type)}} = {{InstantiateExpressionFromType_VB(type)}} + x.{{TestMethodFromType(type)}}(Me.Field) + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, fixedSource); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Memory")] + public async Task LocalSubtracted_OffersFixer_VB(string type) + { + string source = $$""" + Public Class C + Public Sub M() + Dim local as Integer = 2 + Dim x as {{TypeForVB(type)}} = {{InstantiateExpressionFromType_VB(type)}} + [|x.{{TestMethodFromType(type)}}(local, x.Length - local)|] + End Sub + End Class + """; + + string fixedSource = $$""" + Public Class C + Public Sub M() + Dim local as Integer = 2 + Dim x as {{TypeForVB(type)}} = {{InstantiateExpressionFromType_VB(type)}} + x.{{TestMethodFromType(type)}}(local) + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, fixedSource); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Memory")] + public async Task ParameterSubtracted_OffersFixer_VB(string type) + { + string source = $$""" + Public Class C + Public Sub M(parameter as Integer) + Dim x as {{TypeForVB(type)}} = {{InstantiateExpressionFromType_VB(type)}} + [|x.{{TestMethodFromType(type)}}(parameter, x.Length - parameter)|] + End Sub + End Class + """; + + string fixedSource = $$""" + Public Class C + Public Sub M(parameter as Integer) + Dim x as {{TypeForVB(type)}} = {{InstantiateExpressionFromType_VB(type)}} + x.{{TestMethodFromType(type)}}(parameter) + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, fixedSource); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Memory")] + public async Task AutoPropertySubtracted_OffersFixer_VB(string type) + { + string source = $$""" + Public Class C + Public Property AutoProperty As Integer + + Public Sub M() + Dim x as {{TypeForVB(type)}} = {{InstantiateExpressionFromType_VB(type)}} + [|x.{{TestMethodFromType(type)}}(AutoProperty, x.Length - AutoProperty)|] + End Sub + End Class + """; + + string fixedSource = $$""" + Public Class C + Public Property AutoProperty As Integer + + Public Sub M() + Dim x as {{TypeForVB(type)}} = {{InstantiateExpressionFromType_VB(type)}} + x.{{TestMethodFromType(type)}}(AutoProperty) + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, fixedSource); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Memory")] + public async Task AutoPropertyWithInstanceReferenceSubtracted_OffersFixer_VB(string type) + { + string source = $$""" + Public Class C + Public Property AutoProperty As Integer + + Public Sub M() + Dim x as {{TypeForVB(type)}} = {{InstantiateExpressionFromType_VB(type)}} + [|x.{{TestMethodFromType(type)}}(Me.AutoProperty, x.Length - Me.AutoProperty)|] + End Sub + End Class + """; + + string fixedSource = $$""" + Public Class C + Public Property AutoProperty As Integer + + Public Sub M() + Dim x as {{TypeForVB(type)}} = {{InstantiateExpressionFromType_VB(type)}} + x.{{TestMethodFromType(type)}}(Me.AutoProperty) + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, fixedSource); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Memory")] + public async Task ExplicitPropertySubtracted_NoDiagnostic_VB(string type) + { + string source = $$""" + Public Class C + Private ReadOnly Field as Integer + + Public ReadOnly Property Prop As Integer + Get + Return Field + End Get + End Property + + Public Sub M() + Dim x as {{TypeForVB(type)}} = {{InstantiateExpressionFromType_VB(type)}} + x.{{TestMethodFromType(type)}}(Prop, x.Length - Prop) + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, source); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Memory")] + public async Task MethodSubtracted_NoDiagnostic_VB(string type) + { + string source = $$""" + Public Class C + Private ReadOnly Field as Integer + + Public Function Method() as Integer + Return 1 + End Function + + Public Sub M() + Dim x as {{TypeForVB(type)}} = {{InstantiateExpressionFromType_VB(type)}} + x.{{TestMethodFromType(type)}}(Method(), x.Length - Method()) + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, source); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Memory")] + public async Task SupportedUnaryOperator_OffersFixer_VB(string type) + { + string source = $$""" + Public Class C + Public Sub M() + Dim x as {{TypeForVB(type)}} = {{InstantiateExpressionFromType_VB(type)}} + [|x.{{TestMethodFromType(type)}}(+5, x.Length + -5)|] + End Sub + End Class + """; + + string fixedSource = $$""" + Public Class C + Public Sub M() + Dim x as {{TypeForVB(type)}} = {{InstantiateExpressionFromType_VB(type)}} + x.{{TestMethodFromType(type)}}(+5) + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, fixedSource); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Memory")] + public async Task SupportedBinaryOperator_OffersFixer_VB(string type) + { + string source = $$""" + Public Class C + Private ReadOnly Field as Integer + + Public Sub M() + Dim local1 as Integer = 1 + Dim local2 as Integer = 2 + + Dim x as {{TypeForVB(type)}} = {{InstantiateExpressionFromType_VB(type)}} + [|x.{{TestMethodFromType(type)}}(local1, x.Length - local1)|] + [|x.{{TestMethodFromType(type)}}(local1 + local2, x.Length - local1 - local2)|] + [|x.{{TestMethodFromType(type)}}(local1 + local2, x.Length - (local1 + local2))|] + [|x.{{TestMethodFromType(type)}}(Field, x.Length - Field)|] + [|x.{{TestMethodFromType(type)}}(Field + 5, x.Length - Field - 5)|] + [|x.{{TestMethodFromType(type)}}(Field + 5, x.Length - (Field + 5))|] + End Sub + End Class + """; + + string fixedSource = $$""" + Public Class C + Private ReadOnly Field as Integer + + Public Sub M() + Dim local1 as Integer = 1 + Dim local2 as Integer = 2 + + Dim x as {{TypeForVB(type)}} = {{InstantiateExpressionFromType_VB(type)}} + x.{{TestMethodFromType(type)}}(local1) + x.{{TestMethodFromType(type)}}(local1 + local2) + x.{{TestMethodFromType(type)}}(local1 + local2) + x.{{TestMethodFromType(type)}}(Field) + x.{{TestMethodFromType(type)}}(Field + 5) + x.{{TestMethodFromType(type)}}(Field + 5) + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, fixedSource); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Memory")] + public async Task UnsupportedBinaryOperator_NoDiagnostic_VB(string type) + { + string source = $$""" + Public Class C + Public Sub M() + Dim x as {{TypeForVB(type)}} = {{InstantiateExpressionFromType_VB(type)}} + x.{{TestMethodFromType(type)}}(1 * 2, x.Length - 2) + x.{{TestMethodFromType(type)}}(6 / 3, x.Length - 2) + x.{{TestMethodFromType(type)}}(6 \ 3, x.Length - 2) + x.{{TestMethodFromType(type)}}(5 Mod 3, x.Length - 2) + x.{{TestMethodFromType(type)}}(2 ^ 1, x.Length - 2) + x.{{TestMethodFromType(type)}}(1 << 1, x.Length - 2) + x.{{TestMethodFromType(type)}}(4 >> 1, x.Length - 2) + x.{{TestMethodFromType(type)}}(2 And 2, x.Length - 2) + x.{{TestMethodFromType(type)}}(2 Or 0, x.Length - 2) + x.{{TestMethodFromType(type)}}(2 Xor 0, x.Length - 2) + x.{{TestMethodFromType(type)}}(2 Like 0, x.Length - 2) + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, source); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Memory")] + public async Task DifferentConstantLiteralSubtracted_NoDiagnostic_VB(string type) + { + string source = $$""" + Public Class C + Public Sub M() + Dim x as {{TypeForVB(type)}} = {{InstantiateExpressionFromType_VB(type)}} + x.{{TestMethodFromType(type)}}(0, x.Length - 5) + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, source); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Memory")] + public async Task DifferentConstantSubtracted_NoDiagnostic_VB(string type) + { + string source = $$""" + Public Class C + const Constant1 as Integer = 1 + const Constant2 as Integer = 2 + + Public Sub M() + Dim x as {{TypeForVB(type)}} = {{InstantiateExpressionFromType_VB(type)}} + x.{{TestMethodFromType(type)}}(Constant1, x.Length - Constant2) + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, source); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Memory")] + public async Task DifferentFieldSubtracted_NoDiagnostic_VB(string type) + { + string source = $$""" + Public Class C + Private ReadOnly Field1 as Integer = 1 + Private ReadOnly Field2 as Integer = 2 + + Public Sub M() + Dim x as {{TypeForVB(type)}} = {{InstantiateExpressionFromType_VB(type)}} + x.{{TestMethodFromType(type)}}(Field1, x.Length - Field2) + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, source); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Memory")] + public async Task DifferentLocalSubtracted_NoDiagnostic_VB(string type) + { + string source = $$""" + Public Class C + Public Sub M() + Dim local1 as Integer = 1 + Dim local2 as Integer = 2 + + Dim x as {{TypeForVB(type)}} = {{InstantiateExpressionFromType_VB(type)}} + x.{{TestMethodFromType(type)}}(local1, x.Length - local2) + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, source); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Memory")] + public async Task DifferentParameterSubtracted_NoDiagnostic_VB(string type) + { + string source = $$""" + Public Class C + Public Sub M(parameter1 as Integer, parameter2 as Integer) + Dim x as {{TypeForVB(type)}} = {{InstantiateExpressionFromType_VB(type)}} + x.{{TestMethodFromType(type)}}(parameter1, x.Length - parameter2) + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, source); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Memory")] + public async Task DifferentAutoPropertySubtracted_NoDiagnostic_VB(string type) + { + string source = $$""" + Public Class C + Public Property AutoProperty1 As Integer + Public Property AutoProperty2 As Integer + + Public Sub M() + Dim x as {{TypeForVB(type)}} = {{InstantiateExpressionFromType_VB(type)}} + x.{{TestMethodFromType(type)}}(AutoProperty1, x.Length - AutoProperty2) + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, source); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Memory")] + public async Task DifferentLengthPropertySubtracted_NoDiagnostic_VB(string type) + { + string source = $$""" + Public Class C + Public Sub M() + Dim x as {{TypeForVB(type)}} = {{InstantiateExpressionFromType_VB(type)}} + x.{{TestMethodFromType(type)}}(5, x.Length - System.Environment.NewLine.Length - 5) + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, source); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Memory")] + public async Task ZeroAsStartNothingSubtracted_OffersFixer_VB(string type) + { + string source = $$""" + Public Class C + Public Sub M() + Dim x as {{TypeForVB(type)}} = {{InstantiateExpressionFromType_VB(type)}} + [|x.{{TestMethodFromType(type)}}(0, x.Length)|] + End Sub + End Class + """; + + string fixedSource = $$""" + Public Class C + Public Sub M() + Dim x as {{TypeForVB(type)}} = {{InstantiateExpressionFromType_VB(type)}} + x.{{TestMethodFromType(type)}}(0) + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, fixedSource); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Memory")] + public async Task AdditionalParenthesis_OffersFixer_VB(string type) + { + string source = $$""" + Public Class C + Public Sub M() + Dim x as {{TypeForVB(type)}} = {{InstantiateExpressionFromType_VB(type)}} + [|x.{{TestMethodFromType(type)}}(((1 + 2)) + 3, x.Length - 1 - 2 - 3)|] + End Sub + End Class + """; + + string fixedSource = $$""" + Public Class C + Public Sub M() + Dim x as {{TypeForVB(type)}} = {{InstantiateExpressionFromType_VB(type)}} + x.{{TestMethodFromType(type)}}(((1 + 2)) + 3) + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, fixedSource); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Memory")] + public async Task NestedConstantsSubtracted_OffersFixer_VB(string type) + { + string source = $$""" + Public Class C + Public Sub M() + Dim x as {{TypeForVB(type)}} = {{InstantiateExpressionFromType_VB(type)}} + [|x.{{TestMethodFromType(type)}}(1 + 2 - (3 + (4 - +5 - 6 + -7) + 8) - 9, x.Length - 1 - 2 + 3 + 4 - 5 - 6 - 7 + 8 + 9)|] + End Sub + End Class + """; + + string fixedSource = $$""" + Public Class C + Public Sub M() + Dim x as {{TypeForVB(type)}} = {{InstantiateExpressionFromType_VB(type)}} + x.{{TestMethodFromType(type)}}(1 + 2 - (3 + (4 - +5 - 6 + -7) + 8) - 9) + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task NamedArguments_OffersFixer_VB() + { + string source = $$""" + Public Class C + Public Sub M() + Dim x as System.Memory(Of Char) = System.Memory(Of Char).Empty + [|x.Slice(start:=1, length:=x.Length - 1)|] + End Sub + End Class + """; + + string fixedSource = $$""" + Public Class C + Public Sub M() + Dim x as System.Memory(Of Char) = System.Memory(Of Char).Empty + x.Slice(start:=1) + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task NamedArgumentsSwapped_OffersFixer_VB() + { + string source = $$""" + Public Class C + Public Sub M() + Dim x as System.Memory(Of Char) = System.Memory(Of Char).Empty + [|x.Slice(length:=x.Length - 1, start:=1)|] + End Sub + End Class + """; + + string fixedSource = $$""" + Public Class C + Public Sub M() + Dim x as System.Memory(Of Char) = System.Memory(Of Char).Empty + x.Slice(start:=1) + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, fixedSource); + } + + [Theory] + [InlineData("string")] + [InlineData("System.Memory")] + public async Task TriviaIsPreserved_OffersFixer_VB(string type) + { + string source = $$""" + Public Class C + Public Sub M() + Dim x as {{TypeForVB(type)}} = {{InstantiateExpressionFromType_VB(type)}} + ' reticulates the splines + [|x.{{TestMethodFromType(type)}}(1, x.Length - 1)|] + End Sub + End Class + """; + + string fixedSource = $$""" + Public Class C + Public Sub M() + Dim x as {{TypeForVB(type)}} = {{InstantiateExpressionFromType_VB(type)}} + ' reticulates the splines + x.{{TestMethodFromType(type)}}(1) + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, fixedSource); + } + + [Fact] + public async Task StringLiteralSubstring_OffersFixer_VB() + { + string source = $$""" + Public Class C + Public Sub M() + Dim x as String = [|"test".Substring(2, "test".Length - 2)|] + End Sub + End Class + """; + + string fixedSource = $$""" + Public Class C + Public Sub M() + Dim x as String = "test".Substring(2) + End Sub + End Class + """; + + await VerifyVB.VerifyCodeFixAsync(source, fixedSource); + } + + #region Helpers + private static string InstantiateExpressionFromType_CS(string type) + { + return type switch + { + "string" => "string.Empty", + "System.Span" => "System.Span.Empty", + "System.ReadOnlySpan" => "System.ReadOnlySpan.Empty", + "System.Memory" => "System.Memory.Empty", + _ => throw new NotImplementedException() + }; + } + + private static string InstantiateExpressionFromType_VB(string type) + { + return type switch + { + "string" => "String.Empty", + "System.Memory" => "System.Memory(Of Char).Empty", + _ => throw new NotImplementedException() + }; + } + + private static string TypeForVB(string type) + { + return type switch + { + "string" => "String", + "System.Memory" => "System.Memory(Of Char)", + _ => throw new NotImplementedException() + }; + } + + private static string TestMethodFromType(string type) + { + switch (type) + { + case "string": + return "Substring"; + default: + return "Slice"; + } + } + #endregion + } +} diff --git a/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt b/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt index 34ae27b448..38330d36cb 100644 --- a/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt +++ b/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt @@ -17,7 +17,7 @@ Security: CA2100-CA2153, CA2300-CA2330, CA3000-CA3147, CA5300-CA5405 Usage: CA1801, CA1806, CA1816, CA2200-CA2209, CA2211-CA2261 Naming: CA1700-CA1727 Interoperability: CA1400-CA1422 -Maintainability: CA1500-CA1513 +Maintainability: CA1500-CA1514 Reliability: CA9998-CA9999, CA2000-CA2021 Documentation: CA1200-CA1200