File: SpecializedEnumerableCreationAnalyzer.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.
 
#nullable disable warnings
 
using System;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using Analyzer.Utilities;
using Analyzer.Utilities.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
 
namespace Roslyn.Diagnostics.Analyzers
{
    using static RoslynDiagnosticsAnalyzersResources;
 
    // TODO: This should be updated to follow the flow of array creation expressions
    // that are eventually converted to and leave a given method as IEnumerable<T> once we have
    // the ability to do more thorough data-flow analysis in diagnostic analyzers.
    /// <summary>
    /// RS0001: <inheritdoc cref="UseSpecializedCollectionsEmptyEnumerableTitle"/>
    /// RS0002: <inheritdoc cref="UseSpecializedCollectionsSingletonEnumerableTitle"/>
    /// </summary>
    public abstract class SpecializedEnumerableCreationAnalyzer : DiagnosticAnalyzer
    {
        internal const string SpecializedCollectionsMetadataName = "Roslyn.Utilities.SpecializedCollections";
        internal const string LinqEnumerableMetadataName = "System.Linq.Enumerable";
        internal const string EmptyMethodName = "Empty";
 
        internal static readonly DiagnosticDescriptor UseEmptyEnumerableRule = new(
            RoslynDiagnosticIds.UseEmptyEnumerableRuleId,
            CreateLocalizableResourceString(nameof(UseSpecializedCollectionsEmptyEnumerableTitle)),
            CreateLocalizableResourceString(nameof(UseSpecializedCollectionsEmptyEnumerableMessage)),
            DiagnosticCategory.RoslynDiagnosticsPerformance,
            DiagnosticSeverity.Warning,
            isEnabledByDefault: true,
            customTags: WellKnownDiagnosticTagsExtensions.Telemetry);
 
        internal static readonly DiagnosticDescriptor UseSingletonEnumerableRule = new(
            RoslynDiagnosticIds.UseSingletonEnumerableRuleId,
            CreateLocalizableResourceString(nameof(UseSpecializedCollectionsSingletonEnumerableTitle)),
            CreateLocalizableResourceString(nameof(UseSpecializedCollectionsSingletonEnumerableMessage)),
            DiagnosticCategory.RoslynDiagnosticsPerformance,
            DiagnosticSeverity.Warning,
            isEnabledByDefault: true,
            customTags: WellKnownDiagnosticTagsExtensions.Telemetry);
 
        public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(UseEmptyEnumerableRule, UseSingletonEnumerableRule);
 
        public override void Initialize(AnalysisContext context)
        {
            context.EnableConcurrentExecution();
 
            context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
 
            context.RegisterCompilationStartAction(context =>
            {
                INamedTypeSymbol? specializedCollectionsSymbol = context.Compilation.GetOrCreateTypeByMetadataName(SpecializedCollectionsMetadataName);
                if (specializedCollectionsSymbol == null)
                {
                    // TODO: In the future, we may want to run this analyzer even if the SpecializedCollections
                    // type cannot be found in this compilation. In some cases, we may want to add a reference
                    // to SpecializedCollections as a linked file or an assembly that contains it. With this
                    // check, we will not warn where SpecializedCollections is not yet referenced.
                    return;
                }
 
                INamedTypeSymbol? genericEnumerableSymbol = context.Compilation.GetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemCollectionsGenericIEnumerable1);
                if (genericEnumerableSymbol == null)
                {
                    return;
                }
 
                INamedTypeSymbol? linqEnumerableSymbol = context.Compilation.GetOrCreateTypeByMetadataName(LinqEnumerableMetadataName);
                if (linqEnumerableSymbol == null)
                {
                    return;
                }
 
                if (linqEnumerableSymbol.GetMembers(EmptyMethodName).FirstOrDefault() is not IMethodSymbol genericEmptyEnumerableSymbol ||
                    genericEmptyEnumerableSymbol.Arity != 1 ||
                    !genericEmptyEnumerableSymbol.Parameters.IsEmpty)
                {
                    return;
                }
 
                GetCodeBlockStartedAnalyzer(context, genericEnumerableSymbol, genericEmptyEnumerableSymbol);
            });
        }
 
        protected abstract void GetCodeBlockStartedAnalyzer(CompilationStartAnalysisContext context, INamedTypeSymbol genericEnumerableSymbol, IMethodSymbol genericEmptyEnumerableSymbol);
 
        protected abstract class AbstractCodeBlockStartedAnalyzer<TLanguageKindEnum> where TLanguageKindEnum : struct
        {
            private readonly INamedTypeSymbol _genericEnumerableSymbol;
            private readonly IMethodSymbol _genericEmptyEnumerableSymbol;
 
            protected AbstractCodeBlockStartedAnalyzer(INamedTypeSymbol genericEnumerableSymbol, IMethodSymbol genericEmptyEnumerableSymbol)
            {
                _genericEnumerableSymbol = genericEnumerableSymbol;
                _genericEmptyEnumerableSymbol = genericEmptyEnumerableSymbol;
            }
 
            protected abstract void GetSyntaxAnalyzer(CodeBlockStartAnalysisContext<TLanguageKindEnum> context, INamedTypeSymbol genericEnumerableSymbol, IMethodSymbol genericEmptyEnumerableSymbol);
 
            public void Initialize(CodeBlockStartAnalysisContext<TLanguageKindEnum> context)
            {
                if (context.OwningSymbol is IMethodSymbol methodSymbol &&
    Equals(methodSymbol.ReturnType.OriginalDefinition, _genericEnumerableSymbol))
                {
                    GetSyntaxAnalyzer(context, _genericEnumerableSymbol, _genericEmptyEnumerableSymbol);
                }
            }
        }
 
        protected abstract class AbstractSyntaxAnalyzer
        {
            protected INamedTypeSymbol GenericEnumerableSymbol { get; }
            private readonly IMethodSymbol _genericEmptyEnumerableSymbol;
 
            protected AbstractSyntaxAnalyzer(INamedTypeSymbol genericEnumerableSymbol, IMethodSymbol genericEmptyEnumerableSymbol)
            {
                this.GenericEnumerableSymbol = genericEnumerableSymbol;
                _genericEmptyEnumerableSymbol = genericEmptyEnumerableSymbol;
            }
 
            public static ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(UseEmptyEnumerableRule, UseSingletonEnumerableRule);
 
            protected bool ShouldAnalyzeArrayCreationExpression(SyntaxNode expression, SemanticModel semanticModel, CancellationToken cancellationToken)
            {
                TypeInfo typeInfo = semanticModel.GetTypeInfo(expression, cancellationToken);
 
                return typeInfo.ConvertedType != null &&
                    Equals(typeInfo.ConvertedType.OriginalDefinition, GenericEnumerableSymbol) &&
                    typeInfo.Type is IArrayTypeSymbol arrayType &&
                    arrayType.Rank == 1;
            }
 
            protected void AnalyzeMemberAccessName(SyntaxNode name, SemanticModel semanticModel, Action<Diagnostic> addDiagnostic, CancellationToken cancellationToken)
            {
                if (semanticModel.GetSymbolInfo(name, cancellationToken).Symbol is IMethodSymbol methodSymbol &&
                    Equals(methodSymbol.OriginalDefinition, _genericEmptyEnumerableSymbol))
                {
                    addDiagnostic(name.Parent.CreateDiagnostic(UseEmptyEnumerableRule));
                }
            }
 
            protected static void AnalyzeArrayLength(int length, SyntaxNode arrayCreationExpression, Action<Diagnostic> addDiagnostic)
            {
                if (length == 0)
                {
                    addDiagnostic(arrayCreationExpression.CreateDiagnostic(UseEmptyEnumerableRule));
                }
                else if (length == 1)
                {
                    addDiagnostic(arrayCreationExpression.CreateDiagnostic(UseSingletonEnumerableRule));
                }
            }
        }
    }
}