diff --git a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md index 78144c82c5..272ba8e499 100644 --- a/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md +++ b/src/NetAnalyzers/Core/AnalyzerReleases.Unshipped.md @@ -13,6 +13,7 @@ CA1857 | Performance | Warning | ConstantExpectedAnalyzer, [Documentation](https CA1858 | Performance | Info | UseStartsWithInsteadOfIndexOfComparisonWithZero, [Documentation](https://docs.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1858) CA1859 | Performance | Info | UseConcreteTypeAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1859) CA1860 | Performance | Info | PreferLengthCountIsEmptyOverAnyAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1860) +CA1861 | Performance | Info | AvoidConstArrays, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1861) CA2021 | Reliability | Warning | DoNotCallEnumerableCastOrOfTypeWithIncompatibleTypesAnalyzer, [Documentation](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2021) ### Removed Rules diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx index 0b29bb7bce..76fa46218a 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/MicrosoftNetCoreAnalyzersResources.resx @@ -213,6 +213,20 @@ Avoid unsealed attributes + + Avoid constant arrays as arguments + + + Extract to static readonly field + + + Constant arrays passed as arguments are not reused which implies a performance overhead. Consider extracting them to 'static readonly' fields to improve performance. + {Locked="static readonly"} + + + Prefer 'static readonly' fields over local constant array arguments + {Locked="static readonly"} + Test for empty strings using string length @@ -2040,4 +2054,4 @@ Widening and user defined conversions are not supported with generic types. Prefer an 'IsEmpty' check rather than using 'Any()', both for clarity and for performance - \ No newline at end of file + diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs new file mode 100644 index 0000000000..8d6b06338c --- /dev/null +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.Fixer.cs @@ -0,0 +1,184 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Globalization; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Analyzer.Utilities; +using Analyzer.Utilities.Extensions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Operations; + +namespace Microsoft.NetCore.Analyzers.Runtime +{ + /// + /// CA1861: Avoid constant arrays as arguments. Replace with static readonly arrays. + /// + [ExportCodeFixProvider(LanguageNames.CSharp, LanguageNames.VisualBasic), Shared] + public sealed class AvoidConstArraysFixer : CodeFixProvider + { + public sealed override ImmutableArray FixableDiagnosticIds { get; } = ImmutableArray.Create(AvoidConstArraysAnalyzer.RuleId); + + private static readonly ImmutableArray s_collectionMemberEndings = ImmutableArray.Create("array", "collection", "enumerable", "list"); + + // See https://github.com/dotnet/roslyn/blob/main/docs/analyzers/FixAllProvider.md for more information on Fix All Providers + public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + public override async Task RegisterCodeFixesAsync(CodeFixContext context) + { + Document document = context.Document; + SyntaxNode root = await document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); + SyntaxNode node = root.FindNode(context.Span); + + context.RegisterCodeFix(CodeAction.Create( + MicrosoftNetCoreAnalyzersResources.AvoidConstArraysCodeFixTitle, + async ct => await ExtractConstArrayAsync(document, root, node, context.Diagnostics[0].Properties, ct).ConfigureAwait(false), + equivalenceKey: nameof(MicrosoftNetCoreAnalyzersResources.AvoidConstArraysCodeFixTitle)), + context.Diagnostics); + } + + private static async Task ExtractConstArrayAsync(Document document, SyntaxNode root, SyntaxNode node, + ImmutableDictionary properties, CancellationToken cancellationToken) + { + SemanticModel model = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + DocumentEditor editor = await DocumentEditor.CreateAsync(document, cancellationToken).ConfigureAwait(false); + SyntaxGenerator generator = editor.Generator; + IArrayCreationOperation arrayArgument = GetArrayCreationOperation(node, model, cancellationToken, out bool isInvoked); + INamedTypeSymbol containingType = model.GetEnclosingSymbol(node.SpanStart, cancellationToken).ContainingType; + + // Get a valid member name for the extracted constant + string newMemberName = GetExtractedMemberName(containingType.MemberNames, properties["paramName"] ?? GetMemberNameFromType(arrayArgument)); + + // Get method containing the symbol that is being diagnosed + IOperation? methodContext = arrayArgument.GetAncestor(OperationKind.MethodBody); + methodContext ??= arrayArgument.GetAncestor(OperationKind.Block); // VB methods have a different structure than CS methods + + // Create the new member + SyntaxNode newMember = generator.FieldDeclaration( + newMemberName, + generator.TypeExpression(arrayArgument.Type), + GetAccessibility(methodContext is null ? null : model.GetEnclosingSymbol(methodContext.Syntax.SpanStart, cancellationToken)), + DeclarationModifiers.Static | DeclarationModifiers.ReadOnly, + arrayArgument.Syntax.WithoutTrailingTrivia() // don't include extra trivia before the end of the declaration + ); + + // Add any additional formatting + if (methodContext is not null) + { + newMember = newMember.FormatForExtraction(methodContext.Syntax); + } + + ISymbol lastFieldOrPropertySymbol = containingType.GetMembers().LastOrDefault(x => x is IFieldSymbol or IPropertySymbol); + if (lastFieldOrPropertySymbol is not null) + { + var span = lastFieldOrPropertySymbol.Locations.First().SourceSpan; + if (root.FullSpan.Contains(span)) + { + // Insert after fields or properties + SyntaxNode lastFieldOrPropertyNode = root.FindNode(span); + editor.InsertAfter(generator.GetDeclaration(lastFieldOrPropertyNode), newMember); + } + else + { + // Span not found + editor.InsertBefore(methodContext?.Syntax, newMember); + } + } + else + { + // No fields or properties, insert right before the containing method for simplicity + editor.InsertBefore(methodContext?.Syntax, newMember); + } + + // Replace argument with a reference to our new member + SyntaxNode identifier = generator.IdentifierName(newMemberName); + if (isInvoked) + { + editor.ReplaceNode(node, generator.WithExpression(identifier, node)); + } + else + { + // add any extra trivia that was after the original argument + editor.ReplaceNode(node, generator.Argument(identifier).WithTriviaFrom(arrayArgument.Syntax)); + } + + // Return changed document + return editor.GetChangedDocument(); + } + + private static IArrayCreationOperation GetArrayCreationOperation(SyntaxNode node, SemanticModel model, CancellationToken cancellationToken, out bool isInvoked) + { + // The analyzer only passes a diagnostic for two scenarios, each having an IArrayCreationOperation: + // 1. The node is an IArgumentOperation that is a direct parent of an IArrayCreationOperation + // 2. The node is an IArrayCreationOperation already, as it was pulled from an + // invocation, like with LINQ extension methods + + // If this is a LINQ invocation, the node is already an IArrayCreationOperation + if (model.GetOperation(node, cancellationToken) is IArrayCreationOperation arrayCreation) + { + isInvoked = true; + return arrayCreation; + } + // Otherwise, we'll get the IArrayCreationOperation from the argument node's child + isInvoked = false; + return (IArrayCreationOperation)model.GetOperation(node.ChildNodes().First(), cancellationToken); + } + + private static string GetExtractedMemberName(IEnumerable memberNames, string parameterName) + { + bool hasCollectionEnding = s_collectionMemberEndings.Any(x => parameterName.EndsWith(x, true, CultureInfo.InvariantCulture)); + + if (parameterName == "source" // for LINQ, "sourceArray" is clearer than "source" + || (memberNames.Contains(parameterName) && !hasCollectionEnding)) + { + parameterName += "Array"; + } + + if (memberNames.Contains(parameterName)) + { + int suffix = 0; + while (memberNames.Contains(parameterName + suffix)) + { + suffix++; + } + + return parameterName + suffix; + } + + return parameterName; + } + + private static string GetMemberNameFromType(IArrayCreationOperation arrayCreationOperation) + { +#pragma warning disable CA1308 // Normalize strings to uppercase + return ((IArrayTypeSymbol)arrayCreationOperation.Type).ElementType.OriginalDefinition.Name.ToLowerInvariant() + "Array"; +#pragma warning restore CA1308 // Normalize strings to uppercase + } + + private static Accessibility GetAccessibility(ISymbol? methodSymbol) + { + // If private or public, return private since public accessibility not wanted for fields by default + return methodSymbol?.GetResultantVisibility() switch + { + // Return internal if internal + SymbolVisibility.Internal => Accessibility.Internal, + _ => Accessibility.Private + }; + } + } + + internal static class SyntaxNodeExtensions + { + internal static SyntaxNode FormatForExtraction(this SyntaxNode node, SyntaxNode previouslyContainingNode) + { + return node.HasTrailingTrivia ? node : node.WithTrailingTrivia(previouslyContainingNode.GetTrailingTrivia()); + } + } +} diff --git a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.cs b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.cs new file mode 100644 index 0000000000..98a7095075 --- /dev/null +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArrays.cs @@ -0,0 +1,153 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Linq; +using System.Collections.Generic; +using System.Collections.Immutable; +using Analyzer.Utilities; +using Analyzer.Utilities.Extensions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; + +namespace Microsoft.NetCore.Analyzers.Runtime +{ + using static MicrosoftNetCoreAnalyzersResources; + + /// + /// CA1861: Avoid constant arrays as arguments. Replace with static readonly arrays. + /// + [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)] + public sealed class AvoidConstArraysAnalyzer : DiagnosticAnalyzer + { + internal const string RuleId = "CA1861"; + + internal static readonly DiagnosticDescriptor Rule = DiagnosticDescriptorHelper.Create(RuleId, + CreateLocalizableResourceString(nameof(AvoidConstArraysTitle)), + CreateLocalizableResourceString(nameof(AvoidConstArraysMessage)), + DiagnosticCategory.Performance, + RuleLevel.IdeSuggestion, + CreateLocalizableResourceString(nameof(AvoidConstArraysDescription)), + isPortedFxCopRule: false, + isDataflowRule: false); + + public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); + + public override void Initialize(AnalysisContext context) + { + context.EnableConcurrentExecution(); + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + + context.RegisterCompilationStartAction(context => + { + INamedTypeSymbol? readonlySpanType = context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemReadOnlySpan1); + INamedTypeSymbol? functionType = context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemFunc2); + + // Analyzes an argument operation + context.RegisterOperationAction(context => + { + IArgumentOperation? argumentOperation; + + if (context.ContainingSymbol is IMethodSymbol method && method.MethodKind == MethodKind.StaticConstructor) + { + return; + } + + if (context.Operation is IArrayCreationOperation arrayCreationOperation) // For arrays passed as arguments + { + argumentOperation = arrayCreationOperation.GetAncestor(OperationKind.Argument); + + // If no argument, return + // If argument is passed as a params array but isn't itself an array, return + if (argumentOperation is null || (argumentOperation.Parameter.IsParams && arrayCreationOperation.IsImplicit)) + { + return; + } + } + else if (context.Operation is IInvocationOperation invocationOperation) // For arrays passed in extension methods, like in LINQ + { + IEnumerable invocationDescendants = invocationOperation.Descendants(); + if (invocationDescendants.Any(x => x is IArrayCreationOperation) + && invocationDescendants.Any(x => x is IArgumentOperation)) + { + // This is an invocation that contains an array as an argument + // This will get caught by the first case in another cycle + return; + } + + argumentOperation = invocationOperation.Arguments.FirstOrDefault(); + if (argumentOperation is not null) + { + if (argumentOperation.Value is not IConversionOperation conversionOperation + || conversionOperation.Operand is not IArrayCreationOperation arrayCreation) + { + return; + } + + arrayCreationOperation = arrayCreation; + } + else // An invocation, extension or regular, has an argument, unless it's a VB extension method call + { + // For VB extension method invocations, find a matching child + arrayCreationOperation = (IArrayCreationOperation)invocationDescendants + .FirstOrDefault(x => x is IArrayCreationOperation); + if (arrayCreationOperation is null) + { + return; + } + } + } + else + { + return; + } + + // Must be literal array + if (arrayCreationOperation.Initializer.ElementValues.Any(x => x is not ILiteralOperation)) + { + return; + } + + string? paramName = null; + if (argumentOperation is not null) + { + IFieldInitializerOperation? fieldInitializer = argumentOperation.GetAncestor( + OperationKind.FieldInitializer, f => f.InitializedFields.Any(x => x.IsReadOnly)); + IPropertyInitializerOperation? propertyInitializer = argumentOperation.GetAncestor( + OperationKind.PropertyInitializer, p => p.InitializedProperties.Any(x => x.IsReadOnly)); + + if (fieldInitializer is not null || propertyInitializer is not null) + { + return; + } + + ITypeSymbol originalDefinition = argumentOperation.Parameter.Type.OriginalDefinition; + + // Can't be a ReadOnlySpan, as those are already optimized + if (SymbolEqualityComparer.Default.Equals(readonlySpanType, originalDefinition)) + { + return; + } + + // Check if the parameter is a function so the name can be set to null + // Otherwise, the parameter name doesn't reflect the array creation as well + bool isDirectlyInsideLambda = originalDefinition.Equals(functionType); + + // Parameter shouldn't have same containing type as the context, to prevent naming ambiguity + // Ignore parameter name if we're inside a lambda function + if (!isDirectlyInsideLambda && !argumentOperation.Parameter.ContainingType.Equals(context.ContainingSymbol.ContainingType)) + { + paramName = argumentOperation.Parameter.Name; + } + } + + ImmutableDictionary.Builder properties = ImmutableDictionary.CreateBuilder(); + properties.Add("paramName", paramName); + + context.ReportDiagnostic(arrayCreationOperation.CreateDiagnostic(Rule, properties.ToImmutable())); + }, + OperationKind.ArrayCreation, + OperationKind.Invocation); + }); + } + } +} 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 3662da9d4b..abf9c7c550 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.cs.xlf @@ -47,6 +47,26 @@ Literály řetězců atributů by se měly správně parsovat + + Extract to static readonly field + Extract to static readonly field + + + + Constant arrays passed as arguments are not reused which implies a performance overhead. Consider extracting them to 'static readonly' fields to improve performance. + Constant arrays passed as arguments are not reused which implies a performance overhead. Consider extracting them to 'static readonly' fields to improve performance. + {Locked="static readonly"} + + + Prefer 'static readonly' fields over local constant array arguments + Prefer 'static readonly' fields over local constant array arguments + {Locked="static readonly"} + + + Avoid constant arrays as arguments + Avoid constant arrays as arguments + + Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in multiple allocations for one marshalling operation. Zařazením parametru StringBuilder se vždy vytvoří kopie nativní vyrovnávací paměti, která bude mít za následek vícenásobné přidělení pro jednu operaci zařazování. 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 26b3954eca..633e7fd896 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.de.xlf @@ -47,6 +47,26 @@ Attributzeichenfolgenliterale müssen richtig analysiert werden + + Extract to static readonly field + Extract to static readonly field + + + + Constant arrays passed as arguments are not reused which implies a performance overhead. Consider extracting them to 'static readonly' fields to improve performance. + Constant arrays passed as arguments are not reused which implies a performance overhead. Consider extracting them to 'static readonly' fields to improve performance. + {Locked="static readonly"} + + + Prefer 'static readonly' fields over local constant array arguments + Prefer 'static readonly' fields over local constant array arguments + {Locked="static readonly"} + + + Avoid constant arrays as arguments + Avoid constant arrays as arguments + + Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in multiple allocations for one marshalling operation. Beim Marshalling von "StringBuilder" wird immer eine native Pufferkopie erstellt, sodass mehrere Zuordnungen für einen Marshallingvorgang vorhanden sind. 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 03d64c8ab0..1f44b014f6 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.es.xlf @@ -47,6 +47,26 @@ Los literales de cadena de atributo se deben analizar correctamente + + Extract to static readonly field + Extract to static readonly field + + + + Constant arrays passed as arguments are not reused which implies a performance overhead. Consider extracting them to 'static readonly' fields to improve performance. + Constant arrays passed as arguments are not reused which implies a performance overhead. Consider extracting them to 'static readonly' fields to improve performance. + {Locked="static readonly"} + + + Prefer 'static readonly' fields over local constant array arguments + Prefer 'static readonly' fields over local constant array arguments + {Locked="static readonly"} + + + Avoid constant arrays as arguments + Avoid constant arrays as arguments + + Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in multiple allocations for one marshalling operation. Al serializar "StringBuilder" siempre se crea una copia del búfer nativo, lo que da lugar a varias asignaciones para una operación de serialización. 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 850dddd82f..545a2d8f0c 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.fr.xlf @@ -47,6 +47,26 @@ Les littéraux de chaîne d'attribut doivent être analysés correctement + + Extract to static readonly field + Extract to static readonly field + + + + Constant arrays passed as arguments are not reused which implies a performance overhead. Consider extracting them to 'static readonly' fields to improve performance. + Constant arrays passed as arguments are not reused which implies a performance overhead. Consider extracting them to 'static readonly' fields to improve performance. + {Locked="static readonly"} + + + Prefer 'static readonly' fields over local constant array arguments + Prefer 'static readonly' fields over local constant array arguments + {Locked="static readonly"} + + + Avoid constant arrays as arguments + Avoid constant arrays as arguments + + Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in multiple allocations for one marshalling operation. Le marshaling de 'StringBuilder' crée toujours une copie de la mémoire tampon native, ce qui entraîne plusieurs allocations pour une seule opération de marshaling. 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 554b7c180c..40341125c6 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.it.xlf @@ -47,6 +47,26 @@ I valori letterali stringa dell'attributo devono essere analizzati correttamente + + Extract to static readonly field + Extract to static readonly field + + + + Constant arrays passed as arguments are not reused which implies a performance overhead. Consider extracting them to 'static readonly' fields to improve performance. + Constant arrays passed as arguments are not reused which implies a performance overhead. Consider extracting them to 'static readonly' fields to improve performance. + {Locked="static readonly"} + + + Prefer 'static readonly' fields over local constant array arguments + Prefer 'static readonly' fields over local constant array arguments + {Locked="static readonly"} + + + Avoid constant arrays as arguments + Avoid constant arrays as arguments + + Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in multiple allocations for one marshalling operation. Il marshalling di 'StringBuilder' crea sempre una copia del buffer nativo, di conseguenza vengono generate più allocazioni per una singola operazione di marshalling. 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 778f41fceb..6a72f6ceb0 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ja.xlf @@ -47,6 +47,26 @@ 属性文字列リテラルは、正しく解析する必要があります + + Extract to static readonly field + Extract to static readonly field + + + + Constant arrays passed as arguments are not reused which implies a performance overhead. Consider extracting them to 'static readonly' fields to improve performance. + Constant arrays passed as arguments are not reused which implies a performance overhead. Consider extracting them to 'static readonly' fields to improve performance. + {Locked="static readonly"} + + + Prefer 'static readonly' fields over local constant array arguments + Prefer 'static readonly' fields over local constant array arguments + {Locked="static readonly"} + + + Avoid constant arrays as arguments + Avoid constant arrays as arguments + + Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in multiple allocations for one marshalling operation. 'StringBuilder' をマーシャリングすると、ネイティブ バッファーのコピーが常に作成され、1 回のマーシャリング操作に対して複数の割り当てが発生します。 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 5e732ee9d2..c627de63ca 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ko.xlf @@ -47,6 +47,26 @@ 특성 문자열 리터럴이 올바르게 구문 분석되어야 합니다. + + Extract to static readonly field + Extract to static readonly field + + + + Constant arrays passed as arguments are not reused which implies a performance overhead. Consider extracting them to 'static readonly' fields to improve performance. + Constant arrays passed as arguments are not reused which implies a performance overhead. Consider extracting them to 'static readonly' fields to improve performance. + {Locked="static readonly"} + + + Prefer 'static readonly' fields over local constant array arguments + Prefer 'static readonly' fields over local constant array arguments + {Locked="static readonly"} + + + Avoid constant arrays as arguments + Avoid constant arrays as arguments + + Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in multiple allocations for one marshalling operation. 'StringBuilder'를 마샬링하는 경우 항상 네이티브 버퍼 복사본이 만들어지므로 하나의 마샬링 작업에 대해 할당이 여러 번 이루어집니다. 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 14e2688d6b..1b7d478cd0 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.pl.xlf @@ -47,6 +47,26 @@ Analiza literałów ciągu atrybutu powinna kończyć się powodzeniem + + Extract to static readonly field + Extract to static readonly field + + + + Constant arrays passed as arguments are not reused which implies a performance overhead. Consider extracting them to 'static readonly' fields to improve performance. + Constant arrays passed as arguments are not reused which implies a performance overhead. Consider extracting them to 'static readonly' fields to improve performance. + {Locked="static readonly"} + + + Prefer 'static readonly' fields over local constant array arguments + Prefer 'static readonly' fields over local constant array arguments + {Locked="static readonly"} + + + Avoid constant arrays as arguments + Avoid constant arrays as arguments + + Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in multiple allocations for one marshalling operation. Marshalling elementu „StringBuilder” zawsze tworzy natywną kopię buforu, co powoduje powstanie wielu alokacji dla jednej operacji marshallingu. 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 9e1fe145ea..6fde376b79 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 @@ -47,6 +47,26 @@ Literais de cadeias de caracteres de atributos devem ser analisados corretamente + + Extract to static readonly field + Extract to static readonly field + + + + Constant arrays passed as arguments are not reused which implies a performance overhead. Consider extracting them to 'static readonly' fields to improve performance. + Constant arrays passed as arguments are not reused which implies a performance overhead. Consider extracting them to 'static readonly' fields to improve performance. + {Locked="static readonly"} + + + Prefer 'static readonly' fields over local constant array arguments + Prefer 'static readonly' fields over local constant array arguments + {Locked="static readonly"} + + + Avoid constant arrays as arguments + Avoid constant arrays as arguments + + Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in multiple allocations for one marshalling operation. O marshaling de 'StringBuilder' sempre cria uma cópia de buffer nativo, resultando em várias alocações para uma operação de marshalling. 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 f23fca6a1f..bcf98e5819 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.ru.xlf @@ -47,6 +47,26 @@ Синтаксический анализ строковых литералов атрибута должен осуществляться правильно + + Extract to static readonly field + Extract to static readonly field + + + + Constant arrays passed as arguments are not reused which implies a performance overhead. Consider extracting them to 'static readonly' fields to improve performance. + Constant arrays passed as arguments are not reused which implies a performance overhead. Consider extracting them to 'static readonly' fields to improve performance. + {Locked="static readonly"} + + + Prefer 'static readonly' fields over local constant array arguments + Prefer 'static readonly' fields over local constant array arguments + {Locked="static readonly"} + + + Avoid constant arrays as arguments + Avoid constant arrays as arguments + + Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in multiple allocations for one marshalling operation. При маршалировании "StringBuilder" всегда создается собственная копия буфера, что приводит к множественным выделениям для одной операции маршалирования. 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 6c0af6c4f9..1a4210b765 100644 --- a/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf +++ b/src/NetAnalyzers/Core/Microsoft.NetCore.Analyzers/xlf/MicrosoftNetCoreAnalyzersResources.tr.xlf @@ -47,6 +47,26 @@ Öznitelik dizesinin sabit değerleri doğru ayrıştırılmalıdır + + Extract to static readonly field + Extract to static readonly field + + + + Constant arrays passed as arguments are not reused which implies a performance overhead. Consider extracting them to 'static readonly' fields to improve performance. + Constant arrays passed as arguments are not reused which implies a performance overhead. Consider extracting them to 'static readonly' fields to improve performance. + {Locked="static readonly"} + + + Prefer 'static readonly' fields over local constant array arguments + Prefer 'static readonly' fields over local constant array arguments + {Locked="static readonly"} + + + Avoid constant arrays as arguments + Avoid constant arrays as arguments + + Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in multiple allocations for one marshalling operation. 'StringBuilder' öğesinin hazırlanması her zaman, bir hazırlama işlemi için birden çok ayırmaya neden olan yerel arabellek kopyası oluşturur. 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 7e6a3c6656..9cf41f75d8 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 @@ -47,6 +47,26 @@ 特性字符串文本应正确分析 + + Extract to static readonly field + Extract to static readonly field + + + + Constant arrays passed as arguments are not reused which implies a performance overhead. Consider extracting them to 'static readonly' fields to improve performance. + Constant arrays passed as arguments are not reused which implies a performance overhead. Consider extracting them to 'static readonly' fields to improve performance. + {Locked="static readonly"} + + + Prefer 'static readonly' fields over local constant array arguments + Prefer 'static readonly' fields over local constant array arguments + {Locked="static readonly"} + + + Avoid constant arrays as arguments + Avoid constant arrays as arguments + + Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in multiple allocations for one marshalling operation. "StringBuilder" 的封送处理总是会创建一个本机缓冲区副本,这导致一个封送处理操作出现多次分配。 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 466317bd52..2e23f0edc0 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 @@ -47,6 +47,26 @@ 屬性字串常值應正確剖析 + + Extract to static readonly field + Extract to static readonly field + + + + Constant arrays passed as arguments are not reused which implies a performance overhead. Consider extracting them to 'static readonly' fields to improve performance. + Constant arrays passed as arguments are not reused which implies a performance overhead. Consider extracting them to 'static readonly' fields to improve performance. + {Locked="static readonly"} + + + Prefer 'static readonly' fields over local constant array arguments + Prefer 'static readonly' fields over local constant array arguments + {Locked="static readonly"} + + + Avoid constant arrays as arguments + Avoid constant arrays as arguments + + Marshalling of 'StringBuilder' always creates a native buffer copy, resulting in multiple allocations for one marshalling operation. 封送處理 'StringBuilder' 一律都會建立原生緩衝區複本,因而導致單一封送處理作業出現多重配置。 diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md index 936febb30d..0d47d7c49b 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.md @@ -1692,6 +1692,18 @@ Prefer using 'IsEmpty', 'Count' or 'Length' properties whichever available, rath |CodeFix|True| --- +## [CA1861](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1861): Avoid constant arrays as arguments + +Constant arrays passed as arguments are not reused which implies a performance overhead. Consider extracting them to 'static readonly' fields to improve performance. + +|Item|Value| +|-|-| +|Category|Performance| +|Enabled|True| +|Severity|Info| +|CodeFix|True| +--- + ## [CA2000](https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca2000): Dispose objects before losing scope If a disposable object is not explicitly disposed before all references to it are out of scope, the object will be disposed at some indeterminate time when the garbage collector runs the finalizer of the object. Because an exceptional event might occur that will prevent the finalizer of the object from running, the object should be explicitly disposed instead. diff --git a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif index 9e4bcda6ba..75b0d2affb 100644 --- a/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif +++ b/src/NetAnalyzers/Microsoft.CodeAnalysis.NetAnalyzers.sarif @@ -3135,6 +3135,26 @@ ] } }, + "CA1861": { + "id": "CA1861", + "shortDescription": "Avoid constant arrays as arguments", + "fullDescription": "Constant arrays passed as arguments are not reused which implies a performance overhead. Consider extracting them to 'static readonly' fields to improve performance.", + "defaultLevel": "note", + "helpUri": "https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1861", + "properties": { + "category": "Performance", + "isEnabledByDefault": true, + "typeName": "AvoidConstArraysAnalyzer", + "languages": [ + "C#", + "Visual Basic" + ], + "tags": [ + "Telemetry", + "EnabledRuleInAggressiveMode" + ] + } + }, "CA2000": { "id": "CA2000", "shortDescription": "Dispose objects before losing scope", diff --git a/src/NetAnalyzers/RulesMissingDocumentation.md b/src/NetAnalyzers/RulesMissingDocumentation.md index 1316348ed0..cb801d4a81 100644 --- a/src/NetAnalyzers/RulesMissingDocumentation.md +++ b/src/NetAnalyzers/RulesMissingDocumentation.md @@ -9,5 +9,5 @@ CA1513 | | Incorrect usage of ConstantExpected attribute | CA1857 | | A constant is expected for the parameter | CA1859 | | Use concrete types when possible for improved performance | -CA1860 | | Avoid using 'Enumerable.Any()' extension method | +CA1861 | | Avoid constant arrays as arguments | CA2021 | | Do not call Enumerable.Cast\ or Enumerable.OfType\ with incompatible types | diff --git a/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.cs b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.cs new file mode 100644 index 0000000000..de5a789cd0 --- /dev/null +++ b/src/NetAnalyzers/UnitTests/Microsoft.NetCore.Analyzers/Runtime/AvoidConstArraysTests.cs @@ -0,0 +1,783 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the MIT license. See License.txt in the project root for license information. + +using System.Threading.Tasks; +using Xunit; +using VerifyCS = Test.Utilities.CSharpCodeFixVerifier< + Microsoft.NetCore.Analyzers.Runtime.AvoidConstArraysAnalyzer, + Microsoft.NetCore.Analyzers.Runtime.AvoidConstArraysFixer>; +using VerifyVB = Test.Utilities.VisualBasicCodeFixVerifier< + Microsoft.NetCore.Analyzers.Runtime.AvoidConstArraysAnalyzer, + Microsoft.NetCore.Analyzers.Runtime.AvoidConstArraysFixer>; + +namespace Microsoft.NetCore.Analyzers.Runtime.UnitTests +{ + public class AvoidConstArraysTests + { + [Fact] + public async Task IdentifyConstArrays_ImplicitInitialization() + { + await VerifyCS.VerifyCodeFixAsync(@" +using System; + +namespace Z +{ + public class A + { + public void B() + { + Console.WriteLine({|CA1861:new[]{ 1, 2, 3 }|}); + } + } +} +", @" +using System; + +namespace Z +{ + public class A + { + private static readonly int[] value = new[]{ 1, 2, 3 }; + + public void B() + { + Console.WriteLine(value); + } + } +} +"); + + await VerifyVB.VerifyCodeFixAsync(@" +Imports System + +Namespace Z + Public Class A + Public Sub B() + Console.WriteLine({|CA1861:{1, 2, 3}|}) + End Sub + End Class +End Namespace +", @" +Imports System + +Namespace Z + Public Class A + Private Shared ReadOnly value As Integer() = {1, 2, 3} + Public Sub B() + Console.WriteLine(value) + End Sub + End Class +End Namespace +"); + } + + [Fact] + public async Task IdentifyConstArrays_ExplicitInitialization() + { + await VerifyCS.VerifyCodeFixAsync(@" +using System; + +namespace Z +{ + public class A + { + public void B() + { + Console.WriteLine({|CA1861:new int[]{ 1, 2, 3 }|}); + } + } +} +", @" +using System; + +namespace Z +{ + public class A + { + private static readonly int[] value = new int[]{ 1, 2, 3 }; + + public void B() + { + Console.WriteLine(value); + } + } +} +"); + + await VerifyCS.VerifyCodeFixAsync(@" +using System; + +namespace Z +{ + public class A + { + public void B() + { + Console.WriteLine({|CA1861:new int[]{ 1, 2, 3 }|}); + } + } +} +", @" +using System; + +namespace Z +{ + public class A + { + private static readonly int[] value = new int[]{ 1, 2, 3 }; + + public void B() + { + Console.WriteLine(value); + } + } +} +"); + + await VerifyVB.VerifyCodeFixAsync(@" +Imports System + +Namespace Z + Public Class A + Public Sub B() + Console.WriteLine({|CA1861:New Integer() {1, 2, 3}|}) + End Sub + End Class +End Namespace +", @" +Imports System + +Namespace Z + Public Class A + Private Shared ReadOnly value As Integer() = New Integer() {1, 2, 3} + Public Sub B() + Console.WriteLine(value) + End Sub + End Class +End Namespace +"); + } + + [Fact] + public async Task IdentifyConstArrays_NestedArgs() + { + await VerifyCS.VerifyCodeFixAsync(@" +using System; + +namespace Z +{ + public class A + { + public void B() + { + Console.WriteLine(string.Join("" "", {|CA1861:new[] { ""Cake"", ""is"", ""good"" }|})); + } + } +} +", @" +using System; + +namespace Z +{ + public class A + { + private static readonly string[] value = new[] { ""Cake"", ""is"", ""good"" }; + + public void B() + { + Console.WriteLine(string.Join("" "", value)); + } + } +} +"); + + await VerifyVB.VerifyCodeFixAsync(@" +Imports System + +Namespace Z + Public Class A + Public Sub B() + Console.WriteLine(String.Join("" ""c, {|CA1861:{""Cake"", ""is"", ""good""}|})) + End Sub + End Class +End Namespace +", @" +Imports System + +Namespace Z + Public Class A + Private Shared ReadOnly value As String() = {""Cake"", ""is"", ""good""} + Public Sub B() + Console.WriteLine(String.Join("" ""c, value)) + End Sub + End Class +End Namespace +"); + } + + [Fact] + public async Task IdentifyConstArrays_TriviaTest() + { + await VerifyCS.VerifyCodeFixAsync(@" +using System; + +namespace Z +{ + public class A + { + public void B() + { + Console.WriteLine(string.Join( + ""a"", + {|CA1861:new[] { ""b"", ""c"" }|}, /* test comment */ + ""d"" + )); + } + } +} +", @" +using System; + +namespace Z +{ + public class A + { + private static readonly string[] values = new[] { ""b"", ""c"" }; + + public void B() + { + Console.WriteLine(string.Join( + ""a"", + values, /* test comment */ + ""d"" + )); + } + } +} +"); + } + + [Fact] + public async Task IdentifyConstArrays_LambdaArrayCreation() + { + await VerifyCS.VerifyCodeFixAsync(@" +using System; +using System.Linq; + +namespace Z +{ + public class A + { + public void B() + { + var x = new string[] { ""a"", ""b"" }; + var y = x.Select(z => {|CA1861:new[] { ""c"" }|}); + } + } +} +", @" +using System; +using System.Linq; + +namespace Z +{ + public class A + { + private static readonly string[] stringArray = new[] { ""c"" }; + + public void B() + { + var x = new string[] { ""a"", ""b"" }; + var y = x.Select(z => stringArray); + } + } +} +"); + } + + [Fact] + public async Task IdentifyConstArrays_LambdaArrayCreationTwoParams() + { + await VerifyCS.VerifyCodeFixAsync(@" +using System; +using System.Linq; + +namespace Z +{ + public class A + { + public void B() + { + var x = new string[] { ""a"", ""b"" }; + var y = x.Select((z1, z2) => {|CA1861:new[] { ""c"" }|}); + } + } +} +", @" +using System; +using System.Linq; + +namespace Z +{ + public class A + { + private static readonly string[] selector = new[] { ""c"" }; + + public void B() + { + var x = new string[] { ""a"", ""b"" }; + var y = x.Select((z1, z2) => selector); + } + } +} +"); + } + + [Fact] + public async Task IdentifyConstArrays_LambdaInvokedArrayCreation() + { + await VerifyCS.VerifyCodeFixAsync(@" +using System; +using System.Linq; + +namespace Z +{ + public class A + { + public void B() + { + var x = new string[] { ""a"", ""b"" }; + var y = x.Select(z => {|CA1861:new[] { ""c"" }|}.First()); + } + } +} +", @" +using System; +using System.Linq; + +namespace Z +{ + public class A + { + private static readonly string[] sourceArray = new[] { ""c"" }; + + public void B() + { + var x = new string[] { ""a"", ""b"" }; + var y = x.Select(z => sourceArray.First()); + } + } +} +"); + } + + [Fact] + public async Task IdentifyConstArrays_ExtensionMethod() + { + await VerifyCS.VerifyCodeFixAsync(@" +using System; +using System.Linq; + +namespace Z +{ + public class A + { + public void B() + { + string y = {|CA1861:new[] { ""a"", ""b"", ""c"" }|}.First(); + Console.WriteLine(y); + } + } +} +", @" +using System; +using System.Linq; + +namespace Z +{ + public class A + { + private static readonly string[] sourceArray = new[] { ""a"", ""b"", ""c"" }; + + public void B() + { + string y = sourceArray.First(); + Console.WriteLine(y); + } + } +} +"); + + await VerifyVB.VerifyCodeFixAsync(@" +Imports System +Imports System.Linq + +Namespace Z + Public Class A + Public Sub B() + Dim y As String = {|CA1861:{""a"", ""b"", ""c""}|}.First() + Console.WriteLine(y) + End Sub + End Class +End Namespace +", @" +Imports System +Imports System.Linq + +Namespace Z + Public Class A + Private Shared ReadOnly stringArray As String() = {""a"", ""b"", ""c""} + Public Sub B() + Dim y As String = stringArray.First() + Console.WriteLine(y) + End Sub + End Class +End Namespace +"); + } + + [Fact] + public async Task IdentifyConstArrays_ParamsArrayOfLiterals() + { + // A params argument passed as an array of literals + await VerifyCS.VerifyCodeFixAsync(@" +namespace Z +{ + public class A + { + public void B() + { + C({|CA1861:new bool[] { true, false }|}); + } + + private void C(params bool[] booleans) + { + } + } +} +", @" +namespace Z +{ + public class A + { + private static readonly bool[] booleanArray = new bool[] { true, false }; + + public void B() + { + C(booleanArray); + } + + private void C(params bool[] booleans) + { + } + } +} +"); + } + + [Fact] + public async Task IdentifyConstArrays_ParamsArrays() + { + // A params array of arrays + // Doubles as test for batch fix and two or more errors on same line + await new VerifyCS.Test() + { + TestCode = @" +namespace Z +{ + public class A + { + public void B() + { + C({|CA1861:new bool[] { true, false }|}, {|CA1861:new bool[] { false, true }|}); + } + + private void C(params bool[][] booleans) + { + } + } +} +", + NumberOfFixAllIterations = 2, + FixedCode = @" +namespace Z +{ + public class A + { + private static readonly bool[] booleanArray = new bool[] { true, false }; + private static readonly bool[] booleanArray0 = new bool[] { false, true }; + + public void B() + { + C(booleanArray, booleanArray0); + } + + private void C(params bool[][] booleans) + { + } + } +} +" + }.RunAsync(); + } + + [Fact] + public async Task IdentifyConstArrays_MemberExtractionTest() + { + // Member extraction tests + await VerifyCS.VerifyCodeFixAsync(@" +using System; + +namespace Z +{ + public class A + { + private static readonly string value = ""hello""; + private static readonly int[] valueArray = new[]{ -2, -1, 0 }; + private static readonly bool[] valueArray1 = new[]{ true, false, true }; + + private static readonly int x = 1; + + public void B() + { + Console.WriteLine({|CA1861:new[]{ 1, 2, 3 }|}); + } + } +} +", @" +using System; + +namespace Z +{ + public class A + { + private static readonly string value = ""hello""; + private static readonly int[] valueArray = new[]{ -2, -1, 0 }; + private static readonly bool[] valueArray1 = new[]{ true, false, true }; + + private static readonly int x = 1; + private static readonly int[] valueArray0 = new[]{ 1, 2, 3 }; + + public void B() + { + Console.WriteLine(valueArray0); + } + } +} +"); + + await VerifyVB.VerifyCodeFixAsync(@" +Imports System + +Namespace Z + Public Class A + Private Shared ReadOnly value As String = ""hello"" + Private Shared ReadOnly valueArray As Integer() = {-2, -1, 0} + Private Shared ReadOnly valueArray1 As Boolean() = {True, False, True} + Private Shared ReadOnly x As Integer = 1 + + Public Sub B() + Console.WriteLine({|CA1861:{1, 2, 3}|}) + End Sub + End Class +End Namespace +", @" +Imports System + +Namespace Z + Public Class A + Private Shared ReadOnly value As String = ""hello"" + Private Shared ReadOnly valueArray As Integer() = {-2, -1, 0} + Private Shared ReadOnly valueArray1 As Boolean() = {True, False, True} + Private Shared ReadOnly x As Integer = 1 + Private Shared ReadOnly valueArray0 As Integer() = {1, 2, 3} + + Public Sub B() + Console.WriteLine(valueArray0) + End Sub + End Class +End Namespace +"); + } + + [Fact] + public async Task IgnoreOtherArgs_NoDiagnostic() + { + // A string + await VerifyCS.VerifyAnalyzerAsync(@" +using System; + +namespace Z +{ + public class A + { + public void B() + { + Console.WriteLine(""Lorem ipsum""); + } + } +} +"); + + await VerifyVB.VerifyAnalyzerAsync(@" +Imports System + +Namespace Z + Public Class A + Public Sub B() + Console.WriteLine(""Lorem ipsum"") + End Sub + End Class +End Namespace +"); + + // Test another type to be extra safe + await VerifyCS.VerifyAnalyzerAsync(@" +using System; + +namespace Z +{ + public class A + { + public void B() + { + Console.WriteLine(123); + } + } +} +"); + + await VerifyVB.VerifyAnalyzerAsync(@" +Imports System + +Namespace Z + Public Class A + Public Sub B() + Console.WriteLine(123) + End Sub + End Class +End Namespace +"); + + // Non-literal array + await VerifyCS.VerifyAnalyzerAsync(@" +using System; + +namespace Z +{ + public class A + { + public void B() + { + string str = ""Lorem ipsum""; + Console.WriteLine(new[] { str }); + } + } +} +"); + + await VerifyVB.VerifyAnalyzerAsync(@" +Imports System + +Namespace Z + Public Class A + Public Sub B() + Dim str As String = ""Lorem ipsum"" + Console.WriteLine({ str }) + End Sub + End Class +End Namespace +"); + } + + [Fact] + public async Task IgnoreReadonlySpan_NoDiagnostic() + { + // A ReadOnlySpan, which is already optimized + await VerifyCS.VerifyAnalyzerAsync(@" +using System; + +namespace Z +{ + public class A + { + public void B() + { + C(new bool[] { true, false }); + } + + private void C(ReadOnlySpan span) + { + } + } +} +"); + } + + [Fact] + public async Task IgnoreParams_NoDiagnostic() + { + // Params arguments + await VerifyCS.VerifyAnalyzerAsync(@" +namespace Z +{ + public class A + { + public void B() + { + C(true, false); + } + + private void C(params bool[] booleans) + { + } + } +} +"); + } + + [Fact] + public async Task IgnoreReadonlyFieldAssignment_NoDiagnostic() + { + // Ignore when we're an argument used in a method/constructor that is assigned to a readonly field + await VerifyCS.VerifyAnalyzerAsync(@" +namespace Z +{ + public class A + { + private static readonly B s = new B(new string[] { ""a"" }); + } + + public class B + { + public B(string[] s) + { + } + } +} +"); + } + + [Fact] + public async Task IgnoreReadOnlyProperties_NoDiagnostic() + { + await VerifyCS.VerifyAnalyzerAsync(@" +using System; +using System.Collections.Generic; + +public class A +{ + public static readonly A Field; + public static List Property { get; } = GetValues(new string[] { ""close"" }); + public static string[] Property2 { get; } = new string[] { ""close"" }; + + static A() // Exclude initialization in static constructors + { + Property = GetValues(new string[] { ""close"" }); + Field = new A(new string[] { ""close"" }); + } + public A(string[] arr) { } + private static List GetValues(string[] arr) => null; +}"); + } + } +} diff --git a/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt b/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt index 789add83ee..43ad39c7a9 100644 --- a/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt +++ b/src/Utilities/Compiler/DiagnosticCategoryAndIdRanges.txt @@ -12,7 +12,7 @@ Design: CA2210, CA1000-CA1070 Globalization: CA2101, CA1300-CA1311 Mobility: CA1600-CA1601 -Performance: HA, CA1800-CA1860 +Performance: HA, CA1800-CA1861 Security: CA2100-CA2153, CA2300-CA2330, CA3000-CA3147, CA5300-CA5405 Usage: CA1801, CA1806, CA1816, CA2200-CA2209, CA2211-CA2260 Naming: CA1700-CA1727 diff --git a/src/Utilities/Compiler/WellKnownTypeNames.cs b/src/Utilities/Compiler/WellKnownTypeNames.cs index 53e6c0de1b..bd800d4ee3 100644 --- a/src/Utilities/Compiler/WellKnownTypeNames.cs +++ b/src/Utilities/Compiler/WellKnownTypeNames.cs @@ -222,6 +222,7 @@ internal static class WellKnownTypeNames public const string SystemException = "System.Exception"; public const string SystemExecutionEngineException = "System.ExecutionEngineException"; public const string SystemFlagsAttribute = "System.FlagsAttribute"; + public const string SystemFunc2 = "System.Func`2"; public const string SystemGC = "System.GC"; public const string SystemGlobalizationCultureInfo = "System.Globalization.CultureInfo"; public const string SystemGuid = "System.Guid";