File: ImmutableArrayBoxingAnalyzer.cs
Web Access
Project: src\src\Razor\src\Analyzers\Razor.Diagnostics.Analyzers\Razor.Diagnostics.Analyzers.csproj (Razor.Diagnostics.Analyzers)
// 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.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
using static Razor.Diagnostics.Analyzers.Resources;
 
namespace Razor.Diagnostics.Analyzers;
 
[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class ImmutableArrayBoxingAnalyzer : DiagnosticAnalyzer
{
    public static readonly DiagnosticDescriptor Rule = new(
        DiagnosticIds.ImmutableArrayBoxing,
        CreateLocalizableResourceString(nameof(ImmutableArrayBoxingTitle)),
        CreateLocalizableResourceString(nameof(ImmutableArrayBoxingMessage)),
        DiagnosticCategory.Performance,
        DiagnosticSeverity.Warning,
        isEnabledByDefault: true,
        description: CreateLocalizableResourceString(nameof(ImmutableArrayBoxingDescription)));
 
    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => [Rule];
 
    public override void Initialize(AnalysisContext context)
    {
        context.EnableConcurrentExecution();
        context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
 
        context.RegisterCompilationStartAction(context =>
        {
            var immutableArray = context.Compilation.GetTypeByMetadataName(WellKnownTypeNames.ImmutableArray_T);
            if (immutableArray is null)
            {
                return;
            }
 
            var readOnlyListExtensions = context.Compilation.GetTypeByMetadataName(WellKnownTypeNames.ReadOnlyListExtensions);
            if (readOnlyListExtensions is null)
            {
                return;
            }
 
            var enumerableExtensions = context.Compilation.GetTypeByMetadataName(WellKnownTypeNames.EnumerableExtensions);
            if (enumerableExtensions is null)
            {
                return;
            }
 
            context.RegisterOperationAction(
                context => AnalyzeInvocation(context, immutableArray, readOnlyListExtensions, enumerableExtensions),
                OperationKind.Invocation);
        });
    }
 
    private static void AnalyzeInvocation(
        OperationAnalysisContext context,
        INamedTypeSymbol immutableArrayType,
        INamedTypeSymbol readOnlyListExtensionsType,
        INamedTypeSymbol enumerableExtensionsType)
    {
        var invocation = (IInvocationOperation)context.Operation;
        var targetMethod = invocation.TargetMethod.ReducedFrom ?? invocation.TargetMethod;
 
        var isReadOnlyListExtensions = SymbolEqualityComparer.Default.Equals(targetMethod.OriginalDefinition.ContainingType, readOnlyListExtensionsType);
        var isEnumerableExtensions = SymbolEqualityComparer.Default.Equals(targetMethod.OriginalDefinition.ContainingType, enumerableExtensionsType);
 
        if (!isReadOnlyListExtensions && !isEnumerableExtensions)
        {
            return;
        }
 
        var instance = invocation.Instance ?? invocation.Arguments.FirstOrDefault()?.Value;
 
        if (instance is not IConversionOperation conversionOperation)
        {
            return;
        }
 
        var conversion = conversionOperation.GetConversion();
        if (!conversion.IsBoxing)
        {
            return;
        }
 
        if (conversionOperation.Operand.Type is not INamedTypeSymbol operandType ||
            !SymbolEqualityComparer.Default.Equals(operandType.OriginalDefinition, immutableArrayType))
        {
            return;
        }
 
        var typeName = isReadOnlyListExtensions
            ? WellKnownTypeNames.ReadOnlyListExtensions
            : WellKnownTypeNames.EnumerableExtensions;
 
        context.ReportDiagnostic(instance.CreateDiagnostic(Rule, $"{typeName}.{targetMethod.Name}"));
    }
}