File: CallAnalysis\Arrays.cs
Web Access
Project: src\src\Analyzers\Microsoft.Analyzers.Extra\Microsoft.Analyzers.Extra.csproj (Microsoft.Analyzers.Extra)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
 
namespace Microsoft.Extensions.ExtraAnalyzers.CallAnalysis;
 
/// <summary>
/// Recommends replacing dictionaries and sets indexed by [enum|byte|sbyte] with simple arrays instead.
/// </summary>
internal sealed class Arrays
{
    private static readonly string[] _collectionTypes = new[]
    {
        "System.Collections.Generic.Dictionary`2",
        "System.Collections.Generic.HashSet`1",
        "System.Collections.Generic.SortedDictionary`2",
        "System.Collections.Generic.SortedSet`1",
        "System.Collections.Immutable.ImmutableDictionary`2",
        "System.Collections.Immutable.ImmutableHashSet`1",
        "System.Collections.Immutable.ImmutableSortedDictionary`2",
        "System.Collections.Immutable.ImmutableSortedSet`1",
        "System.Collections.Frozen.FrozenDictionary`2",
        "System.Collections.Frozen.FrozenSet`1",
    };
 
    private static readonly Dictionary<string, string[]> _collectionFactories = new()
    {
        ["System.Collections.Immutable.ImmutableDictionary"] = new[]
        {
            "Create",
            "CreateRange",
        },
 
        ["System.Collections.Immutable.ImmutableHashSet"] = new[]
        {
            "Create",
            "CreateRange",
        },
 
        ["System.Collections.Immutable.ImmutableSortedDictionary"] = new[]
        {
            "Create",
            "CreateRange",
        },
 
        ["System.Collections.Immutable.ImmutableSortedSet"] = new[]
        {
            "Create",
            "CreateRange",
        },
 
        ["System.Collections.Immutable.ImmutableDictionary`2+Builder"] = new[]
        {
            "ToImmutable",
        },
 
        ["System.Collections.Immutable.ImmutableHashSet`1+Builder"] = new[]
        {
            "ToImmutable",
        },
 
        ["System.Collections.Immutable.ImmutableSortedDictionary`2+Builder"] = new[]
        {
            "ToImmutable",
        },
 
        ["System.Collections.Immutable.ImmutableSortedSet`1+Builder"] = new[]
        {
            "ToImmutable",
        },
    };
 
    public Arrays(CallAnalyzer.Registrar reg)
    {
        reg.RegisterConstructors(_collectionTypes, HandleConstructor);
        reg.RegisterMethods(_collectionFactories, HandleMethod);
 
        var freezer = reg.Compilation.GetTypeByMetadataName("System.Collections.Frozen.FrozenDictionary");
        if (freezer != null)
        {
            foreach (var method in freezer.GetMembers("ToFrozenDictionary").OfType<IMethodSymbol>().Where(m => m.TypeParameters.Length == 2))
            {
                reg.RegisterMethod(method, HandleMethod);
            }
        }
 
        freezer = reg.Compilation.GetTypeByMetadataName("System.Collections.Frozen.FrozenSet");
        if (freezer != null)
        {
            foreach (var method in freezer.GetMembers("ToFrozenSet").OfType<IMethodSymbol>().Where(m => m.TypeParameters.Length == 1))
            {
                reg.RegisterMethod(method, HandleMethod);
            }
        }
 
        static void HandleMethod(OperationAnalysisContext context, IInvocationOperation op) => HandleSuspectType(context, (INamedTypeSymbol)op.TargetMethod.ReturnType, op.Syntax.GetLocation());
 
        static void HandleConstructor(OperationAnalysisContext context, IObjectCreationOperation op) => HandleSuspectType(context, (INamedTypeSymbol)op.Type!, op.Syntax.GetLocation());
 
        static void HandleSuspectType(OperationAnalysisContext context, INamedTypeSymbol type, Location loc)
        {
            var keyType = type.TypeArguments[0];
            if (keyType.TypeKind == TypeKind.Enum
             || keyType.SpecialType == SpecialType.System_Byte
             || keyType.SpecialType == SpecialType.System_SByte)
            {
                if (keyType.TypeKind == TypeKind.Enum)
                {
                    var flagsAttr = context.Compilation.GetTypeByMetadataName("System.FlagsAttribute");
                    if (keyType.GetAttributes().Any(a => a.AttributeClass != null && SymbolEqualityComparer.Default.Equals(a.AttributeClass, flagsAttr)))
                    {
                        // not for [Flags] enums
                        return;
                    }
                }
 
                var valueType = keyType;
                if (type.TypeArguments.Length == 2)
                {
                    valueType = type.TypeArguments[1];
                }
 
                var diagnostic = Diagnostic.Create(DiagDescriptors.Arrays, loc, valueType.ToDisplayString(), type.ToDisplayString());
                context.ReportDiagnostic(diagnostic);
            }
        }
    }
}