From ba6d78d95a254078fbca3da1aacd4d0e289f2eeb Mon Sep 17 00:00:00 2001 From: NewellClark Date: Mon, 25 Jan 2021 13:24:02 -0500 Subject: [PATCH 01/18] Add required files --- .../Core/AnalyzerReleases.Unshipped.md | 5 +++ .../MicrosoftNetCoreAnalyzersResources.resx | 9 ++++ .../Runtime/UseSpanBasedStringConcat.Fixer.cs | 21 +++++++++ .../Runtime/UseSpanBasedStringConcat.cs | 43 +++++++++++++++++++ .../Runtime/UseSpanBasedStringConcatTests.cs | 20 +++++++++ .../DiagnosticCategoryAndIdRanges.txt | 2 +- 6 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs create mode 100644 src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs create mode 100644 src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs diff --git a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md index 64372afc0b..bf76e5ec66 100644 --- a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md +++ b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md @@ -1,5 +1,10 @@ ; Please do not edit this file manually, it should only be updated through code fix application. +### New Rules +Rule ID | Category | Severity | Notes +--------|----------|----------|------- +CA1841 | Performance | Info | UseSpanBasedStringConcat, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1841) + ### Removed Rules Rule ID | Category | Severity | Notes diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx index 0b8e62a9ba..af0257a751 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx @@ -1510,4 +1510,13 @@ and all other platforms This call site is reachable on: 'windows' 10.0.2000 and later, and all other platforms + + description + + + message + + + title + \ No newline at end of file diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs new file mode 100644 index 0000000000..4580f730cf --- /dev/null +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Immutable; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; + +namespace Microsoft.NetCore.Analyzers.Runtime +{ + [ExportCodeFixProvider(LanguageNames.CSharp, LanguageNames.VisualBasic)] + public sealed class UseSpanBasedStringConcatFixer : CodeFixProvider + { + public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(UseSpanBasedStringConcat.RuleId); + + public override Task RegisterCodeFixesAsync(CodeFixContext context) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs new file mode 100644 index 0000000000..92a8f76024 --- /dev/null +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Immutable; +using Analyzer.Utilities; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Resx = Microsoft.NetCore.Analyzers.MicrosoftNetCoreAnalyzersResources; + +namespace Microsoft.NetCore.Analyzers.Runtime +{ + [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] + public sealed class UseSpanBasedStringConcat : DiagnosticAnalyzer + { + internal const string RuleId = "CA1841"; + + private static readonly LocalizableString s_localizableTitle = CreateResource(nameof(Resx.UseSpanBasedStringConcatTitle)); + private static readonly LocalizableString s_localizableMessage = CreateResource(nameof(Resx.UseSpanBasedStringConcatMessage)); + private static readonly LocalizableString s_localizableDescription = CreateResource(nameof(Resx.UseSpanBasedStringConcatDescription)); + + internal static readonly DiagnosticDescriptor Rule = DiagnosticDescriptorHelper.Create( + RuleId, + s_localizableTitle, + s_localizableMessage, + DiagnosticCategory.Performance, + RuleLevel.IdeSuggestion, + s_localizableDescription, + isPortedFxCopRule: false, + isDataflowRule: false); + + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) + { + throw new NotImplementedException(); + } + + private static LocalizableString CreateResource(string resourceName) + { + return new LocalizableResourceString(resourceName, Resx.ResourceManager, typeof(Resx)); + } + } +} diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs new file mode 100644 index 0000000000..8f68eb73f0 --- /dev/null +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Text; +using Xunit; + +using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< + Microsoft.NetCore.Analyzers.Runtime.UseSpanBasedStringConcat, + Microsoft.NetCore.Analyzers.Runtime.UseSpanBasedStringConcatFixer>; +using VerifyVB = Test.Utilities.VisualBasicCodeFixVerifier< + Microsoft.NetCore.Analyzers.Runtime.UseSpanBasedStringConcat, + Microsoft.NetCore.Analyzers.Runtime.UseSpanBasedStringConcatFixer>; + +namespace Microsoft.NetCore.Analyzers.Runtime.UnitTests +{ + public class UseSpanBasedStringConcatTests + { + } +} diff --git a/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt b/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt index 94d4940230..6847720374 100644 --- a/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt +++ b/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt @@ -12,7 +12,7 @@ Design: CA2210, CA1000-CA1070 Globalization: CA2101, CA1300-CA1310 Mobility: CA1600-CA1601 -Performance: HA, CA1800-CA1838 +Performance: HA, CA1800-CA1841 Security: CA2100-CA2153, CA2300-CA2330, CA3000-CA3147, CA5300-CA5403 Usage: CA1801, CA1806, CA1816, CA2200-CA2209, CA2211-CA2249 Naming: CA1700-CA1726 From 42107f2320ea1a566a9b319d45819dba4da40696 Mon Sep 17 00:00:00 2001 From: NewellClark Date: Sun, 31 Jan 2021 11:01:45 -0500 Subject: [PATCH 02/18] Common cases --- .../CSharpUseSpanBasedStringConcat.Fixer.cs | 55 ++ .../Runtime/CSharpUseSpanBasedStringConcat.cs | 19 + .../Runtime/UseSpanBasedStringConcat.Fixer.cs | 165 +++- .../Runtime/UseSpanBasedStringConcat.cs | 235 +++++- .../MicrosoftNetCoreAnalyzersResources.cs.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.de.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.es.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.fr.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.it.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.ja.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.ko.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.pl.xlf | 15 + ...crosoftNetCoreAnalyzersResources.pt-BR.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.ru.xlf | 15 + .../MicrosoftNetCoreAnalyzersResources.tr.xlf | 15 + ...osoftNetCoreAnalyzersResources.zh-Hans.xlf | 15 + ...osoftNetCoreAnalyzersResources.zh-Hant.xlf | 15 + .../Runtime/UseSpanBasedStringConcatTests.cs | 719 +++++++++++++++++- .../BasicUseSpanBasedStringConcat.Fixer.vb | 65 ++ .../Runtime/BasicUseSpanBasedStringConcat.vb | 18 + src/Utilities/Compiler/WellKnownTypeNames.cs | 1 + 21 files changed, 1457 insertions(+), 15 deletions(-) create mode 100644 src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpUseSpanBasedStringConcat.Fixer.cs create mode 100644 src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpUseSpanBasedStringConcat.cs create mode 100644 src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.Fixer.vb create mode 100644 src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.vb diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpUseSpanBasedStringConcat.Fixer.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpUseSpanBasedStringConcat.Fixer.cs new file mode 100644 index 0000000000..23e54c8576 --- /dev/null +++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpUseSpanBasedStringConcat.Fixer.cs @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Operations; +using Microsoft.NetCore.Analyzers.Runtime; + +namespace Microsoft.NetCore.CSharp.Analyzers.Runtime +{ + [ExportCodeFixProvider(LanguageNames.CSharp)] + public sealed class CSharpUseSpanBasedStringConcatFixer : UseSpanBasedStringConcatFixer + { + private protected override SyntaxNode ReplaceInvocationMethodName(SyntaxGenerator generator, SyntaxNode invocationSyntax, string newName) + { + var memberAccessSyntax = (MemberAccessExpressionSyntax)((InvocationExpressionSyntax)invocationSyntax).Expression; + var oldNameSyntax = memberAccessSyntax.Name; + var newNameSyntax = generator.IdentifierName(newName).WithTriviaFrom(oldNameSyntax); + return invocationSyntax.ReplaceNode(oldNameSyntax, newNameSyntax); + } + + private protected override SyntaxToken GetOperatorToken(IBinaryOperation binaryOperation) + { + var syntax = (BinaryExpressionSyntax)binaryOperation.Syntax; + return syntax.OperatorToken; + } + + private protected override bool IsSystemNamespaceImported(IReadOnlyList namespaceImports) + { + foreach (var import in namespaceImports) + { + if (import is UsingDirectiveSyntax { Name: IdentifierNameSyntax { Identifier: { ValueText: nameof(System) } } }) + return true; + } + return false; + } + + private protected override bool IsNamedArgument(IArgumentOperation argument) + { + var node = (ArgumentSyntax)argument.Syntax; + return node.NameColon is not null; + } + + private protected override SyntaxNode CreateConditionalToStringInvocation(SyntaxNode receiverExpression) + { + var expression = (ExpressionSyntax)receiverExpression; + var memberBindingExpression = SyntaxFactory.MemberBindingExpression(SyntaxFactory.IdentifierName(ToStringName)); + var toStringInvocationExpression = SyntaxFactory.InvocationExpression(memberBindingExpression); + return SyntaxFactory.ConditionalAccessExpression(expression, toStringInvocationExpression); + } + } +} diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpUseSpanBasedStringConcat.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpUseSpanBasedStringConcat.cs new file mode 100644 index 0000000000..e62e776911 --- /dev/null +++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpUseSpanBasedStringConcat.cs @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; +using Microsoft.NetCore.Analyzers.Runtime; + +namespace Microsoft.NetCore.CSharp.Analyzers.Runtime +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public sealed class CSharpUseSpanBasedStringConcat : UseSpanBasedStringConcat + { + private protected override bool IsStringConcatOperation(IBinaryOperation binaryOperation) + { + return binaryOperation.OperatorKind == BinaryOperatorKind.Add && + binaryOperation.Type.SpecialType == SpecialType.System_String; + } + } +} diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs index 4580f730cf..0b1c09cbdc 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs @@ -1,21 +1,176 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Collections.Immutable; +using System.Linq; +using System.Threading; using System.Threading.Tasks; +using Analyzer.Utilities; +using Analyzer.Utilities.Extensions; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Operations; + +using Resx = Microsoft.NetCore.Analyzers.MicrosoftNetCoreAnalyzersResources; +using RequiredSymbols = Microsoft.NetCore.Analyzers.Runtime.UseSpanBasedStringConcat.RequiredSymbols; namespace Microsoft.NetCore.Analyzers.Runtime { - [ExportCodeFixProvider(LanguageNames.CSharp, LanguageNames.VisualBasic)] - public sealed class UseSpanBasedStringConcatFixer : CodeFixProvider + public abstract class UseSpanBasedStringConcatFixer : CodeFixProvider { - public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(UseSpanBasedStringConcat.RuleId); + private const string AsSpanName = nameof(MemoryExtensions.AsSpan); + private const string AsSpanStartParameterName = "start"; + private protected const string ToStringName = nameof(ToString); + + private protected abstract SyntaxNode ReplaceInvocationMethodName(SyntaxGenerator generator, SyntaxNode invocationSyntax, string newName); + + private protected abstract SyntaxToken GetOperatorToken(IBinaryOperation binaryOperation); + + private protected abstract bool IsSystemNamespaceImported(IReadOnlyList namespaceImports); + + private protected abstract bool IsNamedArgument(IArgumentOperation argument); + + /// + /// Invokes on the specified expression using the Elvis operator. + /// + private protected abstract SyntaxNode CreateConditionalToStringInvocation(SyntaxNode receiverExpression); + + public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(UseSpanBasedStringConcat.RuleId); - public override Task RegisterCodeFixesAsync(CodeFixContext context) + public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) { - throw new NotImplementedException(); + var document = context.Document; + var diagnostic = context.Diagnostics.First(); + var cancellationToken = context.CancellationToken; + var model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var compilation = model.Compilation; + SyntaxNode root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + + // Bail out early if we're missing anything we need. + if (!RequiredSymbols.TryGetSymbols(compilation, out var symbols)) + return; + if (root.FindNode(context.Span, getInnermostNodeForTie: true) is not SyntaxNode concatExpressionSyntax) + return; + if (model.GetOperation(concatExpressionSyntax, cancellationToken) is not IBinaryOperation concatOperation || concatOperation.OperatorKind != symbols.ConcatOperatorKind) + return; + + var operands = UseSpanBasedStringConcat.FlattenBinaryOperationChain(concatOperation); + // Bail out if we don't have a long enough span-based string.Concat overload. + if (!symbols.TryGetRoscharConcatMethodWithArity(operands.Length, out IMethodSymbol? roscharConcatMethod)) + return; + + var codeAction = CodeAction.Create( + Resx.UseSpanBasedStringConcatTitle, + FixConcatOperationChain, + Resx.UseSpanBasedStringConcatTitle); + context.RegisterCodeFix(codeAction, diagnostic); + + async Task FixConcatOperationChain(CancellationToken cancellationToken) + { + RoslynDebug.Assert(roscharConcatMethod is not null); + + var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); + var generator = editor.Generator; + + // Save leading and trailing trivia so it can be attached to the outside of the 'string.Concat(...)' invocation expression. + var leadingTrivia = operands.First().Syntax.GetLeadingTrivia(); + var trailingTrivia = operands.Last().Syntax.GetTrailingTrivia(); + + SyntaxNode stringTypeNameSyntax = generator.TypeExpressionForStaticMemberAccess(symbols.StringType); + SyntaxNode concatMemberAccessSyntax = generator.MemberAccessExpression(stringTypeNameSyntax, roscharConcatMethod.Name); + var arguments = GenerateConcatMethodArguments(symbols, generator, operands); + SyntaxNode concatMethodInvocationSyntax = generator.InvocationExpression(concatMemberAccessSyntax, arguments) + .WithLeadingTrivia(leadingTrivia) + .WithTrailingTrivia(trailingTrivia); + var newRoot = generator.ReplaceNode(root, concatExpressionSyntax, concatMethodInvocationSyntax); + + // Make sure 'System' namespace is imported. + if (!IsSystemNamespaceImported(generator.GetNamespaceImports(newRoot))) + { + var systemNamespaceImport = generator.NamespaceImportDeclaration(nameof(System)); + newRoot = generator.AddNamespaceImports(newRoot, systemNamespaceImport); + } + + editor.ReplaceNode(root, newRoot); + return editor.GetChangedDocument(); + } + } + + private ImmutableArray GenerateConcatMethodArguments(in RequiredSymbols symbols, SyntaxGenerator generator, ImmutableArray operands) + { + var builder = ImmutableArray.CreateBuilder(operands.Length); + foreach (IOperation operand in operands) + { + // Convert 'Substring' invocations into 'AsSpan' invocations. + if (WalkDownImplicitConversion(operand) is IInvocationOperation invocation && symbols.IsAnySubstringMethod(invocation.TargetMethod)) + { + SyntaxNode newInvocationSyntax = invocation.Syntax; + + // Convert 'Substring' named-arguments to equivalent 'AsSpan' named arguments. + IArgumentOperation? namedStartIndexArgument = GetNamedStartIndexArgumentOrDefault(symbols, invocation); + if (namedStartIndexArgument is not null) + { + SyntaxNode renamedSubstringArgumentSyntax = generator.Argument(AsSpanStartParameterName, RefKind.None, namedStartIndexArgument.Value.Syntax); + newInvocationSyntax = generator.ReplaceNode(newInvocationSyntax, namedStartIndexArgument.Syntax, renamedSubstringArgumentSyntax); + } + + // Replace 'Substring' identifier with 'AsSpan', leaving the rest of the node (including trivia) intact. + newInvocationSyntax = ReplaceInvocationMethodName(generator, newInvocationSyntax, AsSpanName); + builder.Add(generator.Argument(newInvocationSyntax)); + } + else + { + IOperation value = WalkDownImplicitConversion(operand); + if (value.Type.SpecialType == SpecialType.System_String) + { + builder.Add(generator.Argument(value.Syntax)); + } + else + { + SyntaxNode newValueSyntax; + if (value.Type.IsReferenceTypeOrNullableValueType()) + { + newValueSyntax = CreateConditionalToStringInvocation(value.Syntax); + } + else + { + SyntaxNode toStringMemberAccessSyntax = generator.MemberAccessExpression(value.Syntax.WithoutTrivia(), ToStringName); + newValueSyntax = generator.InvocationExpression(toStringMemberAccessSyntax, Array.Empty()) + .WithTriviaFrom(value.Syntax); + } + builder.Add(generator.Argument(newValueSyntax)); + } + } + } + + builder[0] = builder[0].WithoutLeadingTrivia(); + builder[^1] = builder[^1].WithoutTrailingTrivia(); + return builder.MoveToImmutable(); + + // If the 'startIndex' argument was passed using named-arguments, return it. + // Otherwise, return null. + IArgumentOperation? GetNamedStartIndexArgumentOrDefault(in RequiredSymbols symbols, IInvocationOperation substringInvocation) + { + RoslynDebug.Assert(symbols.IsAnySubstringMethod(substringInvocation.TargetMethod)); + foreach (var argument in substringInvocation.Arguments) + { + if (IsNamedArgument(argument) && symbols.IsAnySubstringStartIndexParameter(argument.Parameter)) + return argument; + } + return null; + } + + static IOperation WalkDownImplicitConversion(IOperation operand) + { + if (operand is IConversionOperation { Type: { SpecialType: SpecialType.System_Object or SpecialType.System_String }, IsImplicit: true } conversion) + return conversion.Operand; + return operand; + } } + + public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; } } diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs index 92a8f76024..6fe7fc6fd0 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs @@ -3,14 +3,19 @@ using System; using System.Collections.Immutable; using Analyzer.Utilities; +using Analyzer.Utilities.Extensions; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; using Resx = Microsoft.NetCore.Analyzers.MicrosoftNetCoreAnalyzersResources; +using Analyzer.Utilities.PooledObjects; +using System.Collections.Generic; +using System.Linq; +using System.Diagnostics.CodeAnalysis; namespace Microsoft.NetCore.Analyzers.Runtime { - [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] - public sealed class UseSpanBasedStringConcat : DiagnosticAnalyzer + public abstract class UseSpanBasedStringConcat : DiagnosticAnalyzer { internal const string RuleId = "CA1841"; @@ -28,11 +33,231 @@ public sealed class UseSpanBasedStringConcat : DiagnosticAnalyzer isPortedFxCopRule: false, isDataflowRule: false); - public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); + private const string SubstringName = nameof(string.Substring); + private const string AsSpanName = nameof(MemoryExtensions.AsSpan); + private const string ConcatName = nameof(string.Concat); - public override void Initialize(AnalysisContext context) + private protected abstract bool IsStringConcatOperation(IBinaryOperation binaryOperation); + + public sealed override ImmutableArray SupportedDiagnostics => 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 RequiredSymbols symbols)) + return; + + context.RegisterOperationBlockStartAction(OnOperationBlockStart); + + void OnOperationBlockStart(OperationBlockStartAnalysisContext context) + { + var rootConcatOperations = PooledConcurrentSet.GetInstance(); + + context.RegisterOperationAction(PopulateRootConcatOperations, OperationKind.Binary); + context.RegisterOperationBlockEndAction(ReportDiagnosticsOnRootConcatOperationsWithSubstringCalls); + + void PopulateRootConcatOperations(OperationAnalysisContext context) + { + var binary = (IBinaryOperation)context.Operation; + if (binary.OperatorKind != symbols.ConcatOperatorKind) + return; + + var topBinaryOperation = WalkUpBinaryOperationChain(binary); + if (!IsStringConcatOperation(topBinaryOperation)) + return; + + rootConcatOperations.Add(topBinaryOperation); + } + + void ReportDiagnosticsOnRootConcatOperationsWithSubstringCalls(OperationBlockAnalysisContext context) + { + foreach (var root in rootConcatOperations) + { + var chain = FlattenBinaryOperationChain(root); + if (chain.Any(IsAnySubstringInvocation) && symbols.TryGetRoscharConcatMethodWithArity(chain.Length, out var _)) + { + context.ReportDiagnostic(root.CreateDiagnostic(Rule)); + } + } + rootConcatOperations.Free(context.CancellationToken); + } + } + + bool IsAnySubstringInvocation(IOperation operation) + { + return operation.WalkDownConversion() is IInvocationOperation invocation && + (invocation.TargetMethod.Equals(symbols.Substring1, SymbolEqualityComparer.Default) || + invocation.TargetMethod.Equals(symbols.Substring2, SymbolEqualityComparer.Default)); + } + } + + private static IBinaryOperation WalkUpBinaryOperationChain(IBinaryOperation operation) { - throw new NotImplementedException(); + while (operation.Parent is IBinaryOperation parentBinaryOperation && + parentBinaryOperation.OperatorKind == operation.OperatorKind) + { + operation = parentBinaryOperation; + } + + return operation; + } + + internal static ImmutableArray FlattenBinaryOperationChain(IBinaryOperation root) + { + var stack = new Stack(); + var builder = ImmutableArray.CreateBuilder(); + GoLeft(root); + + while (stack.Count != 0) + { + var current = stack.Pop(); + + if (current.LeftOperand is not IBinaryOperation leftBinary || leftBinary.OperatorKind != root.OperatorKind) + { + builder.Add(current.LeftOperand); + } + + if (current.RightOperand is not IBinaryOperation rightBinary || rightBinary.OperatorKind != root.OperatorKind) + { + builder.Add(current.RightOperand); + } + else + { + GoLeft(rightBinary); + } + } + + return builder.ToImmutable(); + + void GoLeft(IBinaryOperation operation) + { + IBinaryOperation? current = operation; + while (current is not null && current.OperatorKind == root.OperatorKind) + { + stack.Push(current); + current = current.LeftOperand as IBinaryOperation; + } + } + } + + // Use readonly struct instead of record type to save on allocations, since it's not passed by-value. + // We aren't comparing these. +#pragma warning disable CA1815 // Override equals and operator equals on value types + internal readonly struct RequiredSymbols +#pragma warning restore CA1815 // Override equals and operator equals on value types + { + private RequiredSymbols( + INamedTypeSymbol stringType, INamedTypeSymbol roscharType, + IMethodSymbol substring1, IMethodSymbol substring2, + IMethodSymbol asSpan1, IMethodSymbol asSpan2, + BinaryOperatorKind concatOperatorKind) + { + StringType = stringType; + RoscharType = roscharType; + Substring1 = substring1; + Substring2 = substring2; + AsSpan1 = asSpan1; + AsSpan2 = asSpan2; + ConcatOperatorKind = concatOperatorKind; + + RoslynDebug.Assert( + StringType is not null && RoscharType is not null && + Substring1 is not null && Substring2 is not null && + AsSpan1 is not null && AsSpan2 is not null); + } + + public static bool TryGetSymbols(Compilation compilation, out RequiredSymbols symbols) + { + var stringType = compilation.GetSpecialType(SpecialType.System_String); + var roscharType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemReadOnlySpan1) + ?.Construct(compilation.GetSpecialType(SpecialType.System_Char)); + var memoryExtensionsType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemMemoryExtensions); + + if (roscharType is null || memoryExtensionsType is null) + { + symbols = default; + return false; + } + + var intParamInfo = ParameterInfo.GetParameterInfo(compilation.GetSpecialType(SpecialType.System_Int32)); + var stringParamInfo = ParameterInfo.GetParameterInfo(stringType); + + var substringMembers = stringType.GetMembers(SubstringName).OfType(); + var substring1 = substringMembers.GetFirstOrDefaultMemberWithParameterInfos(intParamInfo); + var substring2 = substringMembers.GetFirstOrDefaultMemberWithParameterInfos(intParamInfo, intParamInfo); + + var asSpanMembers = memoryExtensionsType.GetMembers(AsSpanName).OfType(); + var asSpan1 = asSpanMembers.GetFirstOrDefaultMemberWithParameterInfos(stringParamInfo, intParamInfo)?.ReduceExtensionMethod(stringType); + var asSpan2 = asSpanMembers.GetFirstOrDefaultMemberWithParameterInfos(stringParamInfo, intParamInfo, intParamInfo)?.ReduceExtensionMethod(stringType); + + if (substring1 is null || substring2 is null || asSpan1 is null || asSpan2 is null) + { + symbols = default; + return false; + } + + var concatOperatorKind = compilation.Language switch + { + LanguageNames.CSharp => BinaryOperatorKind.Add, + LanguageNames.VisualBasic => BinaryOperatorKind.Concatenate, + _ => BinaryOperatorKind.None + }; + + symbols = new RequiredSymbols( + stringType, roscharType, + substring1, substring2, + asSpan1, asSpan2, + concatOperatorKind); + return true; + } + + public INamedTypeSymbol StringType { get; } + public INamedTypeSymbol RoscharType { get; } + public IMethodSymbol Substring1 { get; } + public IMethodSymbol Substring2 { get; } + public IMethodSymbol AsSpan1 { get; } + public IMethodSymbol AsSpan2 { get; } + public BinaryOperatorKind ConcatOperatorKind { get; } + + public IMethodSymbol? GetAsSpanEquivalent(IMethodSymbol? substringMethod) + { + if (SymbolEqualityComparer.Default.Equals(substringMethod, Substring1)) + return AsSpan1; + if (SymbolEqualityComparer.Default.Equals(substringMethod, Substring2)) + return AsSpan2; + return null; + } + + public bool IsAnySubstringMethod(IMethodSymbol? method) + { + return SymbolEqualityComparer.Default.Equals(method, Substring1) || + SymbolEqualityComparer.Default.Equals(method, Substring2); + } + + public bool IsAnySubstringStartIndexParameter(IParameterSymbol? parameter) + { + return SymbolEqualityComparer.Default.Equals(parameter, Substring1.Parameters.First()) || + SymbolEqualityComparer.Default.Equals(parameter, Substring2.Parameters.First()); + } + + public bool TryGetRoscharConcatMethodWithArity(int arity, [NotNullWhen(true)] out IMethodSymbol? concatMethod) + { + var roscharParamInfo = ParameterInfo.GetParameterInfo(RoscharType); + var argumentList = new ParameterInfo[arity]; + for (int index = 0; index < arity; index++) + argumentList[index] = roscharParamInfo; + + concatMethod = StringType.GetMembers(ConcatName) + .OfType() + .GetFirstOrDefaultMemberWithParameterInfos(argumentList); + return concatMethod is not null; + } } private static LocalizableString CreateResource(string resourceName) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf index 714e9e49b4..d6dbb80219 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf @@ -2237,6 +2237,21 @@ Pokud je to možné, zvažte použití řízení přístupu Azure na základě role namísto sdíleného přístupového podpisu (SAS). Pokud i přesto potřebujete používat sdílený přístupový podpis, zadejte SharedAccessProtocol.HttpsOnly. + + description + description + + + + message + message + + + + title + title + + ValueTasks returned from member invocations are intended to be directly awaited. Attempts to consume a ValueTask multiple times or to directly access one's result before it's known to be completed may result in an exception or corruption. Ignoring such a ValueTask is likely an indication of a functional bug and may degrade performance. Hodnoty ValueTask vrácené z vyvolání členů jsou určené k tomu, aby byly přímo očekávané. Pokusy o vícenásobné využití ValueTask nebo o přímý přístup k výsledku úkolu před tím, než je známo, že je dokončený, můžou způsobit výjimku nebo poškození. Ignorování takové hodnoty ValueTask je pravděpodobně indikací funkční chyby a může snížit výkon. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf index 6e0f153a85..74a68f8bab 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf @@ -2237,6 +2237,21 @@ Erwägen Sie (sofern möglich) die Verwendung der rollenbasierten Zugriffssteuerung von Azure anstelle einer Shared Access Signature (SAS). Wenn Sie weiterhin eine SAS benötigen, verwenden Sie "SharedAccessProtocol.HttpsOnly". + + description + description + + + + message + message + + + + title + title + + ValueTasks returned from member invocations are intended to be directly awaited. Attempts to consume a ValueTask multiple times or to directly access one's result before it's known to be completed may result in an exception or corruption. Ignoring such a ValueTask is likely an indication of a functional bug and may degrade performance. Von Memberaufrufen zurückgegebene ValueTasks sind so konzipiert, dass sie direkt erwartet werden sollten. Versuche, einen ValueTask mehrmals zu nutzen oder direkt auf das Ergebnis zuzugreifen, bevor er offiziell abgeschlossen wurde, können zu einer Ausnahme oder zur Beschädigung führen. Das Ignorieren eines solchen ValueTask ist wahrscheinlich ein Hinweis auf einen Funktionsfehler und kann zu Leistungseinbußen führen. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf index 8c66938819..e2d072c08e 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf @@ -2237,6 +2237,21 @@ Considere la posibilidad de usar el control de acceso basado en rol de Azure en lugar de una firma de acceso compartido (SAS), si es posible. Si tiene que usar una firma de acceso compartido, especifique SharedAccessProtocol.HttpsOnly. + + description + description + + + + message + message + + + + title + title + + ValueTasks returned from member invocations are intended to be directly awaited. Attempts to consume a ValueTask multiple times or to directly access one's result before it's known to be completed may result in an exception or corruption. Ignoring such a ValueTask is likely an indication of a functional bug and may degrade performance. Los elementos ValueTask devueltos por invocaciones de miembros están diseñados para esperarlos directamente. Los intentos de consumir un ValueTask varias veces o de acceder directamente al resultado de uno antes de que se sepa que se ha completado pueden producir una excepción o daños en los datos. La omisión de un ValueTask en este caso indica probablemente un error funcional y puede degradar el rendimiento. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf index 71a0c15261..20c591156f 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf @@ -2237,6 +2237,21 @@ Si possible, utilisez le contrôle d'accès en fonction du rôle d'Azure à la place d'une signature d'accès partagé. Si vous devez quand même utiliser une signature d'accès partagé, spécifiez SharedAccessProtocol.HttpsOnly. + + description + description + + + + message + message + + + + title + title + + ValueTasks returned from member invocations are intended to be directly awaited. Attempts to consume a ValueTask multiple times or to directly access one's result before it's known to be completed may result in an exception or corruption. Ignoring such a ValueTask is likely an indication of a functional bug and may degrade performance. Les ValueTasks retournés par les appels de membres sont censés être directement attendus. Les tentatives de consommation d'un ValueTask à plusieurs reprises ou d'accès direct à son résultat avant la fin de l'opération peuvent entraîner une exception ou une altération des données. Quand un ValueTask est ainsi ignoré, cela indique probablement un bogue fonctionnel et un risque de dégradation du niveau de performance. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf index 61adbbacb6..477cf058b9 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf @@ -2237,6 +2237,21 @@ Se possibile, provare a usare il controllo degli accessi in base al ruolo di Azure, invece della firma di accesso condiviso. Se è necessario usare una firma di accesso condiviso, specificare SharedAccessProtocol.HttpsOnly. + + description + description + + + + message + message + + + + title + title + + ValueTasks returned from member invocations are intended to be directly awaited. Attempts to consume a ValueTask multiple times or to directly access one's result before it's known to be completed may result in an exception or corruption. Ignoring such a ValueTask is likely an indication of a functional bug and may degrade performance. Gli elementi ValueTask restituiti da chiamate ai membri devono essere usati direttamente tramite await. Se si prova a utilizzare più volte un elemento ValueTask oppure ad accedere direttamente al risultato di un oggetto prima che sia realmente completato, potrebbe verificarsi eccezioni o danneggiamenti dei dati. Un elemento ValueTask ignorato è probabilmente indicativo di un bug funzionale e potrebbe influire negativamente sulle prestazioni. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf index 4faf2ebe51..4b4dbc362d 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf @@ -2237,6 +2237,21 @@ 可能な場合は、Shared Access Signature (SAS) の代わりに、Azure のロールベースのアクセス制御を使用することを検討してください。依然として SAS を使用する必要がある場合は、SharedAccessProtocol.HttpsOnly を指定します。 + + description + description + + + + message + message + + + + title + title + + ValueTasks returned from member invocations are intended to be directly awaited. Attempts to consume a ValueTask multiple times or to directly access one's result before it's known to be completed may result in an exception or corruption. Ignoring such a ValueTask is likely an indication of a functional bug and may degrade performance. メンバー呼び出しから返される ValueTask は、直接待機されるよう意図されています。1 つの ValueTask を複数回使用しようとしたり、完了が判明する前に結果に直接アクセスしようとしたりすると、例外または破損が発生する可能性があります。このような ValueTask を無視することは、機能的なバグを示していることが多く、パフォーマンスを低下させる可能性があります。 diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf index 42e160e005..8c22daf246 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf @@ -2237,6 +2237,21 @@ 가능한 경우 SAS(공유 액세스 서명) 대신 Azure의 역할 기반 액세스 제어를 사용하는 것이 좋습니다. 계속 SAS를 사용해야 하는 경우 SharedAccessProtocol.HttpsOnly를 지정하세요. + + description + description + + + + message + message + + + + title + title + + ValueTasks returned from member invocations are intended to be directly awaited. Attempts to consume a ValueTask multiple times or to directly access one's result before it's known to be completed may result in an exception or corruption. Ignoring such a ValueTask is likely an indication of a functional bug and may degrade performance. 멤버 호출에서 반환되는 ValueTasks는 바로 대기되기 위한 것입니다. ValueTask를 여러 번 사용하거나, 완료가 확인되기 전에 해당 결과에 바로 액세스하면 예외나 손상이 발생할 수 있습니다. 이러한 ValueTask를 무시하면 기능 버그가 발생하거나 성능이 저하될 수 있습니다. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf index dd9ad6db14..4b9053b0a0 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf @@ -2237,6 +2237,21 @@ Jeśli to możliwe, rozważ użycie kontroli dostępu opartej na rolach platformy Azure zamiast sygnatury dostępu współdzielonego (SAS). Jeśli nadal chcesz używać sygnatury SAS, określ właściwość SharedAccessProtocol.HttpsOnly. + + description + description + + + + message + message + + + + title + title + + ValueTasks returned from member invocations are intended to be directly awaited. Attempts to consume a ValueTask multiple times or to directly access one's result before it's known to be completed may result in an exception or corruption. Ignoring such a ValueTask is likely an indication of a functional bug and may degrade performance. Elementy ValueTask zwracane przez wywołania elementów członkowskich są przeznaczone do bezpośredniego oczekiwania. Próby wielokrotnego użycia elementu ValueTask lub uzyskania bezpośredniego dostępu do wyniku elementu zanim jest wiadomo, że został on zakończony, mogą spowodować wystąpienie wyjątku lub uszkodzenia. Zignorowanie takiego elementu ValueTask prawdopodobnie wskazuje na usterkę funkcjonalną i może obniżyć wydajność. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf index f73645c162..f56187c75f 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf @@ -2237,6 +2237,21 @@ Se possível, considere usar o controle de acesso baseado em função do Azure em vez de uma SAS (Assinatura de Acesso Compartilhado). Se você ainda precisar usar uma SAS, especifique SharedAccessProtocol.HttpsOnly. + + description + description + + + + message + message + + + + title + title + + ValueTasks returned from member invocations are intended to be directly awaited. Attempts to consume a ValueTask multiple times or to directly access one's result before it's known to be completed may result in an exception or corruption. Ignoring such a ValueTask is likely an indication of a functional bug and may degrade performance. ValueTasks retornados de invocações de membro devem ser aguardados diretamente. Tentativas de consumir um ValueTask várias vezes ou de acessar diretamente um resultado antes que esteja sabidamente concluído podem resultar em exceção ou dano. Ignorar um ValueTask é provavelmente uma indicação de um bug funcional e pode degradar o desempenho. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf index 3b853eaf7e..5f75bc3e08 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf @@ -2237,6 +2237,21 @@ Если возможно, попробуйте использовать управление доступом на основе ролей Azure, а не подписанный URL-адрес (SAS). Если все-таки требуется использовать SAS, укажите SharedAccessProtocol.HttpsOnly. + + description + description + + + + message + message + + + + title + title + + ValueTasks returned from member invocations are intended to be directly awaited. Attempts to consume a ValueTask multiple times or to directly access one's result before it's known to be completed may result in an exception or corruption. Ignoring such a ValueTask is likely an indication of a functional bug and may degrade performance. ValueTask, возвращаемые в результате вызова членов, должны ожидаться напрямую. Попытки использовать ValueTask несколько раз или получить прямой доступ к результату, прежде чем он будет получен, могут привести к выдаче исключения или повреждению данных. Пропуск такого ValueTask, вероятно, свидетельствует о функциональной ошибке и может привести к снижению производительности. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf index e85849b540..40a280c256 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf @@ -2237,6 +2237,21 @@ Mümkünse Paylaşılan Erişim İmzası (SAS) yerine Azure'un rol tabanlı erişim denetimini kullanmayı düşünün. Yine de SAS kullanmanız gerekiyorsa, SharedAccessProtocol.HttpsOnly belirtin. + + description + description + + + + message + message + + + + title + title + + ValueTasks returned from member invocations are intended to be directly awaited. Attempts to consume a ValueTask multiple times or to directly access one's result before it's known to be completed may result in an exception or corruption. Ignoring such a ValueTask is likely an indication of a functional bug and may degrade performance. Üye çağırmalarından döndürülen ValueTask'lerin doğrudan beklenmesi amaçlanır. Bir ValueTask'ı birden çok kez kullanmak ya da tamamlandığı bilinmeden önce birinin sonucuna doğrudan erişmek, bir özel duruma veya bozulmaya neden olabilir. Bu tür bir ValueTask'ın yoksayılması, büyük olasılıkla bir işlev hatasının göstergesidir ve performansı düşürebilir. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf index 2548f9f6f3..72fb48bcba 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf @@ -2237,6 +2237,21 @@ 如果可能,请考虑使用 Azure 基于角色的访问控制,而不是共享访问签名(SAS)。如果仍需使用 SAS,请指定 SharedAccessProtocol.HttpsOnly。 + + description + description + + + + message + message + + + + title + title + + ValueTasks returned from member invocations are intended to be directly awaited. Attempts to consume a ValueTask multiple times or to directly access one's result before it's known to be completed may result in an exception or corruption. Ignoring such a ValueTask is likely an indication of a functional bug and may degrade performance. 应直接等待从成员调用返回的 ValueTask。尝试多次使用 ValueTask 或在已知完成前直接访问结果可能导致异常或损坏。 忽略此类 ValueTask 很可能表示存在功能 bug,可能会降低性能。 diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf index 1aebe6df07..3783ef55bb 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf @@ -2237,6 +2237,21 @@ 如果可行的話,請考慮從共用存取簽章 (SAS) 改為使用 Azure 的角色型存取控制。如果您仍需要使用 SAS,請指定 SharedAccessProtocol.HttpsOnly。 + + description + description + + + + message + message + + + + title + title + + ValueTasks returned from member invocations are intended to be directly awaited. Attempts to consume a ValueTask multiple times or to directly access one's result before it's known to be completed may result in an exception or corruption. Ignoring such a ValueTask is likely an indication of a functional bug and may degrade performance. 應直接等候從成員引動過程傳回的 ValueTask。若在已得知完成 ValueTask 之前,嘗試多次使用 ValueTask 或直接存取其結果,可能會導致發生例外狀況或損毀。忽略這類 ValueTask 可能表示發生功能性 Bug,而且可能會降低效能。 diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs index 8f68eb73f0..0d71a5a214 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs @@ -2,19 +2,728 @@ using System; using System.Collections.Generic; -using System.Text; +using System.Globalization; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Testing; using Xunit; using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< - Microsoft.NetCore.Analyzers.Runtime.UseSpanBasedStringConcat, - Microsoft.NetCore.Analyzers.Runtime.UseSpanBasedStringConcatFixer>; + Microsoft.NetCore.CSharp.Analyzers.Runtime.CSharpUseSpanBasedStringConcat, + Microsoft.NetCore.CSharp.Analyzers.Runtime.CSharpUseSpanBasedStringConcatFixer>; using VerifyVB = Test.Utilities.VisualBasicCodeFixVerifier< - Microsoft.NetCore.Analyzers.Runtime.UseSpanBasedStringConcat, - Microsoft.NetCore.Analyzers.Runtime.UseSpanBasedStringConcatFixer>; + Microsoft.NetCore.VisualBasic.Analyzers.Runtime.BasicUseSpanBasedStringConcat, + Microsoft.NetCore.VisualBasic.Analyzers.Runtime.BasicUseSpanBasedStringConcatFixer>; namespace Microsoft.NetCore.Analyzers.Runtime.UnitTests { public class UseSpanBasedStringConcatTests { + #region Reports Diagnostic + public static IEnumerable Data_SingleViolationInOneBlock_CS + { + get + { + yield return new[] + { + @"var _ = {|#0:foo.Substring(1) + bar|};", + @"var _ = string.Concat(foo.AsSpan(1), bar);" + }; + yield return new[] + { + @"var _ = {|#0:foo.Substring(1, 2) + bar|};", + @"var _ = string.Concat(foo.AsSpan(1, 2), bar);" + }; + yield return new[] + { + @"var _ = {|#0:foo + bar.Substring(1)|};", + @"var _ = string.Concat(foo, bar.AsSpan(1));" + }; + yield return new[] + { + @"var _ = {|#0:foo + bar.Substring(1) + baz|};", + @"var _ = string.Concat(foo, bar.AsSpan(1), baz);" + }; + yield return new[] + { + @"var _ = {|#0:foo.Substring(1) + bar.Substring(1) + baz.Substring(1)|};", + @"var _ = string.Concat(foo.AsSpan(1), bar.AsSpan(1), baz.AsSpan(1));" + }; + yield return new[] + { + @"var _ = {|#0:foo.Substring(1, 2) + bar + baz + baz.Substring(1, 2)|};", + @"var _ = string.Concat(foo.AsSpan(1, 2), bar, baz, baz.AsSpan(1, 2));" + }; + yield return new[] + { + @"Consume({|#0:foo + bar.Substring(1, 2)|});", + @"Consume(string.Concat(foo, bar.AsSpan(1, 2)));" + }; + yield return new[] + { + @"var _ = Fwd({|#0:foo.Substring(1) + bar|});", + @"var _ = Fwd(string.Concat(foo.AsSpan(1), bar));" + }; + yield return new[] + { + @"var _ = Fwd({|#0:foo.Substring(1) + bar.Substring(1)|});", + @"var _ = Fwd(string.Concat(foo.AsSpan(1), bar.AsSpan(1)));" + }; + } + } + + [Theory] + [MemberData(nameof(Data_SingleViolationInOneBlock_CS))] + public Task SingleViolationInOneBlock_ReportedAndFixed_CS(string testStatements, string fixedStatements) + { + var test = new VerifyCS.Test + { + TestCode = CSUsings + CSWithBody(testStatements), + FixedCode = CSUsings + CSWithBody(fixedStatements), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { VerifyCS.Diagnostic(Rule).WithLocation(0) } + }; + return test.RunAsync(); + } + + public static IEnumerable Data_SingleViolationInOneBlock_VB + { + get + { + yield return new[] + { + @"Dim s = {|#0:foo.Substring(1) & bar|}", + @"Dim s = String.Concat(foo.AsSpan(1), bar)" + }; + yield return new[] + { + @"Dim s = {|#0:foo.Substring(1, 2) & bar|}", + @"Dim s = String.Concat(foo.AsSpan(1, 2), bar)" + }; + yield return new[] + { + @"Dim s = {|#0:foo & bar.Substring(1)|}", + @"Dim s = String.Concat(foo, bar.AsSpan(1))" + }; + yield return new[] + { + @"Dim s = {|#0:foo & bar.Substring(1) & baz|}", + @"Dim s = String.Concat(foo, bar.AsSpan(1), baz)" + }; + yield return new[] + { + @"Dim s = {|#0:foo.Substring(1) & bar.Substring(1) & baz.Substring(1)|}", + @"Dim s = String.Concat(foo.AsSpan(1), bar.AsSpan(1), baz.AsSpan(1))" + }; + yield return new[] + { + @"Dim s = {|#0:foo.Substring(1, 2) & bar & baz & baz.Substring(1, 2)|}", + @"Dim s = String.Concat(foo.AsSpan(1, 2), bar, baz, baz.AsSpan(1, 2))" + }; + yield return new[] + { + @"Consume({|#0:foo & bar.Substring(1, 2)|})", + @"Consume(String.Concat(foo, bar.AsSpan(1, 2)))" + }; + yield return new[] + { + @"Dim s = Fwd({|#0:foo.Substring(1) & bar|})", + @"Dim s = Fwd(String.Concat(foo.AsSpan(1), bar))" + }; + yield return new[] + { + @"Dim s = Fwd({|#0:foo.Substring(1) & bar.Substring(1)|})", + @"Dim s = Fwd(String.Concat(foo.AsSpan(1), bar.AsSpan(1)))" + }; + } + } + + [Theory] + [MemberData(nameof(Data_SingleViolationInOneBlock_VB))] + public Task SingleViolationInOneBlock_ReportedAndFixed_VB(string testStatement, string fixedStatement) + { + var test = new VerifyVB.Test + { + TestCode = VBUsings + VBWithBody(testStatement), + FixedCode = VBUsings + VBWithBody(fixedStatement), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { VerifyVB.Diagnostic(Rule).WithLocation(0) } + }; + return test.RunAsync(); + } + + public static IEnumerable Data_MultipleViolationsInOneBlock_CS + { + get + { + yield return new object[] + { + @" +string alpha = {|#0:foo.Substring(1, 2) + bar.Substring(1) + baz|}; +string bravo = {|#1:foo + bar.Substring(3) + baz.Substring(1, 2)|}; +string charlie = {|#2:foo + bar + baz.Substring(1, 2)|}; +string delta = {|#3:foo.Substring(1) + bar.Substring(1) + baz.Substring(1, 2) + foo.Substring(1, 2)|};", + @" +string alpha = string.Concat(foo.AsSpan(1, 2), bar.AsSpan(1), baz); +string bravo = string.Concat(foo, bar.AsSpan(3), baz.AsSpan(1, 2)); +string charlie = string.Concat(foo, bar, baz.AsSpan(1, 2)); +string delta = string.Concat(foo.AsSpan(1), bar.AsSpan(1), baz.AsSpan(1, 2), foo.AsSpan(1, 2));", + new[] { 0, 1, 2, 3 } + }; + yield return new object[] + { + @"Consume({|#0:foo.Substring(1) + bar|}, {|#1:foo + bar.Substring(1)|});", + @"Consume(string.Concat(foo.AsSpan(1), bar), string.Concat(foo, bar.AsSpan(1)));", + new[] { 0, 1 } + }; + yield return new object[] + { + @"Consume(Fwd({|#0:foo.Substring(1) + bar|}), Fwd({|#1:foo + bar.Substring(1)|}));", + @"Consume(Fwd(string.Concat(foo.AsSpan(1), bar)), Fwd(string.Concat(foo, bar.AsSpan(1))));", + new[] { 0, 1 } + }; + } + } + + [Theory] + [MemberData(nameof(Data_MultipleViolationsInOneBlock_CS))] + public Task MultipleViolationsInOneBlock_AreReportedAndFixed_CS(string testStatements, string fixedStatements, int[] locations) + { + var test = new VerifyCS.Test + { + TestCode = CSUsings + CSWithBody(testStatements), + FixedCode = CSUsings + CSWithBody(fixedStatements), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }; + test.ExpectedDiagnostics.AddRange(locations.Select(x => VerifyCS.Diagnostic(Rule).WithLocation(x))); + return test.RunAsync(); + } + + public static IEnumerable Data_MultipleViolationsInOneBlock_VB + { + get + { + yield return new object[] + { + @" +Dim alpha = {|#0:foo.Substring(1, 2) & bar.Substring(1) & baz|} +Dim bravo = {|#1:foo & bar.Substring(3) & baz.Substring(1, 2)|} +Dim charlie = {|#2:foo & bar & baz.Substring(1, 2)|} +Dim delta = {|#3:foo.Substring(1) & bar.Substring(1) & baz.Substring(1, 2) & foo.Substring(1, 2)|}", + @" +Dim alpha = String.Concat(foo.AsSpan(1, 2), bar.AsSpan(1), baz) +Dim bravo = String.Concat(foo, bar.AsSpan(3), baz.AsSpan(1, 2)) +Dim charlie = String.Concat(foo, bar, baz.AsSpan(1, 2)) +Dim delta = String.Concat(foo.AsSpan(1), bar.AsSpan(1), baz.AsSpan(1, 2), foo.AsSpan(1, 2))", + new[] { 0, 1, 2, 3 } + }; + yield return new object[] + { + @"Consume({|#0:foo.Substring(1) & bar|}, {|#1:foo & bar.Substring(1)|})", + @"Consume(String.Concat(foo.AsSpan(1), bar), String.Concat(foo, bar.AsSpan(1)))", + new[] { 0, 1 } + }; + yield return new object[] + { + @"Consume(Fwd({|#0:foo.Substring(1) & bar|}), Fwd({|#1:foo & bar.Substring(1)|}))", + @"Consume(Fwd(String.Concat(foo.AsSpan(1), bar)), Fwd(String.Concat(foo, bar.AsSpan(1))))", + new[] { 0, 1 } + }; + } + } + + [Theory] + [MemberData(nameof(Data_MultipleViolationsInOneBlock_VB))] + public Task MultipleViolationsInOneBlock_AreReportedAndFixed_VB(string testStatements, string fixedStatements, int[] locations) + { + var test = new VerifyVB.Test + { + TestCode = VBUsings + VBWithBody(testStatements), + FixedCode = VBUsings + VBWithBody(fixedStatements), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + }; + test.ExpectedDiagnostics.AddRange(locations.Select(x => VerifyVB.Diagnostic(Rule).WithLocation(x))); + return test.RunAsync(); + } + + public static IEnumerable Data_NestedViolations_CS + { + get + { + yield return new object[] + { + @" +Consume({|#0:Fwd({|#1:foo + bar.Substring(1)|}) + baz.Substring(1)|});", + @" +Consume(string.Concat(Fwd(string.Concat(foo, bar.AsSpan(1))), baz.AsSpan(1)));", + new[] { 0, 1 }, + -4 + }; + yield return new object[] + { + @" +var _ = {|#0:Fwd({|#1:foo.Substring(1) + bar.Substring(1)|}) + Fwd({|#2:foo.Substring(1) + bar|}).Substring(1) + Fwd({|#3:foo + bar.Substring(1)|})|};", + @" +var _ = string.Concat(Fwd(string.Concat(foo.AsSpan(1), bar.AsSpan(1))), Fwd(string.Concat(foo.AsSpan(1), bar)).AsSpan(1), Fwd(string.Concat(foo, bar.AsSpan(1))));", + new[] { 0, 1, 2, 3 }, + -4 + }; + } + } + + [Theory] + [MemberData(nameof(Data_NestedViolations_CS))] + public Task NestedViolations_AreReportedAndFixed_CS(string testStatements, string fixedStatements, int[] locations, int? iterations = null) + { + var test = new VerifyCS.Test + { + TestCode = CSUsings + CSWithBody(testStatements), + FixedCode = CSUsings + CSWithBody(fixedStatements), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + NumberOfIncrementalIterations = iterations, + NumberOfFixAllInDocumentIterations = iterations, + NumberOfFixAllIterations = iterations + }; + test.ExpectedDiagnostics.AddRange(locations.Select(x => VerifyCS.Diagnostic(Rule).WithLocation(x))); + return test.RunAsync(); + } + + public static IEnumerable Data_NestedViolations_VB + { + get + { + yield return new object[] + { + @" +Consume({|#0:Fwd({|#1:foo + bar.Substring(1)|}) + baz.Substring(1)|})", + @" +Consume(String.Concat(Fwd(String.Concat(foo, bar.AsSpan(1))), baz.AsSpan(1)))", + new[] { 0, 1 } + }; + yield return new object[] + { + @" +Dim s = {|#0:Fwd({|#1:foo.Substring(1) & bar.Substring(1)|}) & Fwd({|#2:foo.Substring(1) & bar|}).Substring(1) & Fwd({|#3:foo & bar.Substring(1)|})|}", + @" +Dim s = String.Concat(Fwd(String.Concat(foo.AsSpan(1), bar.AsSpan(1))), Fwd(String.Concat(foo.AsSpan(1), bar)).AsSpan(1), Fwd(String.Concat(foo, bar.AsSpan(1))))", + new[] { 0, 1, 2, 3 } + }; + } + } + + [Theory] + [MemberData(nameof(Data_NestedViolations_VB))] + public Task NestedViolations_AreReportedAndFixed_VB(string testStatements, string fixedStatements, int[] locations) + { + var test = new VerifyVB.Test + { + TestCode = VBUsings + VBWithBody(testStatements), + FixedCode = VBUsings + VBWithBody(fixedStatements), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + NumberOfIncrementalIterations = -10, + NumberOfFixAllIterations = -10, + NumberOfFixAllInDocumentIterations = -10 + }; + test.ExpectedDiagnostics.AddRange(locations.Select(x => VerifyVB.Diagnostic(Rule).WithLocation(x))); + return test.RunAsync(); + } + + [Fact] + public Task MissingImports_AreAdded_CS() + { + var test = new VerifyCS.Test + { + TestCode = CSWithBody(@"var _ = {|#0:foo + bar.Substring(1)|};"), + FixedCode = $"\r\n{CSUsings}\r\n" + CSWithBody(@"var _ = string.Concat(foo, bar.AsSpan(1));"), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { VerifyCS.Diagnostic(Rule).WithLocation(0) } + }; + return test.RunAsync(); + } + + [Fact] + public Task MissingImports_AreAdded_VB() + { + var test = new VerifyVB.Test + { + TestCode = VBWithBody(@"Dim s = {|#0:foo & bar.Substring(1)|}"), + FixedCode = $"\r\n{VBUsings}\r\n" + VBWithBody(@"Dim s = String.Concat(foo, bar.AsSpan(1))"), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { VerifyVB.Diagnostic(Rule).WithLocation(0) } + }; + return test.RunAsync(); + } + + [Theory] + [InlineData(@"Substring(startIndex: 1)", @"AsSpan(start: 1)")] + [InlineData(@"Substring(startIndex: 1, length: 2)", @"AsSpan(start: 1, length: 2)")] + [InlineData(@"Substring(1, length: 2)", @"AsSpan(1, length: 2)")] + [InlineData(@"Substring(startIndex: 1, 2)", @"AsSpan(start: 1, 2)")] + [InlineData(@"Substring(length: 2, startIndex: 1)", @"AsSpan(length: 2, start: 1)")] + public Task NamedSubstringArguments_ArePreserved_CS(string substring, string asSpan) + { + string testStatements = $@"var s = {{|#0:foo.{substring} + bar|}};"; + string fixedStatements = $@"var s = string.Concat(foo.{asSpan}, bar);"; + + var test = new VerifyCS.Test + { + TestCode = CSUsings + CSWithBody(testStatements), + FixedCode = CSUsings + CSWithBody(fixedStatements), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { VerifyCS.Diagnostic(Rule).WithLocation(0) } + }; + return test.RunAsync(); + } + + [Theory] + [InlineData(@"Substring(startIndex:=1)", @"AsSpan(start:=1)")] + [InlineData(@"Substring(startIndex:=1, length:=2)", @"AsSpan(start:=1, length:=2)")] + [InlineData(@"Substring(1, length:=2)", @"AsSpan(1, length:=2)")] + [InlineData(@"Substring(startIndex:=1, 2)", @"AsSpan(start:=1, 2)")] + [InlineData(@"Substring(length:=2, startIndex:=1)", @"AsSpan(length:=2, start:=1)")] + public Task NamedSubstringArguments_ArePreserved_VB(string substring, string asSpan) + { + string testStatements = $@"Dim s = {{|#0:foo.{substring} & bar|}}"; + string fixedStatements = $@"Dim s = String.Concat(foo.{asSpan}, bar)"; + + var test = new VerifyVB.Test + { + TestCode = VBUsings + VBWithBody(testStatements), + FixedCode = VBUsings + VBWithBody(fixedStatements), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { VerifyVB.Diagnostic(Rule).WithLocation(0) } + }; + return test.RunAsync(); + } + + public static IEnumerable Data_NonStringOperands_CS + { + get + { + yield return new[] + { + @"foo.Substring(1) + count", + @"string.Concat(foo.AsSpan(1), count.ToString())" + }; + yield return new[] + { + @"count + foo.Substring(1)", + @"string.Concat(count.ToString(), foo.AsSpan(1))" + }; + yield return new[] + { + @"thing + foo.Substring(1)", + @"string.Concat(thing?.ToString(), foo.AsSpan(1))" + }; + yield return new[] + { + @"foo.Substring(1) + thing", + @"string.Concat(foo.AsSpan(1), thing?.ToString())" + }; + yield return new[] + { + @"maybe + foo.Substring(1)", + @"string.Concat(maybe?.ToString(), foo.AsSpan(1))" + }; + yield return new[] + { + @"thing + foo.Substring(1, 2) + maybe", + @"string.Concat(thing?.ToString(), foo.AsSpan(1, 2), maybe?.ToString())" + }; + yield return new[] + { + @"foo + 17 + bar.Substring(1)", + @"string.Concat(foo, 17.ToString(), bar.AsSpan(1))" + }; + yield return new[] + { + @"foo.Substring(1, 2) + 17", + @"string.Concat(foo.AsSpan(1, 2), 17.ToString())" + }; + yield return new[] + { + @"foo + 3.14159 + bar.Substring(1)", + @"string.Concat(foo, 3.14159.ToString(), bar.AsSpan(1))" + }; + yield return new[] + { + @"3.14159 + foo.Substring(1) + 6.02e23", + @"string.Concat(3.14159.ToString(), foo.AsSpan(1), 6.02e23.ToString())" + }; + } + } + + [Theory] + [MemberData(nameof(Data_NonStringOperands_CS))] + public Task NonStringOperands_AreConvertedToString_CS(string testExpression, string fixedExpression) + { + string format = @" +int count = 11; +object thing = new object(); +TimeSpan? maybe = null; +string s = {0};"; + var culture = CultureInfo.InvariantCulture; + + var test = new VerifyCS.Test + { + TestCode = CSUsings + CSWithBody(string.Format(culture, format, $"{{|#0:{testExpression}|}}")), + FixedCode = CSUsings + CSWithBody(string.Format(culture, format, fixedExpression)), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { VerifyCS.Diagnostic(Rule).WithLocation(0) } + }; + return test.RunAsync(); + } + + public static IEnumerable Data_NonStringOperands_VB + { + get + { + yield return new[] + { + @"foo.Substring(1) & count", + @"String.Concat(foo.AsSpan(1), count.ToString())" + }; + yield return new[] + { + @"count & foo.Substring(1)", + @"String.Concat(count.ToString(), foo.AsSpan(1))" + }; + yield return new[] + { + @"thing & foo.Substring(1)", + @"String.Concat(thing?.ToString(), foo.AsSpan(1))" + }; + yield return new[] + { + @"foo.Substring(1) & thing", + @"String.Concat(foo.AsSpan(1), thing?.ToString())" + }; + yield return new[] + { + @"maybe & foo.Substring(1)", + @"String.Concat(maybe?.ToString(), foo.AsSpan(1))" + }; + yield return new[] + { + @"thing & foo.Substring(1, 2) & maybe", + @"String.Concat(thing?.ToString(), foo.AsSpan(1, 2), maybe?.ToString())" + }; + yield return new[] + { + @"foo & 17 & bar.Substring(1)", + @"String.Concat(foo, 17.ToString(), bar.AsSpan(1))" + }; + yield return new[] + { + @"foo.Substring(1, 2) & 17", + @"String.Concat(foo.AsSpan(1, 2), 17.ToString())" + }; + yield return new[] + { + @"foo & 3.14159 & bar.Substring(1)", + @"String.Concat(foo, 3.14159.ToString(), bar.AsSpan(1))" + }; + yield return new[] + { + @"3.14159 & foo.Substring(1) & 6.02e23", + @"String.Concat(3.14159.ToString(), foo.AsSpan(1), 6.02e23.ToString())" + }; + } + } + + [Theory] + [MemberData(nameof(Data_NonStringOperands_VB))] + public Task NonStringOperands_AreConvertedToString_VB(string testExpression, string fixedExpression) + { + string format = @" +Dim count As Integer = 11 +Dim thing As Object = New Object() +Dim maybe As TimeSpan? = Nothing +Dim s = {0}"; + var culture = CultureInfo.InvariantCulture; + + var test = new VerifyVB.Test + { + TestCode = VBUsings + VBWithBody(string.Format(culture, format, $"{{|#0:{testExpression}|}}")), + FixedCode = VBUsings + VBWithBody(string.Format(culture, format, fixedExpression)), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { VerifyVB.Diagnostic(Rule).WithLocation(0) } + }; + return test.RunAsync(); + } + #endregion + + #region No Diagnostic + [Theory] + [InlineData("foo.Substring(1) + foo + foo + bar + baz")] + [InlineData("foo + foo.Substring(1) + bar + baz + baz")] + [InlineData("foo.Substring(1) + bar.Substring(1) + baz.Substring(1) + bar.Substring(1) + foo.Substring(1)")] + [InlineData("foo.Substring(1) + bar + baz + foo.Substring(1) + bar + baz")] + [InlineData("foo + bar.Substring(1) + baz + foo + bar.Substring(1) + baz")] + [InlineData("foo.Substring(1) + bar + baz.Substring(1) + foo.Substring(1) + bar + baz.Substring(1)")] + public Task TooManyArguments_NoDiagnostic_CS(string expression) + { + string statements = $@"var s = {expression};"; + + var test = new VerifyCS.Test + { + TestCode = CSUsings + CSWithBody(statements), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }; + return test.RunAsync(); + } + + [Theory] + [InlineData("foo.Substring(1) & foo & foo & bar & baz")] + [InlineData("foo & foo.Substring(1) & bar & baz & baz")] + [InlineData("foo.Substring(1) & bar.Substring(1) & baz.Substring(1) & bar.Substring(1) & foo.Substring(1)")] + [InlineData("foo.Substring(1) & bar & baz & foo.Substring(1) & bar & baz")] + [InlineData("foo & bar.Substring(1) & baz & foo & bar.Substring(1) & baz")] + [InlineData("foo.Substring(1) & bar & baz.Substring(1) & foo.Substring(1) & bar & baz.Substring(1)")] + public Task TooManyArguments_NoDiagnostic_VB(string expression) + { + string statements = $@"Dim s = {expression}"; + + var test = new VerifyVB.Test + { + TestCode = VBUsings + VBWithBody(statements), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }; + return test.RunAsync(); + } + + [Theory] + [InlineData("foo + bar")] + [InlineData("foo + bar + baz")] + [InlineData("foo + bar.ToUpper()")] + [InlineData("foo.ToLower() + bar")] + public Task NoSubstringInvocations_NoDiagnostic_CS(string expression) + { + string statements = $@"var s = {expression};"; + + var test = new VerifyCS.Test + { + TestCode = CSUsings + CSWithBody(statements), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }; + return test.RunAsync(); + } + + [Theory] + [InlineData("foo & bar")] + [InlineData("foo & bar & baz")] + [InlineData("foo & bar.ToUpper()")] + [InlineData("foo.ToLower() & bar")] + public Task NoSubstringInvocations_NoDiagnostic_VB(string expression) + { + string statements = $@"Dim s = {expression}"; + + var test = new VerifyVB.Test + { + TestCode = VBUsings + VBWithBody(statements), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }; + return test.RunAsync(); + } + + // No VB case because VB can't overload operators. + [Theory] + [InlineData(@"foo.Substring(1) + evil")] + [InlineData(@"evil + foo.Substring(1)")] + [InlineData(@"foo + evil + bar.Substring(1)")] + [InlineData(@"foo.Substring(1) + evil + bar")] + [InlineData(@"foo.Substring(1) + evil + bar + baz")] + [InlineData(@"foo + bar + evil + baz.Substring(1)")] + [InlineData(@"foo + evil + bar.Substring(1) + evil")] + [InlineData(@"foo + evil + bar.Substring(1) + evil + baz.Substring(1)")] + public Task OverloadedAddOperator_NoDiagnostic(string expression) + { + string statements = $@" +var evil = new EvilOverloads(); +var e = {expression};"; + + var test = new VerifyCS.Test + { + TestState = + { + Sources = + { + EvilOverloads, + CSUsings + CSWithBody(statements) + } + }, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }; + return test.RunAsync(); + } + #endregion + + #region Helpers + private static DiagnosticDescriptor Rule => UseSpanBasedStringConcat.Rule; + private const string CSUsings = @"using System;"; + private const string VBUsings = @"Imports System"; + private const string EvilOverloads = @" +public class EvilOverloads +{ + public static EvilOverloads operator +(EvilOverloads left, string right) => left; + public static EvilOverloads operator +(string left, EvilOverloads right) => right; + public static EvilOverloads operator +(EvilOverloads left, EvilOverloads right) => left; +}"; + + private static string CSWithBody(string statements) + { + return @" +public class Testopolis +{ + private void Consume(string consumed) { } + private void Consume(string s1, string s2) { } + private void Consume(string s1, string s2, string s3) { } + private string Fwd(string arg) => arg; + private string Transform(string first, string second) => second; + private string Transform(string first, string second, string third) => first; + private string Produce() => ""Hello World""; + + public void FrobThem(string foo, string bar, string baz) + { +" + IndentLines(statements, " ") + @" + } +}"; + } + + private static string VBWithBody(string statements) + { + return @" +Public Class Testopolis + Private Sub Consume(consumed As String) + End Sub + Private Sub Consume(s1 As String, s2 As String) + End Sub + Private Sub Consume(s1 As String, s2 As String, s3 As String) + End Sub + Private Function Fwd(arg As String) As String + Return arg + End Function + Private Function Transform(first As String, second As String) As String + Return second + End Function + Private Function Transform(first As String, second As String, third As String) As String + Return first + End Function + Private Function Produce() As String + Return ""Hello World"" + End Function + + Public Sub FrobThem(foo As String, bar As String, baz As String) +" + IndentLines(statements, " ") + @" + End Sub +End Class"; + } + + private static string IndentLines(string body, string indent) + { + return indent + body.TrimStart().Replace("\r\n", "\r\n" + indent, StringComparison.Ordinal); + } + #endregion } } diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.Fixer.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.Fixer.vb new file mode 100644 index 0000000000..7e81120772 --- /dev/null +++ b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.Fixer.vb @@ -0,0 +1,65 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.CodeAnalysis +Imports Microsoft.CodeAnalysis.CodeFixes +Imports Microsoft.CodeAnalysis.Editing +Imports Microsoft.CodeAnalysis.Operations +Imports Microsoft.CodeAnalysis.VisualBasic +Imports Microsoft.CodeAnalysis.VisualBasic.Syntax +Imports Microsoft.NetCore.Analyzers.Runtime + +Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime + + + Public NotInheritable Class BasicUseSpanBasedStringConcatFixer : Inherits UseSpanBasedStringConcatFixer + + Private Protected Overrides Function ReplaceInvocationMethodName(generator As SyntaxGenerator, invocationSyntax As SyntaxNode, newName As String) As SyntaxNode + + Dim cast = DirectCast(invocationSyntax, InvocationExpressionSyntax) + Dim memberAccessSyntax = DirectCast(cast.Expression, MemberAccessExpressionSyntax) + Dim oldNameSyntax = memberAccessSyntax.Name + Dim newNameSyntax = generator.IdentifierName(newName).WithTriviaFrom(oldNameSyntax) + Return invocationSyntax.ReplaceNode(oldNameSyntax, newNameSyntax) + End Function + + Private Protected Overrides Function GetOperatorToken(binaryOperation As IBinaryOperation) As SyntaxToken + + Dim syntax = DirectCast(binaryOperation.Syntax, BinaryExpressionSyntax) + Return syntax.OperatorToken + End Function + + Private Protected Overrides Function IsSystemNamespaceImported(namespaceImports As IReadOnlyList(Of SyntaxNode)) As Boolean + + For Each node As SyntaxNode In namespaceImports + Dim importsStatement = TryCast(node, ImportsStatementSyntax) + If importsStatement Is Nothing Then + Continue For + End If + For Each importsClause As ImportsClauseSyntax In importsStatement.ImportsClauses + Dim simpleClause = TryCast(importsClause, SimpleImportsClauseSyntax) + Dim identifierName = TryCast(simpleClause?.Name, IdentifierNameSyntax) + If identifierName Is Nothing Then + Continue For + End If + If identifierName.Identifier.ValueText = NameOf(System) Then + Return True + End If + Next + Next + Return False + End Function + + Private Protected Overrides Function IsNamedArgument(argument As IArgumentOperation) As Boolean + Return DirectCast(argument.Syntax, ArgumentSyntax).IsNamed + End Function + + Private Protected Overrides Function CreateConditionalToStringInvocation(receiverExpression As SyntaxNode) As SyntaxNode + + Dim expression = DirectCast(receiverExpression, ExpressionSyntax) + Dim memberAccessExpression = SyntaxFactory.SimpleMemberAccessExpression(SyntaxFactory.IdentifierName(ToStringName)) + Dim invocationExpression = SyntaxFactory.InvocationExpression(memberAccessExpression) + Return SyntaxFactory.ConditionalAccessExpression(expression.WithoutTrivia(), invocationExpression).WithTriviaFrom(expression) + End Function + End Class +End Namespace + diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.vb new file mode 100644 index 0000000000..7fd117bc96 --- /dev/null +++ b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.vb @@ -0,0 +1,18 @@ +' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +Imports Microsoft.NetCore.Analyzers.Runtime +Imports Microsoft.CodeAnalysis.Operations +Imports Microsoft.CodeAnalysis.Diagnostics +Imports Microsoft.CodeAnalysis + +Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime + + + Public NotInheritable Class BasicUseSpanBasedStringConcat : Inherits UseSpanBasedStringConcat + + Private Protected Overrides Function IsStringConcatOperation(binaryOperation As IBinaryOperation) As Boolean + Return binaryOperation.OperatorKind = BinaryOperatorKind.Concatenate + End Function + End Class +End Namespace + diff --git a/src/Utilities/Compiler/WellKnownTypeNames.cs b/src/Utilities/Compiler/WellKnownTypeNames.cs index 2a45fad59c..a6033ad82a 100644 --- a/src/Utilities/Compiler/WellKnownTypeNames.cs +++ b/src/Utilities/Compiler/WellKnownTypeNames.cs @@ -218,6 +218,7 @@ internal static class WellKnownTypeNames public const string SystemLinqQueryable = "System.Linq.Queryable"; public const string SystemMarshalByRefObject = "System.MarshalByRefObject"; public const string SystemMemory1 = "System.Memory`1"; + public const string SystemMemoryExtensions = "System.MemoryExtensions"; public const string SystemNetHttpHttpClient = "System.Net.Http.HttpClient"; public const string SystemNetHttpHttpClientHandler = "System.Net.Http.HttpClientHandler"; public const string SystemNetHttpWinHttpHandler = "System.Net.Http.WinHttpHandler"; From 0fe5cce98e84e3b23ad151fd20b15e450b7ed0be Mon Sep 17 00:00:00 2001 From: NewellClark Date: Tue, 2 Feb 2021 12:06:09 -0500 Subject: [PATCH 03/18] Analyzer and fixer working --- .../CSharpUseSpanBasedStringConcat.Fixer.cs | 33 +++-- .../Runtime/CSharpUseSpanBasedStringConcat.cs | 23 ++- .../MicrosoftNetCoreAnalyzersResources.resx | 6 +- .../Runtime/UseSpanBasedStringConcat.Fixer.cs | 139 +++++++++--------- .../Runtime/UseSpanBasedStringConcat.cs | 69 ++++----- .../MicrosoftNetCoreAnalyzersResources.cs.xlf | 12 +- .../MicrosoftNetCoreAnalyzersResources.de.xlf | 12 +- .../MicrosoftNetCoreAnalyzersResources.es.xlf | 12 +- .../MicrosoftNetCoreAnalyzersResources.fr.xlf | 12 +- .../MicrosoftNetCoreAnalyzersResources.it.xlf | 12 +- .../MicrosoftNetCoreAnalyzersResources.ja.xlf | 12 +- .../MicrosoftNetCoreAnalyzersResources.ko.xlf | 12 +- .../MicrosoftNetCoreAnalyzersResources.pl.xlf | 12 +- ...crosoftNetCoreAnalyzersResources.pt-BR.xlf | 12 +- .../MicrosoftNetCoreAnalyzersResources.ru.xlf | 12 +- .../MicrosoftNetCoreAnalyzersResources.tr.xlf | 12 +- ...osoftNetCoreAnalyzersResources.zh-Hans.xlf | 12 +- ...osoftNetCoreAnalyzersResources.zh-Hant.xlf | 12 +- .../Microsoft.CodeAnalysis.NetAnalyzers.md | 12 ++ .../Microsoft.CodeAnalysis.NetAnalyzers.sarif | 38 +++++ src/NetAnalyzers/RulesMissingDocumentation.md | 1 + .../Runtime/UseSpanBasedStringConcatTests.cs | 112 +++++++++++++- .../BasicUseSpanBasedStringConcat.Fixer.vb | 34 +++-- .../Runtime/BasicUseSpanBasedStringConcat.vb | 24 ++- 24 files changed, 416 insertions(+), 231 deletions(-) diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpUseSpanBasedStringConcat.Fixer.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpUseSpanBasedStringConcat.Fixer.cs index 23e54c8576..8289df89bf 100644 --- a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpUseSpanBasedStringConcat.Fixer.cs +++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpUseSpanBasedStringConcat.Fixer.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; +using Analyzer.Utilities; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.CSharp; @@ -22,12 +23,6 @@ private protected override SyntaxNode ReplaceInvocationMethodName(SyntaxGenerato return invocationSyntax.ReplaceNode(oldNameSyntax, newNameSyntax); } - private protected override SyntaxToken GetOperatorToken(IBinaryOperation binaryOperation) - { - var syntax = (BinaryExpressionSyntax)binaryOperation.Syntax; - return syntax.OperatorToken; - } - private protected override bool IsSystemNamespaceImported(IReadOnlyList namespaceImports) { foreach (var import in namespaceImports) @@ -38,18 +33,28 @@ private protected override bool IsSystemNamespaceImported(IReadOnlyListThis call site is reachable on: 'windows' 10.0.2000 and later, and all other platforms - description + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - message + Use span-based string.Concat and 'AsSpan' instead of 'Substring' - title + Use span-based string.Concat \ No newline at end of file diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs index 0b1c09cbdc..63405c5fd3 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs @@ -16,27 +16,31 @@ using Resx = Microsoft.NetCore.Analyzers.MicrosoftNetCoreAnalyzersResources; using RequiredSymbols = Microsoft.NetCore.Analyzers.Runtime.UseSpanBasedStringConcat.RequiredSymbols; +using System.Diagnostics.CodeAnalysis; namespace Microsoft.NetCore.Analyzers.Runtime { public abstract class UseSpanBasedStringConcatFixer : CodeFixProvider { - private const string AsSpanName = nameof(MemoryExtensions.AsSpan); - private const string AsSpanStartParameterName = "start"; + private protected const string AsSpanName = nameof(MemoryExtensions.AsSpan); + private protected const string AsSpanStartParameterName = "start"; private protected const string ToStringName = nameof(ToString); private protected abstract SyntaxNode ReplaceInvocationMethodName(SyntaxGenerator generator, SyntaxNode invocationSyntax, string newName); - private protected abstract SyntaxToken GetOperatorToken(IBinaryOperation binaryOperation); - private protected abstract bool IsSystemNamespaceImported(IReadOnlyList namespaceImports); - private protected abstract bool IsNamedArgument(IArgumentOperation argument); + /// Invoke ToString with the Elvis operator. + private protected abstract SyntaxNode GenerateConditionalToStringInvocationExpression(SyntaxNode expression); /// - /// Invokes on the specified expression using the Elvis operator. + /// Remove the built in implicit conversion to object when a non-string operand is concatenated in C#. + /// In Visual Basic, the implicit conversion can be to string or object. + /// User-defined conversions are not removed. /// - private protected abstract SyntaxNode CreateConditionalToStringInvocation(SyntaxNode receiverExpression); + private protected abstract IOperation WalkDownBuiltInImplicitConversionOnConcatOperand(IOperation operand); + + private protected abstract bool IsNamedArgument(IArgumentOperation argumentOperation); public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(UseSpanBasedStringConcat.RuleId); @@ -49,15 +53,15 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) var compilation = model.Compilation; SyntaxNode root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); - // Bail out early if we're missing anything we need. if (!RequiredSymbols.TryGetSymbols(compilation, out var symbols)) return; if (root.FindNode(context.Span, getInnermostNodeForTie: true) is not SyntaxNode concatExpressionSyntax) return; - if (model.GetOperation(concatExpressionSyntax, cancellationToken) is not IBinaryOperation concatOperation || concatOperation.OperatorKind != symbols.ConcatOperatorKind) + // OperatorKind will be BinaryOperatorKind.Concatenate, even when '+' is used instead of '&' in Visual Basic. + if (model.GetOperation(concatExpressionSyntax, cancellationToken) is not IBinaryOperation concatOperation || concatOperation.OperatorKind is not (BinaryOperatorKind.Add or BinaryOperatorKind.Concatenate)) return; - var operands = UseSpanBasedStringConcat.FlattenBinaryOperationChain(concatOperation); + var operands = UseSpanBasedStringConcat.FlattenBinaryOperation(concatOperation); // Bail out if we don't have a long enough span-based string.Concat overload. if (!symbols.TryGetRoscharConcatMethodWithArity(operands.Length, out IMethodSymbol? roscharConcatMethod)) return; @@ -75,16 +79,25 @@ async Task FixConcatOperationChain(CancellationToken cancellationToken var editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); var generator = editor.Generator; - // Save leading and trailing trivia so it can be attached to the outside of the 'string.Concat(...)' invocation expression. + SyntaxNode stringTypeNameSyntax = generator.TypeExpressionForStaticMemberAccess(symbols.StringType); + SyntaxNode concatMemberAccessSyntax = generator.MemberAccessExpression(stringTypeNameSyntax, roscharConcatMethod.Name); + + // Save leading and trailing trivia so it can be attached to the outside of the string.Concat invocation node. var leadingTrivia = operands.First().Syntax.GetLeadingTrivia(); var trailingTrivia = operands.Last().Syntax.GetTrailingTrivia(); - SyntaxNode stringTypeNameSyntax = generator.TypeExpressionForStaticMemberAccess(symbols.StringType); - SyntaxNode concatMemberAccessSyntax = generator.MemberAccessExpression(stringTypeNameSyntax, roscharConcatMethod.Name); - var arguments = GenerateConcatMethodArguments(symbols, generator, operands); - SyntaxNode concatMethodInvocationSyntax = generator.InvocationExpression(concatMemberAccessSyntax, arguments) + var arguments = ImmutableArray.CreateBuilder(operands.Length); + foreach (var operand in operands) + arguments.Add(ConvertOperandToArgument(symbols, generator, operand)); + + // Strip off leading and trailing trivia from first and last operand nodes, respectively, and + // reattach it to the outside of the newly-created string.Concat invocation node. + arguments[0] = arguments[0].WithoutLeadingTrivia(); + arguments[^1] = arguments[^1].WithoutTrailingTrivia(); + SyntaxNode concatMethodInvocationSyntax = generator.InvocationExpression(concatMemberAccessSyntax, arguments.MoveToImmutable()) .WithLeadingTrivia(leadingTrivia) .WithTrailingTrivia(trailingTrivia); + var newRoot = generator.ReplaceNode(root, concatExpressionSyntax, concatMethodInvocationSyntax); // Make sure 'System' namespace is imported. @@ -99,78 +112,64 @@ async Task FixConcatOperationChain(CancellationToken cancellationToken } } - private ImmutableArray GenerateConcatMethodArguments(in RequiredSymbols symbols, SyntaxGenerator generator, ImmutableArray operands) + public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + private SyntaxNode ConvertOperandToArgument(in RequiredSymbols symbols, SyntaxGenerator generator, IOperation operand) { - var builder = ImmutableArray.CreateBuilder(operands.Length); - foreach (IOperation operand in operands) - { - // Convert 'Substring' invocations into 'AsSpan' invocations. - if (WalkDownImplicitConversion(operand) is IInvocationOperation invocation && symbols.IsAnySubstringMethod(invocation.TargetMethod)) - { - SyntaxNode newInvocationSyntax = invocation.Syntax; + var value = WalkDownBuiltInImplicitConversionOnConcatOperand(operand); - // Convert 'Substring' named-arguments to equivalent 'AsSpan' named arguments. - IArgumentOperation? namedStartIndexArgument = GetNamedStartIndexArgumentOrDefault(symbols, invocation); - if (namedStartIndexArgument is not null) - { - SyntaxNode renamedSubstringArgumentSyntax = generator.Argument(AsSpanStartParameterName, RefKind.None, namedStartIndexArgument.Value.Syntax); - newInvocationSyntax = generator.ReplaceNode(newInvocationSyntax, namedStartIndexArgument.Syntax, renamedSubstringArgumentSyntax); - } + // Convert substring invocations to equivalent AsSpan invocation. + if (value is IInvocationOperation invocation && symbols.IsAnySubstringMethod(invocation.TargetMethod)) + { + SyntaxNode invocationSyntax = invocation.Syntax; - // Replace 'Substring' identifier with 'AsSpan', leaving the rest of the node (including trivia) intact. - newInvocationSyntax = ReplaceInvocationMethodName(generator, newInvocationSyntax, AsSpanName); - builder.Add(generator.Argument(newInvocationSyntax)); + // Swap out parameter names if named-arguments are used. + if (TryGetNamedStartIndexArgument(symbols, invocation, out var namedStartIndexArgument)) + { + var renamedArgumentSyntax = generator.Argument(AsSpanStartParameterName, RefKind.None, namedStartIndexArgument.Value.Syntax); + invocationSyntax = generator.ReplaceNode(invocationSyntax, namedStartIndexArgument.Syntax, renamedArgumentSyntax); + } + var asSpanInvocationSyntax = ReplaceInvocationMethodName(generator, invocationSyntax, AsSpanName); + return generator.Argument(asSpanInvocationSyntax); + } + else if (value.Type.SpecialType == SpecialType.System_String) + { + return generator.Argument(value.Syntax); + } + // If the operand is a non-string type, we need to invoke ToString() on it to + // prevent overload resolution from choosing an object-based string.Concat overload. + else + { + // Invoke ToString with Elvis operator if receiver could be null. + if (value.Type.IsReferenceTypeOrNullableValueType()) + { + SyntaxNode conditionalAccessSyntax = GenerateConditionalToStringInvocationExpression(value.Syntax); + return generator.Argument(conditionalAccessSyntax); } else { - IOperation value = WalkDownImplicitConversion(operand); - if (value.Type.SpecialType == SpecialType.System_String) - { - builder.Add(generator.Argument(value.Syntax)); - } - else - { - SyntaxNode newValueSyntax; - if (value.Type.IsReferenceTypeOrNullableValueType()) - { - newValueSyntax = CreateConditionalToStringInvocation(value.Syntax); - } - else - { - SyntaxNode toStringMemberAccessSyntax = generator.MemberAccessExpression(value.Syntax.WithoutTrivia(), ToStringName); - newValueSyntax = generator.InvocationExpression(toStringMemberAccessSyntax, Array.Empty()) - .WithTriviaFrom(value.Syntax); - } - builder.Add(generator.Argument(newValueSyntax)); - } + SyntaxNode memberAccessSyntax = generator.MemberAccessExpression(value.Syntax.WithoutTrivia(), ToStringName); + SyntaxNode toStringInvocationSyntax = generator.InvocationExpression(memberAccessSyntax, Array.Empty()); + return generator.Argument(toStringInvocationSyntax).WithTriviaFrom(value.Syntax); } } - builder[0] = builder[0].WithoutLeadingTrivia(); - builder[^1] = builder[^1].WithoutTrailingTrivia(); - return builder.MoveToImmutable(); - - // If the 'startIndex' argument was passed using named-arguments, return it. - // Otherwise, return null. - IArgumentOperation? GetNamedStartIndexArgumentOrDefault(in RequiredSymbols symbols, IInvocationOperation substringInvocation) + bool TryGetNamedStartIndexArgument(in RequiredSymbols symbols, IInvocationOperation substringInvocation, [NotNullWhen(true)] out IArgumentOperation? namedStartIndexArgument) { RoslynDebug.Assert(symbols.IsAnySubstringMethod(substringInvocation.TargetMethod)); + foreach (var argument in substringInvocation.Arguments) { if (IsNamedArgument(argument) && symbols.IsAnySubstringStartIndexParameter(argument.Parameter)) - return argument; + { + namedStartIndexArgument = argument; + return true; + } } - return null; - } - static IOperation WalkDownImplicitConversion(IOperation operand) - { - if (operand is IConversionOperation { Type: { SpecialType: SpecialType.System_Object or SpecialType.System_String }, IsImplicit: true } conversion) - return conversion.Operand; - return operand; + namedStartIndexArgument = default; + return false; } } - - public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; } } diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs index 6fe7fc6fd0..e3551c8434 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs @@ -37,7 +37,12 @@ public abstract class UseSpanBasedStringConcat : DiagnosticAnalyzer private const string AsSpanName = nameof(MemoryExtensions.AsSpan); private const string ConcatName = nameof(string.Concat); - private protected abstract bool IsStringConcatOperation(IBinaryOperation binaryOperation); + /// + /// If the specified binary operation is a string concatenation operation, we try to walk up to the top-most + /// string-concatenation operation that it is part of. If it is not a string-concatenation operation, we simply + /// return false. + /// + private protected abstract bool TryGetTopMostConcatOperation(IBinaryOperation binaryOperation, [NotNullWhen(true)] out IBinaryOperation? rootBinaryOperation); public sealed override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); @@ -57,58 +62,49 @@ private void OnCompilationStart(CompilationStartAnalysisContext context) void OnOperationBlockStart(OperationBlockStartAnalysisContext context) { - var rootConcatOperations = PooledConcurrentSet.GetInstance(); + // Maintain set of all top-most concat operations so we don't report sub-expressions of an + // already-reported violation. + // We also don't report any diagnostic if the concat operation has too many operands for the span-based + // Concat overloads to handle. + var topMostConcatOperations = PooledConcurrentSet.GetInstance(); - context.RegisterOperationAction(PopulateRootConcatOperations, OperationKind.Binary); + context.RegisterOperationAction(PopulateTopMostConcatOperations, OperationKind.Binary); context.RegisterOperationBlockEndAction(ReportDiagnosticsOnRootConcatOperationsWithSubstringCalls); - void PopulateRootConcatOperations(OperationAnalysisContext context) + void PopulateTopMostConcatOperations(OperationAnalysisContext context) { + // If the current operation is a string-concatenation operation, walk up to the top-most concat + // operation and add it to the set. var binary = (IBinaryOperation)context.Operation; - if (binary.OperatorKind != symbols.ConcatOperatorKind) + if (!TryGetTopMostConcatOperation(binary, out var topMostConcatOperation)) return; - var topBinaryOperation = WalkUpBinaryOperationChain(binary); - if (!IsStringConcatOperation(topBinaryOperation)) - return; - - rootConcatOperations.Add(topBinaryOperation); + topMostConcatOperations.Add(topMostConcatOperation); } void ReportDiagnosticsOnRootConcatOperationsWithSubstringCalls(OperationBlockAnalysisContext context) { - foreach (var root in rootConcatOperations) + // We report diagnostics for all top-most concat operations that contain substring invocations + // when there is an applicable span-based overload of string.Concat + foreach (var operation in topMostConcatOperations) { - var chain = FlattenBinaryOperationChain(root); + var chain = FlattenBinaryOperation(operation); if (chain.Any(IsAnySubstringInvocation) && symbols.TryGetRoscharConcatMethodWithArity(chain.Length, out var _)) { - context.ReportDiagnostic(root.CreateDiagnostic(Rule)); + context.ReportDiagnostic(operation.CreateDiagnostic(Rule)); } } - rootConcatOperations.Free(context.CancellationToken); + topMostConcatOperations.Free(context.CancellationToken); } } bool IsAnySubstringInvocation(IOperation operation) { - return operation.WalkDownConversion() is IInvocationOperation invocation && - (invocation.TargetMethod.Equals(symbols.Substring1, SymbolEqualityComparer.Default) || - invocation.TargetMethod.Equals(symbols.Substring2, SymbolEqualityComparer.Default)); - } - } - - private static IBinaryOperation WalkUpBinaryOperationChain(IBinaryOperation operation) - { - while (operation.Parent is IBinaryOperation parentBinaryOperation && - parentBinaryOperation.OperatorKind == operation.OperatorKind) - { - operation = parentBinaryOperation; + return operation.WalkDownConversion() is IInvocationOperation invocation && symbols.IsAnySubstringMethod(invocation.TargetMethod); } - - return operation; } - internal static ImmutableArray FlattenBinaryOperationChain(IBinaryOperation root) + internal static ImmutableArray FlattenBinaryOperation(IBinaryOperation root) { var stack = new Stack(); var builder = ImmutableArray.CreateBuilder(); @@ -155,8 +151,7 @@ internal readonly struct RequiredSymbols private RequiredSymbols( INamedTypeSymbol stringType, INamedTypeSymbol roscharType, IMethodSymbol substring1, IMethodSymbol substring2, - IMethodSymbol asSpan1, IMethodSymbol asSpan2, - BinaryOperatorKind concatOperatorKind) + IMethodSymbol asSpan1, IMethodSymbol asSpan2) { StringType = stringType; RoscharType = roscharType; @@ -164,7 +159,6 @@ private RequiredSymbols( Substring2 = substring2; AsSpan1 = asSpan1; AsSpan2 = asSpan2; - ConcatOperatorKind = concatOperatorKind; RoslynDebug.Assert( StringType is not null && RoscharType is not null && @@ -202,18 +196,10 @@ public static bool TryGetSymbols(Compilation compilation, out RequiredSymbols sy return false; } - var concatOperatorKind = compilation.Language switch - { - LanguageNames.CSharp => BinaryOperatorKind.Add, - LanguageNames.VisualBasic => BinaryOperatorKind.Concatenate, - _ => BinaryOperatorKind.None - }; - symbols = new RequiredSymbols( stringType, roscharType, substring1, substring2, - asSpan1, asSpan2, - concatOperatorKind); + asSpan1, asSpan2); return true; } @@ -223,7 +209,6 @@ public static bool TryGetSymbols(Compilation compilation, out RequiredSymbols sy public IMethodSymbol Substring2 { get; } public IMethodSymbol AsSpan1 { get; } public IMethodSymbol AsSpan2 { get; } - public BinaryOperatorKind ConcatOperatorKind { get; } public IMethodSymbol? GetAsSpanEquivalent(IMethodSymbol? substringMethod) { diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf index d6dbb80219..8b7c5abe13 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf @@ -2238,18 +2238,18 @@ - description - description + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - message - message + Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based string.Concat and 'AsSpan' instead of 'Substring' - title - title + Use span-based string.Concat + Use span-based string.Concat diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf index 74a68f8bab..c6e2616a60 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf @@ -2238,18 +2238,18 @@ - description - description + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - message - message + Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based string.Concat and 'AsSpan' instead of 'Substring' - title - title + Use span-based string.Concat + Use span-based string.Concat diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf index e2d072c08e..a1c274311a 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf @@ -2238,18 +2238,18 @@ - description - description + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - message - message + Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based string.Concat and 'AsSpan' instead of 'Substring' - title - title + Use span-based string.Concat + Use span-based string.Concat diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf index 20c591156f..1c7133f129 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf @@ -2238,18 +2238,18 @@ - description - description + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - message - message + Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based string.Concat and 'AsSpan' instead of 'Substring' - title - title + Use span-based string.Concat + Use span-based string.Concat diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf index 477cf058b9..ec7e5df064 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf @@ -2238,18 +2238,18 @@ - description - description + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - message - message + Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based string.Concat and 'AsSpan' instead of 'Substring' - title - title + Use span-based string.Concat + Use span-based string.Concat diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf index 4b4dbc362d..30567af965 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf @@ -2238,18 +2238,18 @@ - description - description + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - message - message + Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based string.Concat and 'AsSpan' instead of 'Substring' - title - title + Use span-based string.Concat + Use span-based string.Concat diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf index 8c22daf246..acfe590ed4 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf @@ -2238,18 +2238,18 @@ - description - description + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - message - message + Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based string.Concat and 'AsSpan' instead of 'Substring' - title - title + Use span-based string.Concat + Use span-based string.Concat diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf index 4b9053b0a0..0a42434f03 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf @@ -2238,18 +2238,18 @@ - description - description + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - message - message + Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based string.Concat and 'AsSpan' instead of 'Substring' - title - title + Use span-based string.Concat + Use span-based string.Concat diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf index f56187c75f..2b28442296 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf @@ -2238,18 +2238,18 @@ - description - description + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - message - message + Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based string.Concat and 'AsSpan' instead of 'Substring' - title - title + Use span-based string.Concat + Use span-based string.Concat diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf index 5f75bc3e08..ca1e6f5909 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf @@ -2238,18 +2238,18 @@ - description - description + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - message - message + Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based string.Concat and 'AsSpan' instead of 'Substring' - title - title + Use span-based string.Concat + Use span-based string.Concat diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf index 40a280c256..4f65b8ef16 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf @@ -2238,18 +2238,18 @@ - description - description + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - message - message + Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based string.Concat and 'AsSpan' instead of 'Substring' - title - title + Use span-based string.Concat + Use span-based string.Concat diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf index 72fb48bcba..3c9a1bad54 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf @@ -2238,18 +2238,18 @@ - description - description + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - message - message + Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based string.Concat and 'AsSpan' instead of 'Substring' - title - title + Use span-based string.Concat + Use span-based string.Concat diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf index 3783ef55bb..7439a26415 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf @@ -2238,18 +2238,18 @@ - description - description + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - message - message + Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based string.Concat and 'AsSpan' instead of 'Substring' - title - title + Use span-based string.Concat + Use span-based string.Concat diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md index fa5d976400..73fc86d433 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md @@ -1296,6 +1296,18 @@ Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in |CodeFix|False| --- +## [CA1841](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1841): Use span-based string.Concat + +'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + +|Item|Value| +|-|-| +|Category|Performance| +|Enabled|True| +|Severity|Info| +|CodeFix|True| +--- + ## [CA2000](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000): Dispose objects before losing scope If a disposable object is not explicitly disposed before all references to it are out of scope, the object will be disposed at some indeterminate time when the garbage collector runs the finalizer of the object. Because an exceptional event might occur that will prevent the finalizer of the object from running, the object should be explicitly disposed instead. diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif index 72c37b9580..058d94ecda 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif @@ -228,6 +228,25 @@ ] } }, + "CA1841": { + "id": "CA1841", + "shortDescription": "Use span-based string.Concat", + "fullDescription": "'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. ", + "defaultLevel": "note", + "helpUri": "https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1841", + "properties": { + "category": "Performance", + "isEnabledByDefault": true, + "typeName": "CSharpUseSpanBasedStringConcat", + "languages": [ + "C#" + ], + "tags": [ + "Telemetry", + "EnabledRuleInAggressiveMode" + ] + } + }, "CA2014": { "id": "CA2014", "shortDescription": "Do not use stackalloc in loops", @@ -5224,6 +5243,25 @@ ] } }, + "CA1841": { + "id": "CA1841", + "shortDescription": "Use span-based string.Concat", + "fullDescription": "'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. ", + "defaultLevel": "note", + "helpUri": "https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1841", + "properties": { + "category": "Performance", + "isEnabledByDefault": true, + "typeName": "BasicUseSpanBasedStringConcat", + "languages": [ + "Visual Basic" + ], + "tags": [ + "Telemetry", + "EnabledRuleInAggressiveMode" + ] + } + }, "CA2016": { "id": "CA2016", "shortDescription": "Forward the 'CancellationToken' parameter to methods", diff --git a/src/NetAnalyzers/RulesMissingDocumentation.md b/src/NetAnalyzers/RulesMissingDocumentation.md index 75e0f77588..7411bda822 100644 --- a/src/NetAnalyzers/RulesMissingDocumentation.md +++ b/src/NetAnalyzers/RulesMissingDocumentation.md @@ -2,3 +2,4 @@ Rule ID | Missing Help Link | Title | --------|-------------------|-------| +CA1841 | | Use span-based string.Concat | diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs index 0d71a5a214..59eff87744 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs @@ -500,11 +500,6 @@ public static IEnumerable Data_NonStringOperands_VB @"String.Concat(foo.AsSpan(1), thing?.ToString())" }; yield return new[] - { - @"maybe & foo.Substring(1)", - @"String.Concat(maybe?.ToString(), foo.AsSpan(1))" - }; - yield return new[] { @"thing & foo.Substring(1, 2) & maybe", @"String.Concat(thing?.ToString(), foo.AsSpan(1, 2), maybe?.ToString())" @@ -552,6 +547,111 @@ public Task NonStringOperands_AreConvertedToString_VB(string testExpression, str }; return test.RunAsync(); } + + [Theory] + [InlineData(@"foo.Substring(1) + (string)explicitTo", @"string.Concat(foo.AsSpan(1), (string)explicitTo)")] + [InlineData(@"(string)explicitTo + foo.Substring(1)", @"string.Concat((string)explicitTo, foo.AsSpan(1))")] + [InlineData(@"foo.Substring(1) + (string)thing", @"string.Concat(foo.AsSpan(1), (string)thing)")] + [InlineData(@"(string)thing + foo.Substring(1)", @"string.Concat((string)thing, foo.AsSpan(1))")] + [InlineData(@"foo.Substring(1) + (thing as string)", @"string.Concat(foo.AsSpan(1), thing as string)")] + [InlineData(@"(thing as string) + foo.Substring(1)", @"string.Concat(thing as string, foo.AsSpan(1))")] + public Task ExplicitConversions_ArePreserved_CS(string testExpression, string fixedExpression) + { + string helperTypes = @" +public class ExplicitTo +{ + public static explicit operator string(ExplicitTo operand) => operand?.ToString(); +} + +public class ExplicitFrom +{ + public static explicit operator ExplicitFrom(string operand) => new ExplicitFrom(); +}"; + string format = @" +var explicitTo = new ExplicitTo(); +object thing = bar; +var s = {0};"; + var culture = CultureInfo.InvariantCulture; + string testStatements = string.Format(culture, format, $@"{{|#0:{testExpression}|}}"); + string fixedStatements = string.Format(culture, format, fixedExpression); + + var test = new VerifyCS.Test + { + TestState = { Sources = { CSUsings + CSWithBody(testStatements), helperTypes } }, + FixedState = { Sources = { CSUsings + CSWithBody(fixedStatements), helperTypes } }, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { VerifyCS.Diagnostic(Rule).WithLocation(0) } + }; + return test.RunAsync(); + } + + [Theory] + [InlineData(@"foo.Substring(1) & CType(explicitTo, String)", @"String.Concat(foo.AsSpan(1), CType(explicitTo, String))")] + [InlineData(@"CType(explicitTo, String) & foo.Substring(1)", @"String.Concat(CType(explicitTo, String), foo.AsSpan(1))")] + [InlineData(@"foo.Substring(1) & DirectCast(thing, String)", @"String.Concat(foo.AsSpan(1), DirectCast(thing, String))")] + [InlineData(@"DirectCast(thing, String) & foo.Substring(1)", @"String.Concat(DirectCast(thing, String), foo.AsSpan(1))")] + [InlineData(@"foo.Substring(1) & TryCast(thing, String)", @"String.Concat(foo.AsSpan(1), TryCast(thing, String))")] + [InlineData(@"TryCast(thing, String) & foo.Substring(1)", @"String.Concat(TryCast(thing, String), foo.AsSpan(1))")] + public Task ExplicitConversions_ArePreserved_VB(string testExpression, string fixedExpression) + { + string helperTypes = @" +Public Class ExplicitTo + + Public Shared Narrowing Operator CType(operand As ExplicitTo) As String + Return New ExplicitTo() + End Operator +End Class + +Public Class ExplicitFrom + Public Shared Narrowing Operator CType(operand As String) As ExplicitFrom + Return New ExplicitFrom() + End Operator +End Class"; + string format = @" +Dim explicitTo = New ExplicitTo() +Dim thing As Object = bar +Dim s = {0}"; + var culture = CultureInfo.InvariantCulture; + string testStatements = string.Format(culture, format, $@"{{|#0:{testExpression}|}}"); + string fixedStatements = string.Format(culture, format, fixedExpression); + + var test = new VerifyVB.Test + { + TestState = { Sources = { VBUsings + VBWithBody(testStatements), helperTypes } }, + FixedState = { Sources = { VBUsings + VBWithBody(fixedStatements), helperTypes } }, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { VerifyVB.Diagnostic(Rule).WithLocation(0) } + }; + return test.RunAsync(); + } + + // No C# case because C# has only one concat operator. + [Theory] + [InlineData(@"foo.Substring(1) & bar + baz", @"String.Concat(foo.AsSpan(1), bar, baz)")] + [InlineData(@"foo & bar.Substring(1) + baz", @"String.Concat(foo, bar.AsSpan(1), baz)")] + [InlineData(@"foo & bar + baz.Substring(1)", @"String.Concat(foo, bar, baz.AsSpan(1))")] + [InlineData(@"foo.Substring(1) + bar & baz", @"String.Concat(foo.AsSpan(1), bar, baz)")] + [InlineData(@"foo + bar.Substring(1) & baz", @"String.Concat(foo, bar.AsSpan(1), baz)")] + [InlineData(@"foo + bar & baz.Substring(1)", @"String.Concat(foo, bar, baz.AsSpan(1))")] + [InlineData(@"foo.Substring(1) & bar + baz & baz.Substring(1)", @"String.Concat(foo.AsSpan(1), bar, baz, baz.AsSpan(1))")] + [InlineData(@"foo & bar.Substring(1) + baz & foo", @"String.Concat(foo, bar.AsSpan(1), baz, foo)")] + [InlineData(@"foo.Substring(1) & bar + baz & foo", @"String.Concat(foo.AsSpan(1), bar, baz, foo)")] + [InlineData(@"foo & bar + baz & foo.Substring(1)", @"String.Concat(foo, bar, baz, foo.AsSpan(1))")] + [InlineData(@"foo.Substring(1) + bar & baz + baz.Substring(1)", @"String.Concat(foo.AsSpan(1), bar, baz, baz.AsSpan(1))")] + [InlineData(@"foo + bar.Substring(1) & baz + foo", @"String.Concat(foo, bar.AsSpan(1), baz, foo)")] + [InlineData(@"foo.Substring(1) + bar & baz + foo", @"String.Concat(foo.AsSpan(1), bar, baz, foo)")] + [InlineData(@"foo + bar & baz + foo.Substring(1)", @"String.Concat(foo, bar, baz, foo.AsSpan(1))")] + public Task MixedAddAndConcatenateOperatorChains_AreReportedAndFixed_VB(string testExpression, string fixedExpression) + { + var test = new VerifyVB.Test + { + TestCode = VBUsings + VBWithBody($@"Dim s = {{|#0:{testExpression}|}}"), + FixedCode = VBUsings + VBWithBody($@"Dim s = {fixedExpression}"), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { VerifyVB.Diagnostic(Rule).WithLocation(0) } + }; + return test.RunAsync(); + } #endregion #region No Diagnostic @@ -637,7 +737,7 @@ public Task NoSubstringInvocations_NoDiagnostic_VB(string expression) [InlineData(@"foo + bar + evil + baz.Substring(1)")] [InlineData(@"foo + evil + bar.Substring(1) + evil")] [InlineData(@"foo + evil + bar.Substring(1) + evil + baz.Substring(1)")] - public Task OverloadedAddOperator_NoDiagnostic(string expression) + public Task OverloadedAddOperator_NoDiagnostic_CS(string expression) { string statements = $@" var evil = new EvilOverloads(); diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.Fixer.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.Fixer.vb index 7e81120772..d6233591d5 100644 --- a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.Fixer.vb +++ b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.Fixer.vb @@ -22,12 +22,6 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime Return invocationSyntax.ReplaceNode(oldNameSyntax, newNameSyntax) End Function - Private Protected Overrides Function GetOperatorToken(binaryOperation As IBinaryOperation) As SyntaxToken - - Dim syntax = DirectCast(binaryOperation.Syntax, BinaryExpressionSyntax) - Return syntax.OperatorToken - End Function - Private Protected Overrides Function IsSystemNamespaceImported(namespaceImports As IReadOnlyList(Of SyntaxNode)) As Boolean For Each node As SyntaxNode In namespaceImports @@ -49,16 +43,30 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime Return False End Function - Private Protected Overrides Function IsNamedArgument(argument As IArgumentOperation) As Boolean - Return DirectCast(argument.Syntax, ArgumentSyntax).IsNamed + Private Protected Overrides Function GenerateConditionalToStringInvocationExpression(expression As SyntaxNode) As SyntaxNode + + Dim identifierName = SyntaxFactory.IdentifierName(ToStringName) + Dim simpleMemberAccess = SyntaxFactory.SimpleMemberAccessExpression(identifierName) + Dim invocation = SyntaxFactory.InvocationExpression(simpleMemberAccess, SyntaxFactory.ArgumentList()) + Return SyntaxFactory.ConditionalAccessExpression(DirectCast(expression, ExpressionSyntax).WithoutTrivia(), invocation).WithTriviaFrom(expression) + End Function + + Private Protected Overrides Function WalkDownBuiltInImplicitConversionOnConcatOperand(operand As IOperation) As IOperation + + Dim conversion = TryCast(operand, IConversionOperation) + If conversion IsNot Nothing AndAlso conversion.IsImplicit AndAlso Not conversion.Conversion.IsUserDefined AndAlso + (conversion.Type.SpecialType = SpecialType.System_String OrElse conversion.Type.SpecialType = SpecialType.System_Object) Then + + Return conversion.Operand + Else + Return operand + End If End Function - Private Protected Overrides Function CreateConditionalToStringInvocation(receiverExpression As SyntaxNode) As SyntaxNode + Private Protected Overrides Function IsNamedArgument(argumentOperation As IArgumentOperation) As Boolean - Dim expression = DirectCast(receiverExpression, ExpressionSyntax) - Dim memberAccessExpression = SyntaxFactory.SimpleMemberAccessExpression(SyntaxFactory.IdentifierName(ToStringName)) - Dim invocationExpression = SyntaxFactory.InvocationExpression(memberAccessExpression) - Return SyntaxFactory.ConditionalAccessExpression(expression.WithoutTrivia(), invocationExpression).WithTriviaFrom(expression) + Dim argumentSyntax = TryCast(argumentOperation.Syntax, ArgumentSyntax) + Return argumentSyntax IsNot Nothing AndAlso argumentSyntax.IsNamed End Function End Class End Namespace diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.vb index 7fd117bc96..87354fb119 100644 --- a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.vb +++ b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.vb @@ -10,8 +10,28 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime Public NotInheritable Class BasicUseSpanBasedStringConcat : Inherits UseSpanBasedStringConcat - Private Protected Overrides Function IsStringConcatOperation(binaryOperation As IBinaryOperation) As Boolean - Return binaryOperation.OperatorKind = BinaryOperatorKind.Concatenate + Private Protected Overrides Function TryGetTopMostConcatOperation(binaryOperation As IBinaryOperation, ByRef rootBinaryOperation As IBinaryOperation) As Boolean + + If Not IsStringConcatOperation(binaryOperation) Then + rootBinaryOperation = Nothing + Return False + End If + + Dim parentBinaryOperation = binaryOperation + Dim current As IBinaryOperation + Do + current = parentBinaryOperation + parentBinaryOperation = TryCast(current.Parent, IBinaryOperation) + Loop While parentBinaryOperation IsNot Nothing AndAlso IsStringConcatOperation(parentBinaryOperation) + + rootBinaryOperation = current + Return True + End Function + + Private Shared Function IsStringConcatOperation(operation As IBinaryOperation) As Boolean + + 'OperatorKind will be Concatenate even when the "+" operator is used, provided both operands are strings. + Return operation.OperatorKind = BinaryOperatorKind.Concatenate End Function End Class End Namespace From e98a55b9d20bbc5b543d69657d84b9996fd582e6 Mon Sep 17 00:00:00 2001 From: Newell Clark Date: Tue, 2 Feb 2021 12:13:41 -0500 Subject: [PATCH 04/18] Update src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx Fix title Co-authored-by: Manish Vasani --- .../MicrosoftNetCoreAnalyzersResources.resx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx index cf7e21ef06..7ce11c7caf 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx @@ -1514,9 +1514,9 @@ 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' Use span-based string.Concat - \ No newline at end of file + From 52652c993e457a25efc8aa1844937a34a96150d5 Mon Sep 17 00:00:00 2001 From: Newell Clark Date: Tue, 2 Feb 2021 12:14:02 -0500 Subject: [PATCH 05/18] Update src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx Co-authored-by: Manish Vasani --- .../MicrosoftNetCoreAnalyzersResources.resx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx index 7ce11c7caf..a3955f94bb 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx @@ -1517,6 +1517,6 @@ Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' - Use span-based string.Concat + Use span-based 'string.Concat' From ea9a6130f2d1f93ad95d25cba2519e4d8d035f48 Mon Sep 17 00:00:00 2001 From: NewellClark Date: Wed, 3 Feb 2021 21:15:58 -0500 Subject: [PATCH 06/18] - Moved WalkDownImplicitConversionOnConcatOperand to the analyzer, and made it a static method. - Added tests for conditional substring invocation. We report conditional substring invocations, but do not fix them. - Fixed issue where `Imports System` was unnecessarily being added in projects that had `System` as an implicit global import, and added tests for VB projects with and without an implicit global `System` import. - Added null tests for both string and char type symbols, just in case. - Used exact values for expected iteration counts for nested violations tests. - Other minor style changes. --- .../CSharpUseSpanBasedStringConcat.Fixer.cs | 11 +-- .../Runtime/UseSpanBasedStringConcat.Fixer.cs | 32 ++++---- .../Runtime/UseSpanBasedStringConcat.cs | 54 ++++++++++--- .../MicrosoftNetCoreAnalyzersResources.cs.xlf | 8 +- .../MicrosoftNetCoreAnalyzersResources.de.xlf | 8 +- .../MicrosoftNetCoreAnalyzersResources.es.xlf | 8 +- .../MicrosoftNetCoreAnalyzersResources.fr.xlf | 8 +- .../MicrosoftNetCoreAnalyzersResources.it.xlf | 8 +- .../MicrosoftNetCoreAnalyzersResources.ja.xlf | 8 +- .../MicrosoftNetCoreAnalyzersResources.ko.xlf | 8 +- .../MicrosoftNetCoreAnalyzersResources.pl.xlf | 8 +- ...crosoftNetCoreAnalyzersResources.pt-BR.xlf | 8 +- .../MicrosoftNetCoreAnalyzersResources.ru.xlf | 8 +- .../MicrosoftNetCoreAnalyzersResources.tr.xlf | 8 +- ...osoftNetCoreAnalyzersResources.zh-Hans.xlf | 8 +- ...osoftNetCoreAnalyzersResources.zh-Hant.xlf | 8 +- .../Microsoft.CodeAnalysis.NetAnalyzers.md | 2 +- .../Microsoft.CodeAnalysis.NetAnalyzers.sarif | 4 +- src/NetAnalyzers/RulesMissingDocumentation.md | 2 +- .../Runtime/UseSpanBasedStringConcatTests.cs | 77 +++++++++++++++---- .../BasicUseSpanBasedStringConcat.Fixer.vb | 19 ++--- 21 files changed, 187 insertions(+), 118 deletions(-) diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpUseSpanBasedStringConcat.Fixer.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpUseSpanBasedStringConcat.Fixer.cs index 8289df89bf..5c836de2dd 100644 --- a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpUseSpanBasedStringConcat.Fixer.cs +++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpUseSpanBasedStringConcat.Fixer.cs @@ -23,7 +23,7 @@ private protected override SyntaxNode ReplaceInvocationMethodName(SyntaxGenerato return invocationSyntax.ReplaceNode(oldNameSyntax, newNameSyntax); } - private protected override bool IsSystemNamespaceImported(IReadOnlyList namespaceImports) + private protected override bool IsSystemNamespaceImported(Project project, IReadOnlyList namespaceImports) { foreach (var import in namespaceImports) { @@ -43,15 +43,6 @@ private protected override SyntaxNode GenerateConditionalToStringInvocationExpre return SyntaxFactory.ConditionalAccessExpression((ExpressionSyntax)expression.WithoutTrivia(), invocation).WithTriviaFrom(expression); } - private protected override IOperation WalkDownBuiltInImplicitConversionOnConcatOperand(IOperation operand) - { - if (operand is IConversionOperation conversion && conversion.IsImplicit && conversion.Type.SpecialType == SpecialType.System_Object) - { - return conversion.Operand; - } - return operand; - } - private protected override bool IsNamedArgument(IArgumentOperation argumentOperation) { return argumentOperation.Syntax is ArgumentSyntax argumentSyntax && argumentSyntax.NameColon is not null; diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs index 63405c5fd3..bb21d819c7 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -13,10 +14,8 @@ using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Operations; - using Resx = Microsoft.NetCore.Analyzers.MicrosoftNetCoreAnalyzersResources; using RequiredSymbols = Microsoft.NetCore.Analyzers.Runtime.UseSpanBasedStringConcat.RequiredSymbols; -using System.Diagnostics.CodeAnalysis; namespace Microsoft.NetCore.Analyzers.Runtime { @@ -28,18 +27,11 @@ public abstract class UseSpanBasedStringConcatFixer : CodeFixProvider private protected abstract SyntaxNode ReplaceInvocationMethodName(SyntaxGenerator generator, SyntaxNode invocationSyntax, string newName); - private protected abstract bool IsSystemNamespaceImported(IReadOnlyList namespaceImports); + private protected abstract bool IsSystemNamespaceImported(Project project, IReadOnlyList namespaceImports); /// Invoke ToString with the Elvis operator. private protected abstract SyntaxNode GenerateConditionalToStringInvocationExpression(SyntaxNode expression); - /// - /// Remove the built in implicit conversion to object when a non-string operand is concatenated in C#. - /// In Visual Basic, the implicit conversion can be to string or object. - /// User-defined conversions are not removed. - /// - private protected abstract IOperation WalkDownBuiltInImplicitConversionOnConcatOperand(IOperation operand); - private protected abstract bool IsNamedArgument(IArgumentOperation argumentOperation); public sealed override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create(UseSpanBasedStringConcat.RuleId); @@ -65,6 +57,10 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) // Bail out if we don't have a long enough span-based string.Concat overload. if (!symbols.TryGetRoscharConcatMethodWithArity(operands.Length, out IMethodSymbol? roscharConcatMethod)) return; + // Bail if none of the operands are a non-conditional substring invocation. This could be the case if the + // only substring invocations in the expression were conditional invocations. + if (!operands.Any(IsAnyNonConditionalSubstringInvocation)) + return; var codeAction = CodeAction.Create( Resx.UseSpanBasedStringConcatTitle, @@ -72,6 +68,12 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) Resx.UseSpanBasedStringConcatTitle); context.RegisterCodeFix(codeAction, diagnostic); + bool IsAnyNonConditionalSubstringInvocation(IOperation operation) + { + var value = UseSpanBasedStringConcat.WalkDownBuiltInImplicitConversionOnConcatOperand(operation); + return value is IInvocationOperation invocation && symbols.IsAnySubstringMethod(invocation.TargetMethod); + } + async Task FixConcatOperationChain(CancellationToken cancellationToken) { RoslynDebug.Assert(roscharConcatMethod is not null); @@ -98,12 +100,12 @@ async Task FixConcatOperationChain(CancellationToken cancellationToken .WithLeadingTrivia(leadingTrivia) .WithTrailingTrivia(trailingTrivia); - var newRoot = generator.ReplaceNode(root, concatExpressionSyntax, concatMethodInvocationSyntax); + SyntaxNode newRoot = generator.ReplaceNode(root, concatExpressionSyntax, concatMethodInvocationSyntax); - // Make sure 'System' namespace is imported. - if (!IsSystemNamespaceImported(generator.GetNamespaceImports(newRoot))) + // Import 'System' namespace if it's absent. + if (!IsSystemNamespaceImported(context.Document.Project, generator.GetNamespaceImports(newRoot))) { - var systemNamespaceImport = generator.NamespaceImportDeclaration(nameof(System)); + SyntaxNode systemNamespaceImport = generator.NamespaceImportDeclaration(nameof(System)); newRoot = generator.AddNamespaceImports(newRoot, systemNamespaceImport); } @@ -116,7 +118,7 @@ async Task FixConcatOperationChain(CancellationToken cancellationToken private SyntaxNode ConvertOperandToArgument(in RequiredSymbols symbols, SyntaxGenerator generator, IOperation operand) { - var value = WalkDownBuiltInImplicitConversionOnConcatOperand(operand); + var value = UseSpanBasedStringConcat.WalkDownBuiltInImplicitConversionOnConcatOperand(operand); // Convert substring invocations to equivalent AsSpan invocation. if (value is IInvocationOperation invocation && symbols.IsAnySubstringMethod(invocation.TargetMethod)) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs index e3551c8434..468442b0ef 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs @@ -1,17 +1,17 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Collections.Immutable; +using System.Diagnostics.CodeAnalysis; +using System.Linq; using Analyzer.Utilities; using Analyzer.Utilities.Extensions; +using Analyzer.Utilities.PooledObjects; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; using Resx = Microsoft.NetCore.Analyzers.MicrosoftNetCoreAnalyzersResources; -using Analyzer.Utilities.PooledObjects; -using System.Collections.Generic; -using System.Linq; -using System.Diagnostics.CodeAnalysis; namespace Microsoft.NetCore.Analyzers.Runtime { @@ -84,12 +84,13 @@ void PopulateTopMostConcatOperations(OperationAnalysisContext context) void ReportDiagnosticsOnRootConcatOperationsWithSubstringCalls(OperationBlockAnalysisContext context) { - // We report diagnostics for all top-most concat operations that contain substring invocations + // We report diagnostics for all top-most concat operations that contain + // direct or conditional substring invocations // when there is an applicable span-based overload of string.Concat foreach (var operation in topMostConcatOperations) { var chain = FlattenBinaryOperation(operation); - if (chain.Any(IsAnySubstringInvocation) && symbols.TryGetRoscharConcatMethodWithArity(chain.Length, out var _)) + if (chain.Any(IsAnyDirectOrConditionalSubstringInvocation) && symbols.TryGetRoscharConcatMethodWithArity(chain.Length, out _)) { context.ReportDiagnostic(operation.CreateDiagnostic(Rule)); } @@ -98,9 +99,13 @@ void ReportDiagnosticsOnRootConcatOperationsWithSubstringCalls(OperationBlockAna } } - bool IsAnySubstringInvocation(IOperation operation) + bool IsAnyDirectOrConditionalSubstringInvocation(IOperation operation) { - return operation.WalkDownConversion() is IInvocationOperation invocation && symbols.IsAnySubstringMethod(invocation.TargetMethod); + var value = WalkDownBuiltInImplicitConversionOnConcatOperand(operation); + if (value is IConditionalAccessOperation conditionallAccessOperation) + value = conditionallAccessOperation.WhenNotNull; + + return value is IInvocationOperation invocation && symbols.IsAnySubstringMethod(invocation.TargetMethod); } } @@ -142,6 +147,28 @@ void GoLeft(IBinaryOperation operation) } } + /// + /// Remove the built in implicit conversion on operands to concat. + /// In VB, the conversion can be to either string or object. + /// In C#, the conversion is always to object. + /// + internal static IOperation WalkDownBuiltInImplicitConversionOnConcatOperand(IOperation operand) + { + if (operand is not IConversionOperation conversion) + return operand; + if (!conversion.IsImplicit || conversion.Conversion.IsUserDefined) + return conversion; + + switch (conversion.Language) + { + case LanguageNames.CSharp when conversion.Type.SpecialType is SpecialType.System_Object: + case LanguageNames.VisualBasic when conversion.Type.SpecialType is SpecialType.System_Object or SpecialType.System_String: + return conversion.Operand; + default: + return conversion; + } + } + // Use readonly struct instead of record type to save on allocations, since it's not passed by-value. // We aren't comparing these. #pragma warning disable CA1815 // Override equals and operator equals on value types @@ -169,8 +196,15 @@ Substring1 is not null && Substring2 is not null && public static bool TryGetSymbols(Compilation compilation, out RequiredSymbols symbols) { var stringType = compilation.GetSpecialType(SpecialType.System_String); - var roscharType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemReadOnlySpan1) - ?.Construct(compilation.GetSpecialType(SpecialType.System_Char)); + var charType = compilation.GetSpecialType(SpecialType.System_Char); + + if (stringType is null || charType is null) + { + symbols = default; + return false; + } + + var roscharType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemReadOnlySpan1)?.Construct(charType); var memoryExtensionsType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemMemoryExtensions); if (roscharType is null || memoryExtensionsType is null) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf index 8b7c5abe13..740564b220 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf @@ -2243,13 +2243,13 @@ - Use span-based string.Concat and 'AsSpan' instead of 'Substring' - Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' - Use span-based string.Concat - Use span-based string.Concat + Use span-based 'string.Concat' + Use span-based 'string.Concat' diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf index c6e2616a60..77ad01ba18 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf @@ -2243,13 +2243,13 @@ - Use span-based string.Concat and 'AsSpan' instead of 'Substring' - Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' - Use span-based string.Concat - Use span-based string.Concat + Use span-based 'string.Concat' + Use span-based 'string.Concat' diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf index a1c274311a..78a7be015a 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf @@ -2243,13 +2243,13 @@ - Use span-based string.Concat and 'AsSpan' instead of 'Substring' - Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' - Use span-based string.Concat - Use span-based string.Concat + Use span-based 'string.Concat' + Use span-based 'string.Concat' diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf index 1c7133f129..440947f079 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf @@ -2243,13 +2243,13 @@ - Use span-based string.Concat and 'AsSpan' instead of 'Substring' - Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' - Use span-based string.Concat - Use span-based string.Concat + Use span-based 'string.Concat' + Use span-based 'string.Concat' diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf index ec7e5df064..6ec762df7d 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf @@ -2243,13 +2243,13 @@ - Use span-based string.Concat and 'AsSpan' instead of 'Substring' - Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' - Use span-based string.Concat - Use span-based string.Concat + Use span-based 'string.Concat' + Use span-based 'string.Concat' diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf index 30567af965..6647d41ed0 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf @@ -2243,13 +2243,13 @@ - Use span-based string.Concat and 'AsSpan' instead of 'Substring' - Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' - Use span-based string.Concat - Use span-based string.Concat + Use span-based 'string.Concat' + Use span-based 'string.Concat' diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf index acfe590ed4..71d733fc1f 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf @@ -2243,13 +2243,13 @@ - Use span-based string.Concat and 'AsSpan' instead of 'Substring' - Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' - Use span-based string.Concat - Use span-based string.Concat + Use span-based 'string.Concat' + Use span-based 'string.Concat' diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf index 0a42434f03..bcac6eadee 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf @@ -2243,13 +2243,13 @@ - Use span-based string.Concat and 'AsSpan' instead of 'Substring' - Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' - Use span-based string.Concat - Use span-based string.Concat + Use span-based 'string.Concat' + Use span-based 'string.Concat' diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf index 2b28442296..31260e8c1c 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf @@ -2243,13 +2243,13 @@ - Use span-based string.Concat and 'AsSpan' instead of 'Substring' - Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' - Use span-based string.Concat - Use span-based string.Concat + Use span-based 'string.Concat' + Use span-based 'string.Concat' diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf index ca1e6f5909..9f0c95bda3 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf @@ -2243,13 +2243,13 @@ - Use span-based string.Concat and 'AsSpan' instead of 'Substring' - Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' - Use span-based string.Concat - Use span-based string.Concat + Use span-based 'string.Concat' + Use span-based 'string.Concat' diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf index 4f65b8ef16..5bd4e98049 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf @@ -2243,13 +2243,13 @@ - Use span-based string.Concat and 'AsSpan' instead of 'Substring' - Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' - Use span-based string.Concat - Use span-based string.Concat + Use span-based 'string.Concat' + Use span-based 'string.Concat' diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf index 3c9a1bad54..ce6bd686b6 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf @@ -2243,13 +2243,13 @@ - Use span-based string.Concat and 'AsSpan' instead of 'Substring' - Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' - Use span-based string.Concat - Use span-based string.Concat + Use span-based 'string.Concat' + Use span-based 'string.Concat' diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf index 7439a26415..99035d25f7 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf @@ -2243,13 +2243,13 @@ - Use span-based string.Concat and 'AsSpan' instead of 'Substring' - Use span-based string.Concat and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' + Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' - Use span-based string.Concat - Use span-based string.Concat + Use span-based 'string.Concat' + Use span-based 'string.Concat' diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md index 73fc86d433..65bdc233ad 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md @@ -1296,7 +1296,7 @@ Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in |CodeFix|False| --- -## [CA1841](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1841): Use span-based string.Concat +## [CA1841](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1841): Use span-based 'string.Concat' 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif index 058d94ecda..65e60a821c 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif @@ -230,7 +230,7 @@ }, "CA1841": { "id": "CA1841", - "shortDescription": "Use span-based string.Concat", + "shortDescription": "Use span-based 'string.Concat'", "fullDescription": "'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. ", "defaultLevel": "note", "helpUri": "https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1841", @@ -5245,7 +5245,7 @@ }, "CA1841": { "id": "CA1841", - "shortDescription": "Use span-based string.Concat", + "shortDescription": "Use span-based 'string.Concat'", "fullDescription": "'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. ", "defaultLevel": "note", "helpUri": "https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1841", diff --git a/src/NetAnalyzers/RulesMissingDocumentation.md b/src/NetAnalyzers/RulesMissingDocumentation.md index 7411bda822..2cbae72ee6 100644 --- a/src/NetAnalyzers/RulesMissingDocumentation.md +++ b/src/NetAnalyzers/RulesMissingDocumentation.md @@ -2,4 +2,4 @@ Rule ID | Missing Help Link | Title | --------|-------------------|-------| -CA1841 | | Use span-based string.Concat | +CA1841 | | Use span-based 'string.Concat' | diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs index 59eff87744..48359f3f19 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Testing; +using Microsoft.CodeAnalysis.VisualBasic; using Xunit; using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< @@ -258,7 +259,7 @@ public static IEnumerable Data_NestedViolations_CS @" Consume(string.Concat(Fwd(string.Concat(foo, bar.AsSpan(1))), baz.AsSpan(1)));", new[] { 0, 1 }, - -4 + 2, 2, 2 }; yield return new object[] { @@ -267,23 +268,25 @@ public static IEnumerable Data_NestedViolations_CS @" var _ = string.Concat(Fwd(string.Concat(foo.AsSpan(1), bar.AsSpan(1))), Fwd(string.Concat(foo.AsSpan(1), bar)).AsSpan(1), Fwd(string.Concat(foo, bar.AsSpan(1))));", new[] { 0, 1, 2, 3 }, - -4 + 4, 3, 3 }; } } [Theory] [MemberData(nameof(Data_NestedViolations_CS))] - public Task NestedViolations_AreReportedAndFixed_CS(string testStatements, string fixedStatements, int[] locations, int? iterations = null) + public Task NestedViolations_AreReportedAndFixed_CS( + string testStatements, string fixedStatements, int[] locations, + int? incrementalIterations, int? fixAllInDocumentIterations, int? fixAllIterations) { var test = new VerifyCS.Test { TestCode = CSUsings + CSWithBody(testStatements), FixedCode = CSUsings + CSWithBody(fixedStatements), ReferenceAssemblies = ReferenceAssemblies.Net.Net50, - NumberOfIncrementalIterations = iterations, - NumberOfFixAllInDocumentIterations = iterations, - NumberOfFixAllIterations = iterations + NumberOfIncrementalIterations = incrementalIterations, + NumberOfFixAllInDocumentIterations = fixAllInDocumentIterations, + NumberOfFixAllIterations = fixAllIterations }; test.ExpectedDiagnostics.AddRange(locations.Select(x => VerifyCS.Diagnostic(Rule).WithLocation(x))); return test.RunAsync(); @@ -299,7 +302,8 @@ public static IEnumerable Data_NestedViolations_VB Consume({|#0:Fwd({|#1:foo + bar.Substring(1)|}) + baz.Substring(1)|})", @" Consume(String.Concat(Fwd(String.Concat(foo, bar.AsSpan(1))), baz.AsSpan(1)))", - new[] { 0, 1 } + new[] { 0, 1 }, + 2, 2, 2 }; yield return new object[] { @@ -307,30 +311,51 @@ public static IEnumerable Data_NestedViolations_VB Dim s = {|#0:Fwd({|#1:foo.Substring(1) & bar.Substring(1)|}) & Fwd({|#2:foo.Substring(1) & bar|}).Substring(1) & Fwd({|#3:foo & bar.Substring(1)|})|}", @" Dim s = String.Concat(Fwd(String.Concat(foo.AsSpan(1), bar.AsSpan(1))), Fwd(String.Concat(foo.AsSpan(1), bar)).AsSpan(1), Fwd(String.Concat(foo, bar.AsSpan(1))))", - new[] { 0, 1, 2, 3 } + new[] { 0, 1, 2, 3 }, + 4, 3, 3 }; } } [Theory] [MemberData(nameof(Data_NestedViolations_VB))] - public Task NestedViolations_AreReportedAndFixed_VB(string testStatements, string fixedStatements, int[] locations) + public Task NestedViolations_AreReportedAndFixed_VB( + string testStatements, string fixedStatements, int[] locations, + int? incrementalIterations, int? fixAllInDocumentIterations, int? fixAllIterations) { var test = new VerifyVB.Test { TestCode = VBUsings + VBWithBody(testStatements), FixedCode = VBUsings + VBWithBody(fixedStatements), ReferenceAssemblies = ReferenceAssemblies.Net.Net50, - NumberOfIncrementalIterations = -10, - NumberOfFixAllIterations = -10, - NumberOfFixAllInDocumentIterations = -10 + NumberOfIncrementalIterations = incrementalIterations, + NumberOfFixAllIterations = fixAllInDocumentIterations, + NumberOfFixAllInDocumentIterations = fixAllIterations }; test.ExpectedDiagnostics.AddRange(locations.Select(x => VerifyVB.Diagnostic(Rule).WithLocation(x))); return test.RunAsync(); } [Fact] - public Task MissingImports_AreAdded_CS() + public Task ConditionalSubstringAccess_IsFlaggedButNotFixed_CS() + { + string statements = @"var s = {|#0:foo?.Substring(1) + bar|};"; + string source = CSUsings + CSWithBody(statements); + + return VerifyCS.VerifyCodeFixAsync(source, VerifyCS.Diagnostic(Rule).WithLocation(0), source); + } + + [Fact] + public Task ConditionalSubstringAccess_IsFlaggedButNotFixed_VB() + { + string statements = @"Dim s = {|#0:foo?.Substring(1) & bar|}"; + string source = VBUsings + VBWithBody(statements); + + return VerifyVB.VerifyCodeFixAsync(source, VerifyVB.Diagnostic(Rule).WithLocation(0), source); + } + + [Fact] + public Task MissingSystemImport_IsAdded_WhenAbsent_CS() { var test = new VerifyCS.Test { @@ -342,8 +367,32 @@ public Task MissingImports_AreAdded_CS() return test.RunAsync(); } + // Visual Basic supports implicit global imports. By default, 'System' is added as a global + // import when you create a project in Visual Studio. + [Fact] + public Task MissingSystemImport_IsNotAdded_WhenIncludedInGlobalImports_VB() + { + var test = new VerifyVB.Test + { + TestCode = VBWithBody(@"Dim s = {|#0:foo & bar.Substring(1)|}"), + FixedCode = VBWithBody(@"Dim s = String.Concat(foo, bar.AsSpan(1))"), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { VerifyVB.Diagnostic(Rule).WithLocation(0) } + }; + test.SolutionTransforms.Add((s, id) => + { + var project = s.Projects.Single(); + var options = project.CompilationOptions as VisualBasicCompilationOptions; + var globalSystemImport = GlobalImport.Parse(nameof(System)); + options = options.WithGlobalImports(globalSystemImport); + return s.WithProjectCompilationOptions(project.Id, options); + }); + return test.RunAsync(); + } + + // We must add 'Imports System' if it is not included as a global import. [Fact] - public Task MissingImports_AreAdded_VB() + public Task MissingSystemImport_IsAdded_WhenAbsentFromGlobalImports_VB() { var test = new VerifyVB.Test { diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.Fixer.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.Fixer.vb index d6233591d5..f9fe9cca79 100644 --- a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.Fixer.vb +++ b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.Fixer.vb @@ -22,7 +22,12 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime Return invocationSyntax.ReplaceNode(oldNameSyntax, newNameSyntax) End Function - Private Protected Overrides Function IsSystemNamespaceImported(namespaceImports As IReadOnlyList(Of SyntaxNode)) As Boolean + Private Protected Overrides Function IsSystemNamespaceImported(project As Project, namespaceImports As IReadOnlyList(Of SyntaxNode)) As Boolean + + Dim options = DirectCast(project.CompilationOptions, VisualBasicCompilationOptions) + If options.GlobalImports.Any(Function(x) x.Name = NameOf(System)) Then + Return True + End If For Each node As SyntaxNode In namespaceImports Dim importsStatement = TryCast(node, ImportsStatementSyntax) @@ -51,18 +56,6 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime Return SyntaxFactory.ConditionalAccessExpression(DirectCast(expression, ExpressionSyntax).WithoutTrivia(), invocation).WithTriviaFrom(expression) End Function - Private Protected Overrides Function WalkDownBuiltInImplicitConversionOnConcatOperand(operand As IOperation) As IOperation - - Dim conversion = TryCast(operand, IConversionOperation) - If conversion IsNot Nothing AndAlso conversion.IsImplicit AndAlso Not conversion.Conversion.IsUserDefined AndAlso - (conversion.Type.SpecialType = SpecialType.System_String OrElse conversion.Type.SpecialType = SpecialType.System_Object) Then - - Return conversion.Operand - Else - Return operand - End If - End Function - Private Protected Overrides Function IsNamedArgument(argumentOperation As IArgumentOperation) As Boolean Dim argumentSyntax = TryCast(argumentOperation.Syntax, ArgumentSyntax) From c1baa81b5bf7e9d5b50ae2853ba1c070856c59b6 Mon Sep 17 00:00:00 2001 From: NewellClark Date: Thu, 4 Feb 2021 03:14:26 -0500 Subject: [PATCH 07/18] Fix tests on net472 Some tests didn't explicitly specify net5.0, which caused the tests to fail due to lack of span-based string.Concat. --- .../Runtime/UseSpanBasedStringConcatTests.cs | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs index 48359f3f19..3207754e83 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs @@ -342,7 +342,14 @@ public Task ConditionalSubstringAccess_IsFlaggedButNotFixed_CS() string statements = @"var s = {|#0:foo?.Substring(1) + bar|};"; string source = CSUsings + CSWithBody(statements); - return VerifyCS.VerifyCodeFixAsync(source, VerifyCS.Diagnostic(Rule).WithLocation(0), source); + var test = new VerifyCS.Test + { + TestCode = source, + FixedCode = source, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { VerifyCS.Diagnostic(Rule).WithLocation(0) } + }; + return test.RunAsync(); } [Fact] @@ -351,7 +358,14 @@ public Task ConditionalSubstringAccess_IsFlaggedButNotFixed_VB() string statements = @"Dim s = {|#0:foo?.Substring(1) & bar|}"; string source = VBUsings + VBWithBody(statements); - return VerifyVB.VerifyCodeFixAsync(source, VerifyVB.Diagnostic(Rule).WithLocation(0), source); + var test = new VerifyVB.Test + { + TestCode = source, + FixedCode = source, + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { VerifyVB.Diagnostic(Rule).WithLocation(0) } + }; + return test.RunAsync(); } [Fact] From 6d12d20719a4a1eebd8877bff9306112147d3f73 Mon Sep 17 00:00:00 2001 From: NewellClark Date: Thu, 4 Feb 2021 11:50:03 -0500 Subject: [PATCH 08/18] Use Environment.NewLine --- .../Runtime/UseSpanBasedStringConcatTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs index 3207754e83..97ba044c2c 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs @@ -374,7 +374,7 @@ public Task MissingSystemImport_IsAdded_WhenAbsent_CS() var test = new VerifyCS.Test { TestCode = CSWithBody(@"var _ = {|#0:foo + bar.Substring(1)|};"), - FixedCode = $"\r\n{CSUsings}\r\n" + CSWithBody(@"var _ = string.Concat(foo, bar.AsSpan(1));"), + FixedCode = $"{Environment.NewLine}{CSUsings}{Environment.NewLine}" + CSWithBody(@"var _ = string.Concat(foo, bar.AsSpan(1));"), ReferenceAssemblies = ReferenceAssemblies.Net.Net50, ExpectedDiagnostics = { VerifyCS.Diagnostic(Rule).WithLocation(0) } }; @@ -411,7 +411,7 @@ public Task MissingSystemImport_IsAdded_WhenAbsentFromGlobalImports_VB() var test = new VerifyVB.Test { TestCode = VBWithBody(@"Dim s = {|#0:foo & bar.Substring(1)|}"), - FixedCode = $"\r\n{VBUsings}\r\n" + VBWithBody(@"Dim s = String.Concat(foo, bar.AsSpan(1))"), + FixedCode = $"{Environment.NewLine}{VBUsings}{Environment.NewLine}" + VBWithBody(@"Dim s = String.Concat(foo, bar.AsSpan(1))"), ReferenceAssemblies = ReferenceAssemblies.Net.Net50, ExpectedDiagnostics = { VerifyVB.Diagnostic(Rule).WithLocation(0) } }; @@ -885,7 +885,7 @@ End Sub private static string IndentLines(string body, string indent) { - return indent + body.TrimStart().Replace("\r\n", "\r\n" + indent, StringComparison.Ordinal); + return indent + body.TrimStart().Replace(Environment.NewLine, Environment.NewLine + indent, StringComparison.Ordinal); } #endregion } From 91cf52d0b78b98663862d85ee3854aa53ef68e2f Mon Sep 17 00:00:00 2001 From: NewellClark Date: Fri, 5 Feb 2021 21:12:51 -0500 Subject: [PATCH 09/18] Fix conditional Substring and char literals - Conditional 'Substring' invocations such as 'foo?.Substring(1)' are reported, but not fixed. - Character literals are converted to string literals. - Concatenations containing non-string operands other than character literals are not reported at all, as we discussed. - Fix null reference crash on illegal operands. --- .../Runtime/UseSpanBasedStringConcat.Fixer.cs | 21 +- .../Runtime/UseSpanBasedStringConcat.cs | 44 +++- .../Runtime/UseSpanBasedStringConcatTests.cs | 243 +++++++----------- .../Runtime/BasicUseSpanBasedStringConcat.vb | 13 +- 4 files changed, 145 insertions(+), 176 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs index bb21d819c7..8f8ca22ade 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs @@ -134,26 +134,15 @@ private SyntaxNode ConvertOperandToArgument(in RequiredSymbols symbols, SyntaxGe var asSpanInvocationSyntax = ReplaceInvocationMethodName(generator, invocationSyntax, AsSpanName); return generator.Argument(asSpanInvocationSyntax); } - else if (value.Type.SpecialType == SpecialType.System_String) + // Character literals become string literals. + else if (value.Type.SpecialType == SpecialType.System_Char && value is ILiteralOperation literalOperation && literalOperation.ConstantValue.HasValue) { - return generator.Argument(value.Syntax); + var stringLiteral = generator.LiteralExpression(literalOperation.ConstantValue.Value.ToString()).WithTriviaFrom(literalOperation.Syntax); + return generator.Argument(stringLiteral); } - // If the operand is a non-string type, we need to invoke ToString() on it to - // prevent overload resolution from choosing an object-based string.Concat overload. else { - // Invoke ToString with Elvis operator if receiver could be null. - if (value.Type.IsReferenceTypeOrNullableValueType()) - { - SyntaxNode conditionalAccessSyntax = GenerateConditionalToStringInvocationExpression(value.Syntax); - return generator.Argument(conditionalAccessSyntax); - } - else - { - SyntaxNode memberAccessSyntax = generator.MemberAccessExpression(value.Syntax.WithoutTrivia(), ToStringName); - SyntaxNode toStringInvocationSyntax = generator.InvocationExpression(memberAccessSyntax, Array.Empty()); - return generator.Argument(toStringInvocationSyntax).WithTriviaFrom(value.Syntax); - } + return generator.Argument(value.Syntax); } bool TryGetNamedStartIndexArgument(in RequiredSymbols symbols, IInvocationOperation substringInvocation, [NotNullWhen(true)] out IArgumentOperation? namedStartIndexArgument) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs index 468442b0ef..cc605f9c8d 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs @@ -85,27 +85,55 @@ void PopulateTopMostConcatOperations(OperationAnalysisContext context) void ReportDiagnosticsOnRootConcatOperationsWithSubstringCalls(OperationBlockAnalysisContext context) { // We report diagnostics for all top-most concat operations that contain - // direct or conditional substring invocations - // when there is an applicable span-based overload of string.Concat + // direct or conditional substring invocations when there is an applicable span-based overload of + // the string.Concat method. + // We don't report when the concatenation contains anything other than strings or character literals. foreach (var operation in topMostConcatOperations) { - var chain = FlattenBinaryOperation(operation); - if (chain.Any(IsAnyDirectOrConditionalSubstringInvocation) && symbols.TryGetRoscharConcatMethodWithArity(chain.Length, out _)) + if (ShouldBeReported(operation)) { context.ReportDiagnostic(operation.CreateDiagnostic(Rule)); } } + topMostConcatOperations.Free(context.CancellationToken); } } + bool ShouldBeReported(IBinaryOperation topMostConcatOperation) + { + var concatOperands = FlattenBinaryOperation(topMostConcatOperation); + + // Bail if no suitable overload of 'string.Concat' exists. + if (!symbols.TryGetRoscharConcatMethodWithArity(concatOperands.Length, out _)) + return false; + + bool anySubstringInvocations = false; + foreach (var operand in concatOperands) + { + var value = WalkDownBuiltInImplicitConversionOnConcatOperand(operand); + switch (value.Type?.SpecialType) + { + // Report diagnostics only when operands are exclusively strings and character literals. + case SpecialType.System_String: + case SpecialType.System_Char when value is ILiteralOperation: + if (IsAnyDirectOrConditionalSubstringInvocation(value)) + anySubstringInvocations = true; + break; + default: + return false; + } + } + + return anySubstringInvocations; + } + bool IsAnyDirectOrConditionalSubstringInvocation(IOperation operation) { - var value = WalkDownBuiltInImplicitConversionOnConcatOperand(operation); - if (value is IConditionalAccessOperation conditionallAccessOperation) - value = conditionallAccessOperation.WhenNotNull; + if (operation is IConditionalAccessOperation conditionallAccessOperation) + operation = conditionallAccessOperation.WhenNotNull; - return value is IInvocationOperation invocation && symbols.IsAnySubstringMethod(invocation.TargetMethod); + return operation is IInvocationOperation invocation && symbols.IsAnySubstringMethod(invocation.TargetMethod); } } diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs index 97ba044c2c..feacc384a5 100644 --- a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcatTests.cs @@ -460,157 +460,6 @@ public Task NamedSubstringArguments_ArePreserved_VB(string substring, string asS return test.RunAsync(); } - public static IEnumerable Data_NonStringOperands_CS - { - get - { - yield return new[] - { - @"foo.Substring(1) + count", - @"string.Concat(foo.AsSpan(1), count.ToString())" - }; - yield return new[] - { - @"count + foo.Substring(1)", - @"string.Concat(count.ToString(), foo.AsSpan(1))" - }; - yield return new[] - { - @"thing + foo.Substring(1)", - @"string.Concat(thing?.ToString(), foo.AsSpan(1))" - }; - yield return new[] - { - @"foo.Substring(1) + thing", - @"string.Concat(foo.AsSpan(1), thing?.ToString())" - }; - yield return new[] - { - @"maybe + foo.Substring(1)", - @"string.Concat(maybe?.ToString(), foo.AsSpan(1))" - }; - yield return new[] - { - @"thing + foo.Substring(1, 2) + maybe", - @"string.Concat(thing?.ToString(), foo.AsSpan(1, 2), maybe?.ToString())" - }; - yield return new[] - { - @"foo + 17 + bar.Substring(1)", - @"string.Concat(foo, 17.ToString(), bar.AsSpan(1))" - }; - yield return new[] - { - @"foo.Substring(1, 2) + 17", - @"string.Concat(foo.AsSpan(1, 2), 17.ToString())" - }; - yield return new[] - { - @"foo + 3.14159 + bar.Substring(1)", - @"string.Concat(foo, 3.14159.ToString(), bar.AsSpan(1))" - }; - yield return new[] - { - @"3.14159 + foo.Substring(1) + 6.02e23", - @"string.Concat(3.14159.ToString(), foo.AsSpan(1), 6.02e23.ToString())" - }; - } - } - - [Theory] - [MemberData(nameof(Data_NonStringOperands_CS))] - public Task NonStringOperands_AreConvertedToString_CS(string testExpression, string fixedExpression) - { - string format = @" -int count = 11; -object thing = new object(); -TimeSpan? maybe = null; -string s = {0};"; - var culture = CultureInfo.InvariantCulture; - - var test = new VerifyCS.Test - { - TestCode = CSUsings + CSWithBody(string.Format(culture, format, $"{{|#0:{testExpression}|}}")), - FixedCode = CSUsings + CSWithBody(string.Format(culture, format, fixedExpression)), - ReferenceAssemblies = ReferenceAssemblies.Net.Net50, - ExpectedDiagnostics = { VerifyCS.Diagnostic(Rule).WithLocation(0) } - }; - return test.RunAsync(); - } - - public static IEnumerable Data_NonStringOperands_VB - { - get - { - yield return new[] - { - @"foo.Substring(1) & count", - @"String.Concat(foo.AsSpan(1), count.ToString())" - }; - yield return new[] - { - @"count & foo.Substring(1)", - @"String.Concat(count.ToString(), foo.AsSpan(1))" - }; - yield return new[] - { - @"thing & foo.Substring(1)", - @"String.Concat(thing?.ToString(), foo.AsSpan(1))" - }; - yield return new[] - { - @"foo.Substring(1) & thing", - @"String.Concat(foo.AsSpan(1), thing?.ToString())" - }; - yield return new[] - { - @"thing & foo.Substring(1, 2) & maybe", - @"String.Concat(thing?.ToString(), foo.AsSpan(1, 2), maybe?.ToString())" - }; - yield return new[] - { - @"foo & 17 & bar.Substring(1)", - @"String.Concat(foo, 17.ToString(), bar.AsSpan(1))" - }; - yield return new[] - { - @"foo.Substring(1, 2) & 17", - @"String.Concat(foo.AsSpan(1, 2), 17.ToString())" - }; - yield return new[] - { - @"foo & 3.14159 & bar.Substring(1)", - @"String.Concat(foo, 3.14159.ToString(), bar.AsSpan(1))" - }; - yield return new[] - { - @"3.14159 & foo.Substring(1) & 6.02e23", - @"String.Concat(3.14159.ToString(), foo.AsSpan(1), 6.02e23.ToString())" - }; - } - } - - [Theory] - [MemberData(nameof(Data_NonStringOperands_VB))] - public Task NonStringOperands_AreConvertedToString_VB(string testExpression, string fixedExpression) - { - string format = @" -Dim count As Integer = 11 -Dim thing As Object = New Object() -Dim maybe As TimeSpan? = Nothing -Dim s = {0}"; - var culture = CultureInfo.InvariantCulture; - - var test = new VerifyVB.Test - { - TestCode = VBUsings + VBWithBody(string.Format(culture, format, $"{{|#0:{testExpression}|}}")), - FixedCode = VBUsings + VBWithBody(string.Format(culture, format, fixedExpression)), - ReferenceAssemblies = ReferenceAssemblies.Net.Net50, - ExpectedDiagnostics = { VerifyVB.Diagnostic(Rule).WithLocation(0) } - }; - return test.RunAsync(); - } - [Theory] [InlineData(@"foo.Substring(1) + (string)explicitTo", @"string.Concat(foo.AsSpan(1), (string)explicitTo)")] [InlineData(@"(string)explicitTo + foo.Substring(1)", @"string.Concat((string)explicitTo, foo.AsSpan(1))")] @@ -715,6 +564,44 @@ public Task MixedAddAndConcatenateOperatorChains_AreReportedAndFixed_VB(string t }; return test.RunAsync(); } + + [Theory] + [InlineData(@"foo.Substring(1) + 'A'", @"string.Concat(foo.AsSpan(1), ""A"")")] + [InlineData(@"'A' + foo.Substring(1)", @"string.Concat(""A"", foo.AsSpan(1))")] + [InlineData(@"foo.Substring(1) + bar + 'A'", @"string.Concat(foo.AsSpan(1), bar, ""A"")")] + [InlineData(@"foo.Substring(1) + 'A' + bar", @"string.Concat(foo.AsSpan(1), ""A"", bar)")] + [InlineData(@"foo + 'A' + bar.Substring(1)", @"string.Concat(foo, ""A"", bar.AsSpan(1))")] + [InlineData(@"foo + bar.Substring(1) + 'A'", @"string.Concat(foo, bar.AsSpan(1), ""A"")")] + public Task CharLiterals_AreConvertedToStringLiterals_CS(string testExpression, string fixedExpression) + { + var test = new VerifyCS.Test + { + TestCode = CSUsings + CSWithBody($"string s = {{|#0:{testExpression}|}};"), + FixedCode = CSUsings + CSWithBody($"string s = {fixedExpression};"), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { VerifyCS.Diagnostic(Rule).WithLocation(0) } + }; + return test.RunAsync(); + } + + [Theory] + [InlineData(@"foo.Substring(1) & ""A""c", @"String.Concat(foo.AsSpan(1), ""A"")")] + [InlineData(@"""A""c & foo.Substring(1)", @"String.Concat(""A"", foo.AsSpan(1))")] + [InlineData(@"foo.Substring(1) & bar & ""A""c", @"String.Concat(foo.AsSpan(1), bar, ""A"")")] + [InlineData(@"foo.Substring(1) & ""A""c & bar", @"String.Concat(foo.AsSpan(1), ""A"", bar)")] + [InlineData(@"foo & ""A""c & bar.Substring(1)", @"String.Concat(foo, ""A"", bar.AsSpan(1))")] + [InlineData(@"foo & bar.Substring(1) & ""A""c", @"String.Concat(foo, bar.AsSpan(1), ""A"")")] + public Task CharLiterals_AreConvertedToStringLiterals_VB(string testExpression, string fixedExpression) + { + var test = new VerifyVB.Test + { + TestCode = VBUsings + VBWithBody($"Dim s = {{|#0:{testExpression}|}}"), + FixedCode = VBUsings + VBWithBody($"Dim s = {fixedExpression}"), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50, + ExpectedDiagnostics = { VerifyVB.Diagnostic(Rule).WithLocation(0) } + }; + return test.RunAsync(); + } #endregion #region No Diagnostic @@ -820,6 +707,60 @@ public Task OverloadedAddOperator_NoDiagnostic_CS(string expression) }; return test.RunAsync(); } + + [Theory] + [InlineData(@"foo.Substring(1) + thing")] + [InlineData(@"thing + foo.Substring(1)")] + [InlineData(@"foo.Substring(1) + count")] + [InlineData(@"count + foo.Substring(1)")] + [InlineData(@"foo.Substring(1) + charvar")] + [InlineData(@"charvar + foo.Substring(1)")] + [InlineData(@"foo.Substring(1) + bar + thing")] + [InlineData(@"foo + bar.Substring(1) + thing")] + [InlineData(@"foo.Substring(1) + thing + bar.Substring(1)")] + [InlineData(@"foo.Substring(1) + bar.Substring(1) + thing")] + public Task WithNonStringNonCharLiteralOperands_NoDiagnostic_CS(string expression) + { + string statements = $@" +object thing = new object(); +int count = 17; +char charvar = 'H'; +string s = {expression};"; + + var test = new VerifyCS.Test + { + TestCode = CSUsings + CSWithBody(statements), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }; + return test.RunAsync(); + } + + [Theory] + [InlineData(@"foo.Substring(1) & thing")] + [InlineData(@"thing & foo.Substring(1)")] + [InlineData(@"foo.Substring(1) & count")] + [InlineData(@"count & foo.Substring(1)")] + [InlineData(@"foo.Substring(1) & charvar")] + [InlineData(@"charvar & foo.Substring(1)")] + [InlineData(@"foo.Substring(1) & bar & thing")] + [InlineData(@"foo & bar.Substring(1) & thing")] + [InlineData(@"foo.Substring(1) & thing & bar.Substring(1)")] + [InlineData(@"foo.Substring(1) & bar.Substring(1) & thing")] + public Task WithNonStringNonCharLiteralOperands_NoDiagnostic_VB(string expression) + { + string statements = $@" +Dim thing As Object = New Object() +Dim count As Integer = 17 +Dim charvar As Char = ""H"" +Dim s As String = {expression}"; + + var test = new VerifyVB.Test + { + TestCode = VBUsings + VBWithBody(statements), + ReferenceAssemblies = ReferenceAssemblies.Net.Net50 + }; + return test.RunAsync(); + } #endregion #region Helpers diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.vb index 87354fb119..7defe13a23 100644 --- a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.vb +++ b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.vb @@ -21,7 +21,7 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime Dim current As IBinaryOperation Do current = parentBinaryOperation - parentBinaryOperation = TryCast(current.Parent, IBinaryOperation) + parentBinaryOperation = TryCast(WalkUpImplicitConversionToObject(current.Parent), IBinaryOperation) Loop While parentBinaryOperation IsNot Nothing AndAlso IsStringConcatOperation(parentBinaryOperation) rootBinaryOperation = current @@ -33,6 +33,17 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime 'OperatorKind will be Concatenate even when the "+" operator is used, provided both operands are strings. Return operation.OperatorKind = BinaryOperatorKind.Concatenate End Function + + Private Shared Function WalkUpImplicitConversionToObject(operation As IOperation) As IOperation + + Dim conversion = TryCast(operation, IConversionOperation) + If conversion IsNot Nothing AndAlso conversion.Type.SpecialType = SpecialType.System_Object AndAlso + conversion.IsImplicit AndAlso Not conversion.Conversion.IsUserDefined Then + Return conversion.Parent + Else + Return operation + End If + End Function End Class End Namespace From 043be6fd40f2796a448df70db53cae50e73b257a Mon Sep 17 00:00:00 2001 From: NewellClark Date: Fri, 5 Feb 2021 21:26:42 -0500 Subject: [PATCH 10/18] Remove unused using --- .../Runtime/UseSpanBasedStringConcat.Fixer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs index 8f8ca22ade..4f0c57fc10 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs @@ -8,7 +8,6 @@ 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; From 516259594ac031559b5d044bea4ca8414265cace Mon Sep 17 00:00:00 2001 From: NewellClark Date: Fri, 19 Feb 2021 13:20:07 -0500 Subject: [PATCH 11/18] Apply suggested changes from other PR Apply changes suggested by @Evangelink on 'Prefer AsSpan Over Substring' PR -Use better variable names in 'RequiredSymbols' -Don't use helper method for creating resource strings -Fix warning about diagnostic description that wasn't reported earlier due to helper methods. --- .../MicrosoftNetCoreAnalyzersResources.resx | 7 +- .../Runtime/UseSpanBasedStringConcat.Fixer.cs | 4 +- .../Runtime/UseSpanBasedStringConcat.cs | 75 +++++++++---------- .../MicrosoftNetCoreAnalyzersResources.cs.xlf | 9 ++- .../MicrosoftNetCoreAnalyzersResources.de.xlf | 9 ++- .../MicrosoftNetCoreAnalyzersResources.es.xlf | 9 ++- .../MicrosoftNetCoreAnalyzersResources.fr.xlf | 9 ++- .../MicrosoftNetCoreAnalyzersResources.it.xlf | 9 ++- .../MicrosoftNetCoreAnalyzersResources.ja.xlf | 9 ++- .../MicrosoftNetCoreAnalyzersResources.ko.xlf | 9 ++- .../MicrosoftNetCoreAnalyzersResources.pl.xlf | 9 ++- ...crosoftNetCoreAnalyzersResources.pt-BR.xlf | 9 ++- .../MicrosoftNetCoreAnalyzersResources.ru.xlf | 9 ++- .../MicrosoftNetCoreAnalyzersResources.tr.xlf | 9 ++- ...osoftNetCoreAnalyzersResources.zh-Hans.xlf | 9 ++- ...osoftNetCoreAnalyzersResources.zh-Hant.xlf | 9 ++- .../Microsoft.CodeAnalysis.NetAnalyzers.sarif | 4 +- 17 files changed, 135 insertions(+), 72 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx index a3955f94bb..9070333368 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx @@ -1511,7 +1511,7 @@ This call site is reachable on: 'windows' 10.0.2000 and later, and all other platforms - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' @@ -1519,4 +1519,7 @@ Use span-based 'string.Concat' - + + Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' + + \ No newline at end of file diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs index 4f0c57fc10..a1ca493ece 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs @@ -62,9 +62,9 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) return; var codeAction = CodeAction.Create( - Resx.UseSpanBasedStringConcatTitle, + Resx.UseSpanBasedStringConcatCodeFixTitle, FixConcatOperationChain, - Resx.UseSpanBasedStringConcatTitle); + Resx.UseSpanBasedStringConcatCodeFixTitle); context.RegisterCodeFix(codeAction, diagnostic); bool IsAnyNonConditionalSubstringInvocation(IOperation operation) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs index cc605f9c8d..48730b76f8 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs @@ -19,9 +19,9 @@ public abstract class UseSpanBasedStringConcat : DiagnosticAnalyzer { internal const string RuleId = "CA1841"; - private static readonly LocalizableString s_localizableTitle = CreateResource(nameof(Resx.UseSpanBasedStringConcatTitle)); - private static readonly LocalizableString s_localizableMessage = CreateResource(nameof(Resx.UseSpanBasedStringConcatMessage)); - private static readonly LocalizableString s_localizableDescription = CreateResource(nameof(Resx.UseSpanBasedStringConcatDescription)); + private static readonly LocalizableString s_localizableTitle = new LocalizableResourceString(nameof(Resx.UseSpanBasedStringConcatTitle), Resx.ResourceManager, typeof(Resx)); + private static readonly LocalizableString s_localizableMessage = new LocalizableResourceString(nameof(Resx.UseSpanBasedStringConcatMessage), Resx.ResourceManager, typeof(Resx)); + private static readonly LocalizableString s_localizableDescription = new LocalizableResourceString(nameof(Resx.UseSpanBasedStringConcatDescription), Resx.ResourceManager, typeof(Resx)); internal static readonly DiagnosticDescriptor Rule = DiagnosticDescriptorHelper.Create( RuleId, @@ -209,16 +209,16 @@ private RequiredSymbols( IMethodSymbol asSpan1, IMethodSymbol asSpan2) { StringType = stringType; - RoscharType = roscharType; - Substring1 = substring1; - Substring2 = substring2; - AsSpan1 = asSpan1; - AsSpan2 = asSpan2; + ReadOnlySpanOfCharType = roscharType; + SubstringStart = substring1; + SubstringStartLength = substring2; + AsSpanStart = asSpan1; + AsSpanStartLength = asSpan2; RoslynDebug.Assert( - StringType is not null && RoscharType is not null && - Substring1 is not null && Substring2 is not null && - AsSpan1 is not null && AsSpan2 is not null); + StringType is not null && ReadOnlySpanOfCharType is not null && + SubstringStart is not null && SubstringStartLength is not null && + AsSpanStart is not null && AsSpanStartLength is not null); } public static bool TryGetSymbols(Compilation compilation, out RequiredSymbols symbols) @@ -232,10 +232,10 @@ public static bool TryGetSymbols(Compilation compilation, out RequiredSymbols sy return false; } - var roscharType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemReadOnlySpan1)?.Construct(charType); + var readOnlySpanOfCharType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemReadOnlySpan1)?.Construct(charType); var memoryExtensionsType = compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemMemoryExtensions); - if (roscharType is null || memoryExtensionsType is null) + if (readOnlySpanOfCharType is null || memoryExtensionsType is null) { symbols = default; return false; @@ -245,57 +245,57 @@ public static bool TryGetSymbols(Compilation compilation, out RequiredSymbols sy var stringParamInfo = ParameterInfo.GetParameterInfo(stringType); var substringMembers = stringType.GetMembers(SubstringName).OfType(); - var substring1 = substringMembers.GetFirstOrDefaultMemberWithParameterInfos(intParamInfo); - var substring2 = substringMembers.GetFirstOrDefaultMemberWithParameterInfos(intParamInfo, intParamInfo); + var substringStart = substringMembers.GetFirstOrDefaultMemberWithParameterInfos(intParamInfo); + var substringStartLength = substringMembers.GetFirstOrDefaultMemberWithParameterInfos(intParamInfo, intParamInfo); var asSpanMembers = memoryExtensionsType.GetMembers(AsSpanName).OfType(); - var asSpan1 = asSpanMembers.GetFirstOrDefaultMemberWithParameterInfos(stringParamInfo, intParamInfo)?.ReduceExtensionMethod(stringType); - var asSpan2 = asSpanMembers.GetFirstOrDefaultMemberWithParameterInfos(stringParamInfo, intParamInfo, intParamInfo)?.ReduceExtensionMethod(stringType); + var asSpanStart = asSpanMembers.GetFirstOrDefaultMemberWithParameterInfos(stringParamInfo, intParamInfo)?.ReduceExtensionMethod(stringType); + var asSpanStartLength = asSpanMembers.GetFirstOrDefaultMemberWithParameterInfos(stringParamInfo, intParamInfo, intParamInfo)?.ReduceExtensionMethod(stringType); - if (substring1 is null || substring2 is null || asSpan1 is null || asSpan2 is null) + if (substringStart is null || substringStartLength is null || asSpanStart is null || asSpanStartLength is null) { symbols = default; return false; } symbols = new RequiredSymbols( - stringType, roscharType, - substring1, substring2, - asSpan1, asSpan2); + stringType, readOnlySpanOfCharType, + substringStart, substringStartLength, + asSpanStart, asSpanStartLength); return true; } public INamedTypeSymbol StringType { get; } - public INamedTypeSymbol RoscharType { get; } - public IMethodSymbol Substring1 { get; } - public IMethodSymbol Substring2 { get; } - public IMethodSymbol AsSpan1 { get; } - public IMethodSymbol AsSpan2 { get; } + public INamedTypeSymbol ReadOnlySpanOfCharType { get; } + public IMethodSymbol SubstringStart { get; } + public IMethodSymbol SubstringStartLength { get; } + public IMethodSymbol AsSpanStart { get; } + public IMethodSymbol AsSpanStartLength { get; } public IMethodSymbol? GetAsSpanEquivalent(IMethodSymbol? substringMethod) { - if (SymbolEqualityComparer.Default.Equals(substringMethod, Substring1)) - return AsSpan1; - if (SymbolEqualityComparer.Default.Equals(substringMethod, Substring2)) - return AsSpan2; + if (SymbolEqualityComparer.Default.Equals(substringMethod, SubstringStart)) + return AsSpanStart; + if (SymbolEqualityComparer.Default.Equals(substringMethod, SubstringStartLength)) + return AsSpanStartLength; return null; } public bool IsAnySubstringMethod(IMethodSymbol? method) { - return SymbolEqualityComparer.Default.Equals(method, Substring1) || - SymbolEqualityComparer.Default.Equals(method, Substring2); + return SymbolEqualityComparer.Default.Equals(method, SubstringStart) || + SymbolEqualityComparer.Default.Equals(method, SubstringStartLength); } public bool IsAnySubstringStartIndexParameter(IParameterSymbol? parameter) { - return SymbolEqualityComparer.Default.Equals(parameter, Substring1.Parameters.First()) || - SymbolEqualityComparer.Default.Equals(parameter, Substring2.Parameters.First()); + return SymbolEqualityComparer.Default.Equals(parameter, SubstringStart.Parameters.First()) || + SymbolEqualityComparer.Default.Equals(parameter, SubstringStartLength.Parameters.First()); } public bool TryGetRoscharConcatMethodWithArity(int arity, [NotNullWhen(true)] out IMethodSymbol? concatMethod) { - var roscharParamInfo = ParameterInfo.GetParameterInfo(RoscharType); + var roscharParamInfo = ParameterInfo.GetParameterInfo(ReadOnlySpanOfCharType); var argumentList = new ParameterInfo[arity]; for (int index = 0; index < arity; index++) argumentList[index] = roscharParamInfo; @@ -306,10 +306,5 @@ public bool TryGetRoscharConcatMethodWithArity(int arity, [NotNullWhen(true)] ou return concatMethod is not null; } } - - private static LocalizableString CreateResource(string resourceName) - { - return new LocalizableResourceString(resourceName, Resx.ResourceManager, typeof(Resx)); - } } } diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf index 740564b220..a59d7e044f 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf @@ -2237,9 +2237,14 @@ Pokud je to možné, zvažte použití řízení přístupu Azure na základě role namísto sdíleného přístupového podpisu (SAS). Pokud i přesto potřebujete používat sdílený přístupový podpis, zadejte SharedAccessProtocol.HttpsOnly. + + Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' + Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' + + - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf index 77ad01ba18..a2f0365edb 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf @@ -2237,9 +2237,14 @@ Erwägen Sie (sofern möglich) die Verwendung der rollenbasierten Zugriffssteuerung von Azure anstelle einer Shared Access Signature (SAS). Wenn Sie weiterhin eine SAS benötigen, verwenden Sie "SharedAccessProtocol.HttpsOnly". + + Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' + Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' + + - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf index 78a7be015a..575b5b4636 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf @@ -2237,9 +2237,14 @@ Considere la posibilidad de usar el control de acceso basado en rol de Azure en lugar de una firma de acceso compartido (SAS), si es posible. Si tiene que usar una firma de acceso compartido, especifique SharedAccessProtocol.HttpsOnly. + + Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' + Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' + + - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf index 440947f079..b388b389b2 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf @@ -2237,9 +2237,14 @@ Si possible, utilisez le contrôle d'accès en fonction du rôle d'Azure à la place d'une signature d'accès partagé. Si vous devez quand même utiliser une signature d'accès partagé, spécifiez SharedAccessProtocol.HttpsOnly. + + Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' + Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' + + - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf index 6ec762df7d..82dd9a588e 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf @@ -2237,9 +2237,14 @@ Se possibile, provare a usare il controllo degli accessi in base al ruolo di Azure, invece della firma di accesso condiviso. Se è necessario usare una firma di accesso condiviso, specificare SharedAccessProtocol.HttpsOnly. + + Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' + Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' + + - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf index 6647d41ed0..027e96ea9c 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf @@ -2237,9 +2237,14 @@ 可能な場合は、Shared Access Signature (SAS) の代わりに、Azure のロールベースのアクセス制御を使用することを検討してください。依然として SAS を使用する必要がある場合は、SharedAccessProtocol.HttpsOnly を指定します。 + + Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' + Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' + + - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf index 71d733fc1f..7c68bf1ed6 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf @@ -2237,9 +2237,14 @@ 가능한 경우 SAS(공유 액세스 서명) 대신 Azure의 역할 기반 액세스 제어를 사용하는 것이 좋습니다. 계속 SAS를 사용해야 하는 경우 SharedAccessProtocol.HttpsOnly를 지정하세요. + + Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' + Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' + + - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf index bcac6eadee..865128a2e6 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf @@ -2237,9 +2237,14 @@ Jeśli to możliwe, rozważ użycie kontroli dostępu opartej na rolach platformy Azure zamiast sygnatury dostępu współdzielonego (SAS). Jeśli nadal chcesz używać sygnatury SAS, określ właściwość SharedAccessProtocol.HttpsOnly. + + Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' + Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' + + - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf index 31260e8c1c..de4aa29552 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf @@ -2237,9 +2237,14 @@ Se possível, considere usar o controle de acesso baseado em função do Azure em vez de uma SAS (Assinatura de Acesso Compartilhado). Se você ainda precisar usar uma SAS, especifique SharedAccessProtocol.HttpsOnly. + + Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' + Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' + + - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf index 9f0c95bda3..273ba3a7da 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf @@ -2237,9 +2237,14 @@ Если возможно, попробуйте использовать управление доступом на основе ролей Azure, а не подписанный URL-адрес (SAS). Если все-таки требуется использовать SAS, укажите SharedAccessProtocol.HttpsOnly. + + Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' + Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' + + - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf index 5bd4e98049..54fe3080cd 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf @@ -2237,9 +2237,14 @@ Mümkünse Paylaşılan Erişim İmzası (SAS) yerine Azure'un rol tabanlı erişim denetimini kullanmayı düşünün. Yine de SAS kullanmanız gerekiyorsa, SharedAccessProtocol.HttpsOnly belirtin. + + Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' + Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' + + - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf index ce6bd686b6..583990c4ac 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf @@ -2237,9 +2237,14 @@ 如果可能,请考虑使用 Azure 基于角色的访问控制,而不是共享访问签名(SAS)。如果仍需使用 SAS,请指定 SharedAccessProtocol.HttpsOnly。 + + Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' + Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' + + - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf index 99035d25f7..d2d03756a7 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf @@ -2237,9 +2237,14 @@ 如果可行的話,請考慮從共用存取簽章 (SAS) 改為使用 Azure 的角色型存取控制。如果您仍需要使用 SAS,請指定 SharedAccessProtocol.HttpsOnly。 + + Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' + Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' + + - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif index 65e60a821c..bc0bd33c4e 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif @@ -231,7 +231,7 @@ "CA1841": { "id": "CA1841", "shortDescription": "Use span-based 'string.Concat'", - "fullDescription": "'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. ", + "fullDescription": "'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator.", "defaultLevel": "note", "helpUri": "https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1841", "properties": { @@ -5246,7 +5246,7 @@ "CA1841": { "id": "CA1841", "shortDescription": "Use span-based 'string.Concat'", - "fullDescription": "'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. ", + "fullDescription": "'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator.", "defaultLevel": "note", "helpUri": "https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1841", "properties": { From 822659c9ce1eb49f7a1a127fb591b6c412c95fd0 Mon Sep 17 00:00:00 2001 From: Newell Clark Date: Fri, 23 Apr 2021 15:07:55 -0400 Subject: [PATCH 12/18] Apply formatting changes Co-authored-by: Manish Vasani --- .../Runtime/UseSpanBasedStringConcat.Fixer.cs | 4 ++++ .../Runtime/UseSpanBasedStringConcat.cs | 7 ++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs index a1ca493ece..71c3f62bc9 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs @@ -48,11 +48,13 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) return; if (root.FindNode(context.Span, getInnermostNodeForTie: true) is not SyntaxNode concatExpressionSyntax) return; + // OperatorKind will be BinaryOperatorKind.Concatenate, even when '+' is used instead of '&' in Visual Basic. if (model.GetOperation(concatExpressionSyntax, cancellationToken) is not IBinaryOperation concatOperation || concatOperation.OperatorKind is not (BinaryOperatorKind.Add or BinaryOperatorKind.Concatenate)) return; var operands = UseSpanBasedStringConcat.FlattenBinaryOperation(concatOperation); + // Bail out if we don't have a long enough span-based string.Concat overload. if (!symbols.TryGetRoscharConcatMethodWithArity(operands.Length, out IMethodSymbol? roscharConcatMethod)) return; @@ -66,7 +68,9 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) FixConcatOperationChain, Resx.UseSpanBasedStringConcatCodeFixTitle); context.RegisterCodeFix(codeAction, diagnostic); + return; + // Local functions bool IsAnyNonConditionalSubstringInvocation(IOperation operation) { var value = UseSpanBasedStringConcat.WalkDownBuiltInImplicitConversionOnConcatOperand(operation); diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs index 48730b76f8..399837567c 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs @@ -59,7 +59,9 @@ private void OnCompilationStart(CompilationStartAnalysisContext context) return; context.RegisterOperationBlockStartAction(OnOperationBlockStart); + return; + // Local functions void OnOperationBlockStart(OperationBlockStartAnalysisContext context) { // Maintain set of all top-most concat operations so we don't report sub-expressions of an @@ -214,11 +216,6 @@ private RequiredSymbols( SubstringStartLength = substring2; AsSpanStart = asSpan1; AsSpanStartLength = asSpan2; - - RoslynDebug.Assert( - StringType is not null && ReadOnlySpanOfCharType is not null && - SubstringStart is not null && SubstringStartLength is not null && - AsSpanStart is not null && AsSpanStartLength is not null); } public static bool TryGetSymbols(Compilation compilation, out RequiredSymbols symbols) From 760cd4ae72962e87b41f328efb9b25e962e28f91 Mon Sep 17 00:00:00 2001 From: NewellClark Date: Sat, 24 Apr 2021 13:35:17 -0400 Subject: [PATCH 13/18] Apply requested changes - Apply Mavasani's changes - Remove unused method from fixers --- .../CSharpUseSpanBasedStringConcat.Fixer.cs | 11 +- .../Runtime/CSharpUseSpanBasedStringConcat.cs | 5 + .../MicrosoftNetCoreAnalyzersResources.resx | 4 +- .../Runtime/UseSpanBasedStringConcat.Fixer.cs | 15 ++- .../Runtime/UseSpanBasedStringConcat.cs | 108 +++++++++--------- .../MicrosoftNetCoreAnalyzersResources.cs.xlf | 8 +- .../MicrosoftNetCoreAnalyzersResources.de.xlf | 8 +- .../MicrosoftNetCoreAnalyzersResources.es.xlf | 8 +- .../MicrosoftNetCoreAnalyzersResources.fr.xlf | 8 +- .../MicrosoftNetCoreAnalyzersResources.it.xlf | 8 +- .../MicrosoftNetCoreAnalyzersResources.ja.xlf | 8 +- .../MicrosoftNetCoreAnalyzersResources.ko.xlf | 8 +- .../MicrosoftNetCoreAnalyzersResources.pl.xlf | 8 +- ...crosoftNetCoreAnalyzersResources.pt-BR.xlf | 8 +- .../MicrosoftNetCoreAnalyzersResources.ru.xlf | 8 +- .../MicrosoftNetCoreAnalyzersResources.tr.xlf | 8 +- ...osoftNetCoreAnalyzersResources.zh-Hans.xlf | 8 +- ...osoftNetCoreAnalyzersResources.zh-Hant.xlf | 8 +- .../BasicUseSpanBasedStringConcat.Fixer.vb | 7 +- .../Runtime/BasicUseSpanBasedStringConcat.vb | 5 + 20 files changed, 134 insertions(+), 125 deletions(-) diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpUseSpanBasedStringConcat.Fixer.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpUseSpanBasedStringConcat.Fixer.cs index 5c836de2dd..2a6898f627 100644 --- a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpUseSpanBasedStringConcat.Fixer.cs +++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpUseSpanBasedStringConcat.Fixer.cs @@ -1,10 +1,8 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; -using Analyzer.Utilities; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Operations; @@ -33,14 +31,9 @@ private protected override bool IsSystemNamespaceImported(Project project, IRead return false; } - private protected override SyntaxNode GenerateConditionalToStringInvocationExpression(SyntaxNode expression) + private protected override IOperation WalkDownBuiltInImplicitConversionOnConcatOperand(IOperation operand) { - RoslynDebug.Assert(expression is ExpressionSyntax, $"{nameof(expression)} must be C# {nameof(ExpressionSyntax)}."); - - var identifierName = SyntaxFactory.IdentifierName(ToStringName); - var memberBinding = SyntaxFactory.MemberBindingExpression(identifierName); - var invocation = SyntaxFactory.InvocationExpression(memberBinding, SyntaxFactory.ArgumentList()); - return SyntaxFactory.ConditionalAccessExpression((ExpressionSyntax)expression.WithoutTrivia(), invocation).WithTriviaFrom(expression); + return UseSpanBasedStringConcat.CSharpWalkDownBuiltInImplicitConversionOnConcatOperand(operand); } private protected override bool IsNamedArgument(IArgumentOperation argumentOperation) diff --git a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpUseSpanBasedStringConcat.cs b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpUseSpanBasedStringConcat.cs index 7e66d5f0f6..db26f01c6c 100644 --- a/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpUseSpanBasedStringConcat.cs +++ b/src/NetAnalyzers/CSharp/Microsoft.NetCore.Analyzers/Runtime/CSharpUseSpanBasedStringConcat.cs @@ -32,5 +32,10 @@ static bool IsConcatOperation(IBinaryOperation operation) operation.Type.SpecialType == SpecialType.System_String; } } + + private protected override IOperation WalkDownBuiltInImplicitConversionOnConcatOperand(IOperation operand) + { + return CSharpWalkDownBuiltInImplicitConversionOnConcatOperand(operand); + } } } diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx index 9070333368..4a564615e9 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx @@ -1511,7 +1511,7 @@ This call site is reachable on: 'windows' 10.0.2000 and later, and all other platforms - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. Use span-based 'string.Concat' and 'AsSpan' instead of 'Substring' @@ -1520,6 +1520,6 @@ Use span-based 'string.Concat' - Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' + Use 'AsSpan' with 'string.Concat' \ No newline at end of file diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs index 71c3f62bc9..5908aa22a6 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.Fixer.cs @@ -28,8 +28,7 @@ public abstract class UseSpanBasedStringConcatFixer : CodeFixProvider private protected abstract bool IsSystemNamespaceImported(Project project, IReadOnlyList namespaceImports); - /// Invoke ToString with the Elvis operator. - private protected abstract SyntaxNode GenerateConditionalToStringInvocationExpression(SyntaxNode expression); + private protected abstract IOperation WalkDownBuiltInImplicitConversionOnConcatOperand(IOperation operand); private protected abstract bool IsNamedArgument(IArgumentOperation argumentOperation); @@ -50,14 +49,18 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) return; // OperatorKind will be BinaryOperatorKind.Concatenate, even when '+' is used instead of '&' in Visual Basic. - if (model.GetOperation(concatExpressionSyntax, cancellationToken) is not IBinaryOperation concatOperation || concatOperation.OperatorKind is not (BinaryOperatorKind.Add or BinaryOperatorKind.Concatenate)) + if (model.GetOperation(concatExpressionSyntax, cancellationToken) is not IBinaryOperation concatOperation + || concatOperation.OperatorKind is not (BinaryOperatorKind.Add or BinaryOperatorKind.Concatenate)) + { return; + } var operands = UseSpanBasedStringConcat.FlattenBinaryOperation(concatOperation); // Bail out if we don't have a long enough span-based string.Concat overload. if (!symbols.TryGetRoscharConcatMethodWithArity(operands.Length, out IMethodSymbol? roscharConcatMethod)) return; + // Bail if none of the operands are a non-conditional substring invocation. This could be the case if the // only substring invocations in the expression were conditional invocations. if (!operands.Any(IsAnyNonConditionalSubstringInvocation)) @@ -68,12 +71,14 @@ public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) FixConcatOperationChain, Resx.UseSpanBasedStringConcatCodeFixTitle); context.RegisterCodeFix(codeAction, diagnostic); + return; // Local functions + bool IsAnyNonConditionalSubstringInvocation(IOperation operation) { - var value = UseSpanBasedStringConcat.WalkDownBuiltInImplicitConversionOnConcatOperand(operation); + var value = WalkDownBuiltInImplicitConversionOnConcatOperand(operation); return value is IInvocationOperation invocation && symbols.IsAnySubstringMethod(invocation.TargetMethod); } @@ -121,7 +126,7 @@ async Task FixConcatOperationChain(CancellationToken cancellationToken private SyntaxNode ConvertOperandToArgument(in RequiredSymbols symbols, SyntaxGenerator generator, IOperation operand) { - var value = UseSpanBasedStringConcat.WalkDownBuiltInImplicitConversionOnConcatOperand(operand); + var value = WalkDownBuiltInImplicitConversionOnConcatOperand(operand); // Convert substring invocations to equivalent AsSpan invocation. if (value is IInvocationOperation invocation && symbols.IsAnySubstringMethod(invocation.TargetMethod)) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs index 399837567c..c7b125f13f 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -33,10 +32,6 @@ public abstract class UseSpanBasedStringConcat : DiagnosticAnalyzer isPortedFxCopRule: false, isDataflowRule: false); - private const string SubstringName = nameof(string.Substring); - private const string AsSpanName = nameof(MemoryExtensions.AsSpan); - private const string ConcatName = nameof(string.Concat); - /// /// If the specified binary operation is a string concatenation operation, we try to walk up to the top-most /// string-concatenation operation that it is part of. If it is not a string-concatenation operation, we simply @@ -44,6 +39,13 @@ public abstract class UseSpanBasedStringConcat : DiagnosticAnalyzer /// private protected abstract bool TryGetTopMostConcatOperation(IBinaryOperation binaryOperation, [NotNullWhen(true)] out IBinaryOperation? rootBinaryOperation); + /// + /// Remove the built in implicit conversion on operands to concat. + /// In VB, the conversion can be to either string or object. + /// In C#, the conversion is always to object. + /// + private protected abstract IOperation WalkDownBuiltInImplicitConversionOnConcatOperand(IOperation operand); + public sealed override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(Rule); public sealed override void Initialize(AnalysisContext context) @@ -141,62 +143,64 @@ bool IsAnyDirectOrConditionalSubstringInvocation(IOperation operation) internal static ImmutableArray FlattenBinaryOperation(IBinaryOperation root) { - var stack = new Stack(); - var builder = ImmutableArray.CreateBuilder(); - GoLeft(root); + var walker = new BinaryOperandWalker(); + walker.Visit(root); - while (stack.Count != 0) - { - var current = stack.Pop(); + return walker.Operands.ToImmutable(); + } - if (current.LeftOperand is not IBinaryOperation leftBinary || leftBinary.OperatorKind != root.OperatorKind) - { - builder.Add(current.LeftOperand); - } + private sealed class BinaryOperandWalker : OperationWalker + { + private BinaryOperatorKind _operatorKind; + + public BinaryOperandWalker() : base() { } + + public ImmutableArray.Builder Operands { get; } = ImmutableArray.CreateBuilder(); - if (current.RightOperand is not IBinaryOperation rightBinary || rightBinary.OperatorKind != root.OperatorKind) + public override void DefaultVisit(IOperation operation) + { + Operands.Add(operation); + } + + public override void VisitBinaryOperator(IBinaryOperation operation) + { + if (_operatorKind is BinaryOperatorKind.None) { - builder.Add(current.RightOperand); + _operatorKind = operation.OperatorKind; } - else + else if (_operatorKind != operation.OperatorKind) { - GoLeft(rightBinary); + DefaultVisit(operation); + return; } + + Visit(operation.LeftOperand); + Visit(operation.RightOperand); } + } - return builder.ToImmutable(); + internal static IOperation CSharpWalkDownBuiltInImplicitConversionOnConcatOperand(IOperation operand) + { + if (operand is not IConversionOperation conversion) + return operand; + if (!conversion.IsImplicit || conversion.Conversion.IsUserDefined) + return conversion; + if (conversion.Type.SpecialType is SpecialType.System_Object) + return conversion.Operand; - void GoLeft(IBinaryOperation operation) - { - IBinaryOperation? current = operation; - while (current is not null && current.OperatorKind == root.OperatorKind) - { - stack.Push(current); - current = current.LeftOperand as IBinaryOperation; - } - } + return conversion; } - /// - /// Remove the built in implicit conversion on operands to concat. - /// In VB, the conversion can be to either string or object. - /// In C#, the conversion is always to object. - /// - internal static IOperation WalkDownBuiltInImplicitConversionOnConcatOperand(IOperation operand) + internal static IOperation BasicWalkDownBuiltInImplicitConversionOnConcatOperand(IOperation operand) { if (operand is not IConversionOperation conversion) return operand; if (!conversion.IsImplicit || conversion.Conversion.IsUserDefined) return conversion; + if (conversion.Type.SpecialType is SpecialType.System_Object or SpecialType.System_String) + return conversion.Operand; - switch (conversion.Language) - { - case LanguageNames.CSharp when conversion.Type.SpecialType is SpecialType.System_Object: - case LanguageNames.VisualBasic when conversion.Type.SpecialType is SpecialType.System_Object or SpecialType.System_String: - return conversion.Operand; - default: - return conversion; - } + return conversion; } // Use readonly struct instead of record type to save on allocations, since it's not passed by-value. @@ -207,15 +211,15 @@ internal readonly struct RequiredSymbols { private RequiredSymbols( INamedTypeSymbol stringType, INamedTypeSymbol roscharType, - IMethodSymbol substring1, IMethodSymbol substring2, - IMethodSymbol asSpan1, IMethodSymbol asSpan2) + IMethodSymbol substringStart, IMethodSymbol substringStartLength, + IMethodSymbol asSpanStart, IMethodSymbol asSpanStartLength) { StringType = stringType; ReadOnlySpanOfCharType = roscharType; - SubstringStart = substring1; - SubstringStartLength = substring2; - AsSpanStart = asSpan1; - AsSpanStartLength = asSpan2; + SubstringStart = substringStart; + SubstringStartLength = substringStartLength; + AsSpanStart = asSpanStart; + AsSpanStartLength = asSpanStartLength; } public static bool TryGetSymbols(Compilation compilation, out RequiredSymbols symbols) @@ -241,11 +245,11 @@ public static bool TryGetSymbols(Compilation compilation, out RequiredSymbols sy var intParamInfo = ParameterInfo.GetParameterInfo(compilation.GetSpecialType(SpecialType.System_Int32)); var stringParamInfo = ParameterInfo.GetParameterInfo(stringType); - var substringMembers = stringType.GetMembers(SubstringName).OfType(); + var substringMembers = stringType.GetMembers(nameof(string.Substring)).OfType(); var substringStart = substringMembers.GetFirstOrDefaultMemberWithParameterInfos(intParamInfo); var substringStartLength = substringMembers.GetFirstOrDefaultMemberWithParameterInfos(intParamInfo, intParamInfo); - var asSpanMembers = memoryExtensionsType.GetMembers(AsSpanName).OfType(); + var asSpanMembers = memoryExtensionsType.GetMembers(nameof(MemoryExtensions.AsSpan)).OfType(); var asSpanStart = asSpanMembers.GetFirstOrDefaultMemberWithParameterInfos(stringParamInfo, intParamInfo)?.ReduceExtensionMethod(stringType); var asSpanStartLength = asSpanMembers.GetFirstOrDefaultMemberWithParameterInfos(stringParamInfo, intParamInfo, intParamInfo)?.ReduceExtensionMethod(stringType); @@ -297,7 +301,7 @@ public bool TryGetRoscharConcatMethodWithArity(int arity, [NotNullWhen(true)] ou for (int index = 0; index < arity; index++) argumentList[index] = roscharParamInfo; - concatMethod = StringType.GetMembers(ConcatName) + concatMethod = StringType.GetMembers(nameof(string.Concat)) .OfType() .GetFirstOrDefaultMemberWithParameterInfos(argumentList); return concatMethod is not null; diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf index a59d7e044f..6a97a3ed40 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf @@ -2238,13 +2238,13 @@ - Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' - Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' + Use 'AsSpan' with 'string.Concat' + Use 'AsSpan' with 'string.Concat' - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf index a2f0365edb..fb21661d44 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf @@ -2238,13 +2238,13 @@ - Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' - Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' + Use 'AsSpan' with 'string.Concat' + Use 'AsSpan' with 'string.Concat' - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf index 575b5b4636..40283d61d8 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf @@ -2238,13 +2238,13 @@ - Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' - Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' + Use 'AsSpan' with 'string.Concat' + Use 'AsSpan' with 'string.Concat' - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf index b388b389b2..85df51fb07 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf @@ -2238,13 +2238,13 @@ - Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' - Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' + Use 'AsSpan' with 'string.Concat' + Use 'AsSpan' with 'string.Concat' - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf index 82dd9a588e..e089455da7 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf @@ -2238,13 +2238,13 @@ - Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' - Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' + Use 'AsSpan' with 'string.Concat' + Use 'AsSpan' with 'string.Concat' - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf index 027e96ea9c..6a08ffe660 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf @@ -2238,13 +2238,13 @@ - Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' - Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' + Use 'AsSpan' with 'string.Concat' + Use 'AsSpan' with 'string.Concat' - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf index 7c68bf1ed6..c6cce4e877 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf @@ -2238,13 +2238,13 @@ - Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' - Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' + Use 'AsSpan' with 'string.Concat' + Use 'AsSpan' with 'string.Concat' - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf index 865128a2e6..ee2ece36d2 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf @@ -2238,13 +2238,13 @@ - Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' - Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' + Use 'AsSpan' with 'string.Concat' + Use 'AsSpan' with 'string.Concat' - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf index de4aa29552..f48b9f4428 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf @@ -2238,13 +2238,13 @@ - Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' - Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' + Use 'AsSpan' with 'string.Concat' + Use 'AsSpan' with 'string.Concat' - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf index 273ba3a7da..cec1d554db 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf @@ -2238,13 +2238,13 @@ - Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' - Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' + Use 'AsSpan' with 'string.Concat' + Use 'AsSpan' with 'string.Concat' - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf index 54fe3080cd..5e4bc10bf9 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf @@ -2238,13 +2238,13 @@ - Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' - Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' + Use 'AsSpan' with 'string.Concat' + Use 'AsSpan' with 'string.Concat' - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf index 583990c4ac..e7b1eb772a 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf @@ -2238,13 +2238,13 @@ - Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' - Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' + Use 'AsSpan' with 'string.Concat' + Use 'AsSpan' with 'string.Concat' - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf index d2d03756a7..272d9ecd5d 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf @@ -2238,13 +2238,13 @@ - Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' - Replace concatenation with 'string.Concat' and replace 'Substring' with 'AsSpan' + Use 'AsSpan' with 'string.Concat' + Use 'AsSpan' with 'string.Concat' - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. - 'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.Fixer.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.Fixer.vb index f9fe9cca79..c299e80223 100644 --- a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.Fixer.vb +++ b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.Fixer.vb @@ -48,12 +48,9 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime Return False End Function - Private Protected Overrides Function GenerateConditionalToStringInvocationExpression(expression As SyntaxNode) As SyntaxNode + Private Protected Overrides Function WalkDownBuiltInImplicitConversionOnConcatOperand(operand As IOperation) As IOperation - Dim identifierName = SyntaxFactory.IdentifierName(ToStringName) - Dim simpleMemberAccess = SyntaxFactory.SimpleMemberAccessExpression(identifierName) - Dim invocation = SyntaxFactory.InvocationExpression(simpleMemberAccess, SyntaxFactory.ArgumentList()) - Return SyntaxFactory.ConditionalAccessExpression(DirectCast(expression, ExpressionSyntax).WithoutTrivia(), invocation).WithTriviaFrom(expression) + Return UseSpanBasedStringConcat.BasicWalkDownBuiltInImplicitConversionOnConcatOperand(operand) End Function Private Protected Overrides Function IsNamedArgument(argumentOperation As IArgumentOperation) As Boolean diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.vb index 7defe13a23..6d3bf839ea 100644 --- a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.vb +++ b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.vb @@ -28,6 +28,11 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime Return True End Function + Private Protected Overrides Function WalkDownBuiltInImplicitConversionOnConcatOperand(operand As IOperation) As IOperation + + Return BasicWalkDownBuiltInImplicitConversionOnConcatOperand(operand) + End Function + Private Shared Function IsStringConcatOperation(operation As IBinaryOperation) As Boolean 'OperatorKind will be Concatenate even when the "+" operator is used, provided both operands are strings. From 49894c3ec30a842acb870cd385f1bed444a1f302 Mon Sep 17 00:00:00 2001 From: NewellClark Date: Sat, 24 Apr 2021 14:12:46 -0400 Subject: [PATCH 14/18] Change ID to CA1845 --- .../Core/AnalyzerReleases.Unshipped.md | 2 +- .../Runtime/UseSpanBasedStringConcat.cs | 2 +- .../Microsoft.CodeAnalysis.NetAnalyzers.md | 4 ++-- .../Microsoft.CodeAnalysis.NetAnalyzers.sarif | 16 ++++++++-------- src/NetAnalyzers/RulesMissingDocumentation.md | 2 +- .../Compiler/DiagnosticCategoryAndIdRanges.txt | 2 +- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md index bf76e5ec66..08a3558ad3 100644 --- a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md +++ b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md @@ -3,7 +3,7 @@ ### New Rules Rule ID | Category | Severity | Notes --------|----------|----------|------- -CA1841 | Performance | Info | UseSpanBasedStringConcat, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1841) +CA1845 | Performance | Info | UseSpanBasedStringConcat, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1845) ### Removed Rules diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs index c7b125f13f..3805009526 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/UseSpanBasedStringConcat.cs @@ -16,7 +16,7 @@ namespace Microsoft.NetCore.Analyzers.Runtime { public abstract class UseSpanBasedStringConcat : DiagnosticAnalyzer { - internal const string RuleId = "CA1841"; + internal const string RuleId = "CA1845"; private static readonly LocalizableString s_localizableTitle = new LocalizableResourceString(nameof(Resx.UseSpanBasedStringConcatTitle), Resx.ResourceManager, typeof(Resx)); private static readonly LocalizableString s_localizableMessage = new LocalizableResourceString(nameof(Resx.UseSpanBasedStringConcatMessage), Resx.ResourceManager, typeof(Resx)); diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md index 65bdc233ad..03c0b7b0da 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md @@ -1296,9 +1296,9 @@ Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in |CodeFix|False| --- -## [CA1841](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1841): Use span-based 'string.Concat' +## [CA1845](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1845): Use span-based 'string.Concat' -'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator. +It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. |Item|Value| |-|-| diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif index bc0bd33c4e..f791b0fde7 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif @@ -228,12 +228,12 @@ ] } }, - "CA1841": { - "id": "CA1841", + "CA1845": { + "id": "CA1845", "shortDescription": "Use span-based 'string.Concat'", - "fullDescription": "'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator.", + "fullDescription": "It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator.", "defaultLevel": "note", - "helpUri": "https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1841", + "helpUri": "https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1845", "properties": { "category": "Performance", "isEnabledByDefault": true, @@ -5243,12 +5243,12 @@ ] } }, - "CA1841": { - "id": "CA1841", + "CA1845": { + "id": "CA1845", "shortDescription": "Use span-based 'string.Concat'", - "fullDescription": "'Substring' is O(n) in the length of the substring, whereas 'AsSpan' is O(1). When using the result of 'Substring' in a string concatenation, it is more efficient to instead call 'AsSpan' and use 'string.Concat', instead of 'Substring' and a concatenation operator.", + "fullDescription": "It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator.", "defaultLevel": "note", - "helpUri": "https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1841", + "helpUri": "https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1845", "properties": { "category": "Performance", "isEnabledByDefault": true, diff --git a/src/NetAnalyzers/RulesMissingDocumentation.md b/src/NetAnalyzers/RulesMissingDocumentation.md index 2cbae72ee6..6da3dcc957 100644 --- a/src/NetAnalyzers/RulesMissingDocumentation.md +++ b/src/NetAnalyzers/RulesMissingDocumentation.md @@ -2,4 +2,4 @@ Rule ID | Missing Help Link | Title | --------|-------------------|-------| -CA1841 | | Use span-based 'string.Concat' | +CA1845 | | Use span-based 'string.Concat' | diff --git a/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt b/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt index 6847720374..6b4f355995 100644 --- a/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt +++ b/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt @@ -12,7 +12,7 @@ Design: CA2210, CA1000-CA1070 Globalization: CA2101, CA1300-CA1310 Mobility: CA1600-CA1601 -Performance: HA, CA1800-CA1841 +Performance: HA, CA1800-CA1845 Security: CA2100-CA2153, CA2300-CA2330, CA3000-CA3147, CA5300-CA5403 Usage: CA1801, CA1806, CA1816, CA2200-CA2209, CA2211-CA2249 Naming: CA1700-CA1726 From e4867b8bce4e92e9dbcbc98ed77df2018196c215 Mon Sep 17 00:00:00 2001 From: NewellClark Date: Sat, 24 Apr 2021 18:07:37 -0400 Subject: [PATCH 15/18] . --- .../MicrosoftNetCoreAnalyzersResources.resx | 2 +- .../xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf | 5 +++-- .../xlf/MicrosoftNetCoreAnalyzersResources.de.xlf | 5 +++-- .../xlf/MicrosoftNetCoreAnalyzersResources.es.xlf | 5 +++-- .../xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf | 5 +++-- .../xlf/MicrosoftNetCoreAnalyzersResources.it.xlf | 5 +++-- .../xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf | 5 +++-- .../xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf | 5 +++-- .../xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf | 5 +++-- .../xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf | 5 +++-- .../xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf | 5 +++-- .../xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf | 5 +++-- .../xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf | 5 +++-- .../xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf | 5 +++-- 14 files changed, 40 insertions(+), 27 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx index 55266f2d97..860a52f516 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx @@ -1601,4 +1601,4 @@ Replace 'WhenAll' call with argument - + \ No newline at end of file diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf index 3b5cf7d44b..a862cd6677 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf @@ -2348,8 +2348,8 @@ - It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. - It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. @@ -2360,6 +2360,7 @@ Use span-based 'string.Concat' Use span-based 'string.Concat' + Platform compatibility analyzer requires a valid platform name and version. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf index f13023de7f..09fcf932cd 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf @@ -2348,8 +2348,8 @@ - It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. - It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. @@ -2360,6 +2360,7 @@ Use span-based 'string.Concat' Use span-based 'string.Concat' + Platform compatibility analyzer requires a valid platform name and version. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf index ffc4941bfe..7b71f6cef3 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf @@ -2348,8 +2348,8 @@ - It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. - It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. @@ -2360,6 +2360,7 @@ Use span-based 'string.Concat' Use span-based 'string.Concat' + Platform compatibility analyzer requires a valid platform name and version. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf index 6aaa3ecd0a..c0b648ba26 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf @@ -2348,8 +2348,8 @@ - It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. - It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. @@ -2360,6 +2360,7 @@ Use span-based 'string.Concat' Use span-based 'string.Concat' + Platform compatibility analyzer requires a valid platform name and version. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf index 777828cef9..71af9825f4 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf @@ -2348,8 +2348,8 @@ - It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. - It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. @@ -2360,6 +2360,7 @@ Use span-based 'string.Concat' Use span-based 'string.Concat' + Platform compatibility analyzer requires a valid platform name and version. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf index e4a1c07ebc..01062d6517 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf @@ -2348,8 +2348,8 @@ - It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. - It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. @@ -2360,6 +2360,7 @@ Use span-based 'string.Concat' Use span-based 'string.Concat' + Platform compatibility analyzer requires a valid platform name and version. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf index 26bf89c41b..76b65720c7 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf @@ -2348,8 +2348,8 @@ - It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. - It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. @@ -2360,6 +2360,7 @@ Use span-based 'string.Concat' Use span-based 'string.Concat' + Platform compatibility analyzer requires a valid platform name and version. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf index 118cfea845..0b7c1adfd3 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf @@ -2348,8 +2348,8 @@ - It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. - It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. @@ -2360,6 +2360,7 @@ Use span-based 'string.Concat' Use span-based 'string.Concat' + Platform compatibility analyzer requires a valid platform name and version. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf index 18d80c816e..6a618b3f8b 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf @@ -2348,8 +2348,8 @@ - It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. - It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. @@ -2360,6 +2360,7 @@ Use span-based 'string.Concat' Use span-based 'string.Concat' + Platform compatibility analyzer requires a valid platform name and version. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf index c44873b0f2..42d2a09c13 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf @@ -2348,8 +2348,8 @@ - It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. - It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. @@ -2360,6 +2360,7 @@ Use span-based 'string.Concat' Use span-based 'string.Concat' + Platform compatibility analyzer requires a valid platform name and version. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf index 67e93b8a00..10a7aecb28 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf @@ -2348,8 +2348,8 @@ - It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. - It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. @@ -2360,6 +2360,7 @@ Use span-based 'string.Concat' Use span-based 'string.Concat' + Platform compatibility analyzer requires a valid platform name and version. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf index c68cfc45b1..e663555362 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf @@ -2348,8 +2348,8 @@ - It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. - It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. @@ -2360,6 +2360,7 @@ Use span-based 'string.Concat' Use span-based 'string.Concat' + Platform compatibility analyzer requires a valid platform name and version. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf index 7ab6583501..d911f1bab5 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf @@ -2348,8 +2348,8 @@ - It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. - It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. @@ -2360,6 +2360,7 @@ Use span-based 'string.Concat' Use span-based 'string.Concat' + Platform compatibility analyzer requires a valid platform name and version. From 2b714609bbdb4167f89487096be36702fa0ff033 Mon Sep 17 00:00:00 2001 From: NewellClark Date: Sat, 24 Apr 2021 18:11:18 -0400 Subject: [PATCH 16/18] Rebuild xlf files to fix CI --- .../xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf | 4 ++-- .../xlf/MicrosoftNetCoreAnalyzersResources.de.xlf | 4 ++-- .../xlf/MicrosoftNetCoreAnalyzersResources.es.xlf | 4 ++-- .../xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf | 4 ++-- .../xlf/MicrosoftNetCoreAnalyzersResources.it.xlf | 4 ++-- .../xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf | 4 ++-- .../xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf | 4 ++-- .../xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf | 4 ++-- .../xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf | 4 ++-- .../xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf | 4 ++-- .../xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf | 4 ++-- .../xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf | 4 ++-- .../xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf | 4 ++-- 13 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf index a862cd6677..9f1a3bcf8b 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf @@ -2348,8 +2348,8 @@ - Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. - Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf index 09fcf932cd..27f034fb08 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf @@ -2348,8 +2348,8 @@ - Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. - Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf index 7b71f6cef3..b839fd1c1b 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf @@ -2348,8 +2348,8 @@ - Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. - Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf index c0b648ba26..0b4fb6d3b8 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf @@ -2348,8 +2348,8 @@ - Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. - Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf index 71af9825f4..4574565a53 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf @@ -2348,8 +2348,8 @@ - Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. - Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf index 01062d6517..feeb63df00 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf @@ -2348,8 +2348,8 @@ - Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. - Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf index 76b65720c7..3cdba0f82f 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf @@ -2348,8 +2348,8 @@ - Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. - Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf index 0b7c1adfd3..415a704204 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf @@ -2348,8 +2348,8 @@ - Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. - Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf index 6a618b3f8b..cb8ef922b8 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pt-BR.xlf @@ -2348,8 +2348,8 @@ - Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. - Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf index 42d2a09c13..63db8c60e1 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf @@ -2348,8 +2348,8 @@ - Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. - Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf index 10a7aecb28..6bf7d7da8d 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf @@ -2348,8 +2348,8 @@ - Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. - Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf index e663555362..cacf403b9c 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hans.xlf @@ -2348,8 +2348,8 @@ - Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. - Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf index d911f1bab5..18831f0fe7 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.zh-Hant.xlf @@ -2348,8 +2348,8 @@ - Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. - Its more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. + It is more efficient to use 'AsSpan' and 'string.Concat', instead of 'Substring' and a concatenation operator. From 268acdcbd0255f01d3bd7170c4bd20c046e51102 Mon Sep 17 00:00:00 2001 From: NewellClark Date: Mon, 10 May 2021 11:29:19 -0400 Subject: [PATCH 17/18] Import System namespace correctly Use case-insensitive string comparison to import System namespace in the VB fixer. --- .../Runtime/BasicUseSpanBasedStringConcat.Fixer.vb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.Fixer.vb b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.Fixer.vb index c299e80223..70cb5e3ee0 100644 --- a/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.Fixer.vb +++ b/src/NetAnalyzers/VisualBasic/Microsoft.NetCore.Analyzers/Runtime/BasicUseSpanBasedStringConcat.Fixer.vb @@ -25,7 +25,7 @@ Namespace Microsoft.NetCore.VisualBasic.Analyzers.Runtime Private Protected Overrides Function IsSystemNamespaceImported(project As Project, namespaceImports As IReadOnlyList(Of SyntaxNode)) As Boolean Dim options = DirectCast(project.CompilationOptions, VisualBasicCompilationOptions) - If options.GlobalImports.Any(Function(x) x.Name = NameOf(System)) Then + If options.GlobalImports.Any(Function(x) String.Compare(x.Name, NameOf(System), StringComparison.OrdinalIgnoreCase) = 0) Then Return True End If From 092b95d6b29686bddc7e7bbcb7b2d1bf89a97810 Mon Sep 17 00:00:00 2001 From: Newell Clark Date: Mon, 17 May 2021 11:48:09 -0400 Subject: [PATCH 18/18] Update src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md Co-authored-by: Manish Vasani --- src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md index 6a7659e4fb..415524a426 100644 --- a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md +++ b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md @@ -8,10 +8,6 @@ CA1840 | Performance | Info | UseEnvironmentMembers, [Documentation](https://doc CA1841 | Performance | Info | PreferDictionaryContainsMethods, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1841) CA1842 | Performance | Info | DoNotUseWhenAllOrWaitAllWithSingleArgument, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1842) CA1843 | Performance | Info | DoNotUseWhenAllOrWaitAllWithSingleArgument, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1843) - -### New Rules -Rule ID | Category | Severity | Notes ---------|----------|----------|------- CA1845 | Performance | Info | UseSpanBasedStringConcat, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1845) ### Removed Rules