| File: Microsoft.NetCore.Analyzers\Runtime\AvoidConstArrays.cs | Web Access |
| Project: src\sdk\src\Microsoft.CodeAnalysis.NetAnalyzers\src\Microsoft.CodeAnalysis.NetAnalyzers\Microsoft.CodeAnalysis.NetAnalyzers.csproj (Microsoft.CodeAnalysis.NetAnalyzers) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. 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; /// <summary> /// CA1861: Avoid constant arrays as arguments. Replace with static readonly arrays. /// </summary> [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<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(Rule); public override void Initialize(AnalysisContext context) { context.EnableConcurrentExecution(); context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); context.RegisterCompilationStartAction(context => { var knownTypeProvider = WellKnownTypeProvider.GetOrCreate(context.Compilation); INamedTypeSymbol? readonlySpanType = knownTypeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemReadOnlySpan1); INamedTypeSymbol? functionType = knownTypeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemFunc2); INamedTypeSymbol? attributeType = knownTypeProvider.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemAttribute); // 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<IArgumentOperation>(OperationKind.Argument); // If no argument, return // If argument is passed as a params array but isn't itself an array, return // If array is declared as an attribute argument, return if (argumentOperation?.Parameter is null || (argumentOperation.Parameter.IsParams && arrayCreationOperation.IsImplicit) || (argumentOperation.Parent is IObjectCreationOperation objectCreation && objectCreation.Type.Inherits(attributeType))) { return; } } else if (context.Operation is IInvocationOperation invocationOperation) // For arrays passed in extension methods, like in LINQ { IEnumerable<IOperation> 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 is not { } initializer || initializer.ElementValues.Any(x => x is not ILiteralOperation)) { return; } string? paramName = null; if (argumentOperation?.Parameter is not null) { if (IsInitializingStaticOrReadOnlyFieldOrProperty(argumentOperation)) { return; } ITypeSymbol? originalDefinition = argumentOperation.Parameter?.Type.OriginalDefinition; // Can't be a ReadOnlySpan, as those are already optimized if (originalDefinition == null || 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<string, string?>.Builder properties = ImmutableDictionary.CreateBuilder<string, string?>(); properties.Add("paramName", paramName); context.ReportDiagnostic(arrayCreationOperation.CreateDiagnostic(Rule, properties.ToImmutable())); }, OperationKind.ArrayCreation, OperationKind.Invocation); }); } private static bool IsInitializingStaticOrReadOnlyFieldOrProperty(IOperation operation) { IOperation? highestInitializerOrAssignment = null; IOperation? ancestor = operation; do { if (ancestor?.Kind is OperationKind.FieldInitializer or OperationKind.PropertyInitializer or OperationKind.CoalesceAssignment or OperationKind.SimpleAssignment) { highestInitializerOrAssignment = ancestor; } ancestor = ancestor!.Parent; } while (ancestor != null); return highestInitializerOrAssignment switch { IFieldInitializerOperation fieldInitializer => fieldInitializer.InitializedFields.Any(x => x.IsStatic || x.IsReadOnly), IPropertyInitializerOperation propertyInitializer => propertyInitializer.InitializedProperties.Any(x => x.IsStatic || x.IsReadOnly), IAssignmentOperation assignment => assignment.Target is IMemberReferenceOperation memberReference && memberReference.Member.IsStatic, _ => false }; } } }