File: ImmutableArrayBoxingAnalyzer.cs
Web Access
Project: src\src\RoslynAnalyzers\Roslyn.Diagnostics.Analyzers\Core\Roslyn.Diagnostics.Analyzers.csproj (Roslyn.Diagnostics.Analyzers)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System.Collections.Immutable;
using System.Linq;
using Analyzer.Utilities;
using Analyzer.Utilities.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;
 
namespace Roslyn.Diagnostics.Analyzers
{
    using static RoslynDiagnosticsAnalyzersResources;
 
    /// <summary>
    /// RS0066: <inheritdoc cref="ImmutableArrayBoxingTitle"/>
    /// </summary>
#pragma warning disable RS1004 // Recommend adding language support to diagnostic analyzer
    [DiagnosticAnalyzer(LanguageNames.CSharp)]
#pragma warning restore RS1004 // Recommend adding language support to diagnostic analyzer
    public class ImmutableArrayBoxingAnalyzer : DiagnosticAnalyzer
    {
        internal static readonly DiagnosticDescriptor Rule = new(
            RoslynDiagnosticIds.ImmutableArrayBoxingRuleId,
            CreateLocalizableResourceString(nameof(ImmutableArrayBoxingTitle)),
            CreateLocalizableResourceString(nameof(ImmutableArrayBoxingMessage)),
            DiagnosticCategory.RoslynDiagnosticsPerformance,
            DiagnosticSeverity.Warning,
            isEnabledByDefault: true,
            description: CreateLocalizableResourceString(nameof(ImmutableArrayBoxingDescription)),
            helpLinkUri: null,
            customTags: WellKnownDiagnosticTagsExtensions.Telemetry);
 
        public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
 
        public override void Initialize(AnalysisContext context)
        {
            context.EnableConcurrentExecution();
            context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
 
            context.RegisterCompilationStartAction(context =>
            {
                var immutableArray = context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemCollectionsImmutableImmutableArray1);
                if (immutableArray is null)
                    return;
 
                var readOnlyListExtensions = context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemCollectionsGenericReadOnlyListExtensions);
                if (readOnlyListExtensions is null)
                    return;
 
                var enumerableExtensions = context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemCollectionsGenericEnumerableExtensions);
                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 = Equals(targetMethod.OriginalDefinition.ContainingType, readOnlyListExtensionsType);
            var isEnumerableExtensions = Equals(targetMethod.OriginalDefinition.ContainingType, enumerableExtensionsType);
 
            if (!isReadOnlyListExtensions && !isEnumerableExtensions)
                return;
 
            var instance = invocation.Instance ?? invocation.Arguments.FirstOrDefault()?.Value;
 
            if (instance is not IConversionOperation conversionOperation)
                return;
 
            if (conversionOperation.Type?.TypeKind != TypeKind.Interface)
                return;
 
            if (conversionOperation.Operand.Type is not INamedTypeSymbol operandType ||
                !Equals(operandType.OriginalDefinition, immutableArrayType))
            {
                return;
            }
 
            var typeName = isReadOnlyListExtensions
                ? WellKnownTypeNames.SystemCollectionsGenericReadOnlyListExtensions
                : WellKnownTypeNames.SystemCollectionsGenericEnumerableExtensions;
 
            context.ReportDiagnostic(instance.CreateDiagnostic(Rule, $"{typeName}.{targetMethod.Name}"));
        }
    }
}