File: TaskEnvironmentConstructorInjectionAnalyzer.cs
Web Access
Project: TaskAnalyzer.csproj (Microsoft.Build.TaskAuthoring.Analyzer)
// 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 Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;

namespace Microsoft.Build.TaskAuthoring.Analyzer
{
    /// <summary>
    /// Suggests constructor injection for concrete tasks that implement <c>IMultiThreadableTask</c>.
    /// </summary>
    [DiagnosticAnalyzer(LanguageNames.CSharp)]
    public sealed class TaskEnvironmentConstructorInjectionAnalyzer : DiagnosticAnalyzer
    {
        public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } =
            ImmutableArray.Create(DiagnosticDescriptors.PreferTaskEnvironmentConstructorInjection);

        public override void Initialize(AnalysisContext context)
        {
            context.EnableConcurrentExecution();
            context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
            context.RegisterCompilationStartAction(OnCompilationStart);
        }

        private static void OnCompilationStart(CompilationStartAnalysisContext context)
        {
            INamedTypeSymbol? multiThreadableTaskType =
                context.Compilation.GetTypeByMetadataName(WellKnownTypeNames.IMultiThreadableTaskFullName);
            if (multiThreadableTaskType is null)
            {
                return;
            }

            INamedTypeSymbol? taskEnvironmentType =
                context.Compilation.GetTypeByMetadataName(WellKnownTypeNames.TaskEnvironmentFullName);
            if (taskEnvironmentType is null)
            {
                return;
            }

            context.RegisterSymbolAction(
                symbolContext => AnalyzeNamedType(
                    symbolContext,
                    multiThreadableTaskType,
                    taskEnvironmentType),
                SymbolKind.NamedType);
        }

        private static void AnalyzeNamedType(
            SymbolAnalysisContext context,
            INamedTypeSymbol multiThreadableTaskType,
            INamedTypeSymbol taskEnvironmentType)
        {
            var taskType = (INamedTypeSymbol)context.Symbol;
            if (taskType.TypeKind != TypeKind.Class ||
                taskType.IsAbstract ||
                !SharedAnalyzerHelpers.ImplementsInterface(taskType, multiThreadableTaskType) ||
                HasTaskEnvironmentConstructor(taskType, taskEnvironmentType))
            {
                return;
            }

            foreach (Location location in taskType.Locations)
            {
                if (location.IsInSource)
                {
                    context.ReportDiagnostic(Diagnostic.Create(
                        DiagnosticDescriptors.PreferTaskEnvironmentConstructorInjection,
                        location,
                        taskType.Name));
                    return;
                }
            }
        }

        private static bool HasTaskEnvironmentConstructor(
            INamedTypeSymbol taskType,
            INamedTypeSymbol taskEnvironmentType)
        {
            foreach (IMethodSymbol constructor in taskType.InstanceConstructors)
            {
                if (constructor.DeclaredAccessibility == Accessibility.Public &&
                    constructor.Parameters.Length == 1 &&
                    SymbolEqualityComparer.Default.Equals(constructor.Parameters[0].Type, taskEnvironmentType))
                {
                    return true;
                }
            }

            return false;
        }
    }
}