File: RequiredTaskPropertyInitializationSuppressor.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;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;

namespace Microsoft.Build.TaskAuthoring.Analyzer
{
    [DiagnosticAnalyzer(LanguageNames.CSharp)]
    public sealed class RequiredTaskPropertyInitializationSuppressor : DiagnosticSuppressor
    {
        private static readonly SuppressionDescriptor s_requiredTaskPropertyInitialization = new(
            id: "MSBuildTaskSPR0001",
            suppressedDiagnosticId: "CS8618",
            justification: "MSBuild initializes [Required] task properties before Execute runs.");

        public override ImmutableArray<SuppressionDescriptor> SupportedSuppressions => [s_requiredTaskPropertyInitialization];

        public override void ReportSuppressions(SuppressionAnalysisContext context)
        {
            var requiredAttributeType = context.Compilation.GetTypeByMetadataName(WellKnownTypeNames.RequiredAttributeFullName);
            var iTaskType = context.Compilation.GetTypeByMetadataName(WellKnownTypeNames.ITaskFullName);
            if (requiredAttributeType is null || iTaskType is null)
            {
                return;
            }

            foreach (Diagnostic diagnostic in context.ReportedDiagnostics)
            {
                if (!string.Equals(diagnostic.Id, "CS8618", StringComparison.Ordinal))
                {
                    continue;
                }

                if (!TryGetPropertySymbol(context, diagnostic, out IPropertySymbol? propertyCandidate) || propertyCandidate is null)
                {
                    continue;
                }

                IPropertySymbol property = propertyCandidate;
                INamedTypeSymbol? containingType = property.ContainingType;
                if (containingType is null || !SharedAnalyzerHelpers.ImplementsInterface(containingType, iTaskType))
                {
                    continue;
                }

                if (!CanBeAssignedByMSBuild(property) || !HasAttribute(property, requiredAttributeType))
                {
                    continue;
                }

                context.ReportSuppression(Suppression.Create(s_requiredTaskPropertyInitialization, diagnostic));
            }
        }

        private static bool TryGetPropertySymbol(
            SuppressionAnalysisContext context,
            Diagnostic diagnostic,
            out IPropertySymbol? property)
        {
            if (TryGetPropertySymbol(context, diagnostic.Location, out property))
            {
                return true;
            }

            foreach (Location location in diagnostic.AdditionalLocations)
            {
                if (TryGetPropertySymbol(context, location, out property))
                {
                    return true;
                }
            }

            property = null;
            return false;
        }

        private static bool TryGetPropertySymbol(
            SuppressionAnalysisContext context,
            Location location,
            out IPropertySymbol? property)
        {
            property = null;
            if (!location.IsInSource || location.SourceTree is null)
            {
                return false;
            }

            SemanticModel semanticModel = context.GetSemanticModel(location.SourceTree);
            SyntaxNode root = location.SourceTree.GetRoot(context.CancellationToken);
            SyntaxNode node = root.FindNode(location.SourceSpan, getInnermostNodeForTie: true);

            for (SyntaxNode? current = node; current is not null; current = current.Parent)
            {
                if (current is PropertyDeclarationSyntax propertyDeclaration)
                {
                    property = semanticModel.GetDeclaredSymbol(propertyDeclaration, context.CancellationToken) as IPropertySymbol;
                    return property is not null;
                }
            }

            ISymbol? symbol = semanticModel.GetEnclosingSymbol(location.SourceSpan.Start, context.CancellationToken);
            property = symbol switch
            {
                IPropertySymbol propertySymbol => propertySymbol,
                IMethodSymbol { AssociatedSymbol: IPropertySymbol propertySymbol } => propertySymbol,
                _ => null,
            };

            return property is not null;
        }

        private static bool CanBeAssignedByMSBuild(IPropertySymbol property)
        {
            IMethodSymbol? setMethod = property.SetMethod;
            return setMethod is not null && setMethod.DeclaredAccessibility == Accessibility.Public;
        }

        private static bool HasAttribute(IPropertySymbol property, INamedTypeSymbol attributeType)
        {
            foreach (AttributeData attribute in property.GetAttributes())
            {
                if (SymbolEqualityComparer.Default.Equals(attribute.AttributeClass, attributeType))
                {
                    return true;
                }
            }

            return false;
        }
    }
}