File: Microsoft.NetCore.Analyzers\Tasks\DoNotUseWhenAllOrWaitAllWithSingleArgument.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;
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Analyzer.Utilities;
using Analyzer.Utilities.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Operations;

namespace Microsoft.NetCore.Analyzers.Tasks
{
    using static MicrosoftNetCoreAnalyzersResources;

    /// <summary>
    /// CA1842: <inheritdoc cref="DoNotUseWhenAllWithSingleTaskTitle"/>
    /// CA1843: <inheritdoc cref="DoNotUseWaitAllWithSingleTaskTitle"/>
    /// </summary>
    [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
    public class DoNotUseWhenAllOrWaitAllWithSingleArgument : DiagnosticAnalyzer
    {
        internal const string WhenAllRuleId = "CA1842";
        internal const string WaitAllRuleId = "CA1843";

        internal static readonly DiagnosticDescriptor WhenAllRule = DiagnosticDescriptorHelper.Create(WhenAllRuleId,
            CreateLocalizableResourceString(nameof(DoNotUseWhenAllWithSingleTaskTitle)),
            CreateLocalizableResourceString(nameof(DoNotUseWhenAllWithSingleTaskTitle)),
            DiagnosticCategory.Performance,
            RuleLevel.IdeSuggestion,
            CreateLocalizableResourceString(nameof(DoNotUseWhenAllWithSingleTaskDescription)),
            isPortedFxCopRule: false,
            isDataflowRule: false);

        internal static readonly DiagnosticDescriptor WaitAllRule = DiagnosticDescriptorHelper.Create(WaitAllRuleId,
            CreateLocalizableResourceString(nameof(DoNotUseWaitAllWithSingleTaskTitle)),
            CreateLocalizableResourceString(nameof(DoNotUseWaitAllWithSingleTaskTitle)),
            DiagnosticCategory.Performance,
            RuleLevel.IdeSuggestion,
            CreateLocalizableResourceString(nameof(DoNotUseWaitAllWithSingleTaskDescription)),
            isPortedFxCopRule: false,
            isDataflowRule: false);

        public sealed override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(WhenAllRule, WaitAllRule);

        public sealed override void Initialize(AnalysisContext context)
        {
            context.EnableConcurrentExecution();
            context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
            context.RegisterCompilationStartAction(context =>
            {
                if (!context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingTasksTask, out var taskType) ||
                    !context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemThreadingTasksTask1, out var genericTaskType))
                {
                    return;
                }

                context.RegisterOperationAction(context =>
                {
                    var invocation = (IInvocationOperation)context.Operation;
                    if (IsWhenOrWaitAllMethod(invocation.TargetMethod, taskType) &&
                        IsSingleTaskArgument(invocation, taskType, genericTaskType))
                    {
                        switch (invocation.TargetMethod.Name)
                        {
                            case nameof(Task.WhenAll):
                                context.ReportDiagnostic(invocation.CreateDiagnostic(WhenAllRule));
                                break;

                            case nameof(Task.WaitAll):
                                context.ReportDiagnostic(invocation.CreateDiagnostic(WaitAllRule));
                                break;

                            default:
                                throw new InvalidOperationException($"Unexpected method name: {invocation.TargetMethod.Name}");
                        }
                    }
                }, OperationKind.Invocation);
            });
        }

        private static bool IsWhenOrWaitAllMethod(IMethodSymbol targetMethod, INamedTypeSymbol taskType)
        {
            var nameMatches = targetMethod.Name is (nameof(Task.WhenAll)) or (nameof(Task.WaitAll));
            var parameters = targetMethod.Parameters;

            return nameMatches &&
                targetMethod.IsStatic &&
                SymbolEqualityComparer.Default.Equals(targetMethod.ContainingType, taskType) &&
                parameters.Length == 1 &&
                parameters[0].IsParams;
        }

        private static bool IsSingleTaskArgument(IInvocationOperation invocation, INamedTypeSymbol taskType, INamedTypeSymbol genericTaskType)
        {
            if (invocation.Arguments.Length != 1)
            {
                return false;
            }

            var argument = invocation.Arguments.Single();

            // Task.WhenAll and Task.WaitAll have params arguments, which are implicit
            // array creation for cases where params were passed in without explicitly
            // being an array already. 
            if (argument.Value is not IArrayCreationOperation
                {
                    IsImplicit: true,
                    Initializer.ElementValues: { Length: 1 } initializerValues
                })
            {
                return false;
            }

            if (initializerValues.Single().Type is not INamedTypeSymbol namedTypeSymbol)
            {
                return false;
            }

            return namedTypeSymbol.Equals(taskType, SymbolEqualityComparer.Default) ||
                namedTypeSymbol.ConstructedFrom.Equals(genericTaskType, SymbolEqualityComparer.Default);
        }
    }
}