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
+
+
+
+
+ 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 redundant length argument
+
+
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
+
+
+
+
+ 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 redundant length argument
+
+
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
+
+
+
+
+ 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 redundant length argument
+
+
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
+
+
+
+
+ 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 redundant length argument
+
+
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
+
+
+
+
+ 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 redundant length argument
+
+
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
+
+
+
+
+ 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 redundant length argument
+
+
'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
+
+
+
+
+ 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 redundant length argument
+
+
'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
+
+
+
+
+ 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 redundant length argument
+
+
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
+
+
+
+
+ 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 redundant length argument
+
+
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
+
+
+
+
+ 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 redundant length argument
+
+
Возможно несколько перечислений коллекции 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
+
+
+
+
+ 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 redundant length argument
+
+
'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
+
+
+
+
+ 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 redundant length argument
+
+
“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
+
+
+
+
+ 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 redundant length argument
+
+
'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