File: Microsoft.NetCore.Analyzers\InteropServices\DynamicInterfaceCastableImplementation.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.Collections.Immutable;
using Analyzer.Utilities;
using Analyzer.Utilities.Extensions;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;

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

    /// <summary>
    /// CA2256: <inheritdoc cref="InterfaceMembersMissingImplementationTitle"/>
    /// CA2257: <inheritdoc cref="MembersDeclaredOnImplementationTypeMustBeStaticTitle"/>
    /// CA2258: <inheritdoc cref="DynamicInterfaceCastableImplementationUnsupportedTitle"/>
    /// </summary>
    [DiagnosticAnalyzer(LanguageNames.CSharp, LanguageNames.VisualBasic)]
    internal sealed class DynamicInterfaceCastableImplementationAnalyzer : DiagnosticAnalyzer
    {
        internal const string DynamicInterfaceCastableImplementationUnsupportedRuleId = "CA2258";

        private static readonly DiagnosticDescriptor DynamicInterfaceCastableImplementationUnsupported =
            DiagnosticDescriptorHelper.Create(
                DynamicInterfaceCastableImplementationUnsupportedRuleId,
                CreateLocalizableResourceString(nameof(DynamicInterfaceCastableImplementationUnsupportedTitle)),
                CreateLocalizableResourceString(nameof(DynamicInterfaceCastableImplementationUnsupportedMessage)),
                DiagnosticCategory.Usage,
                RuleLevel.BuildWarning,
                CreateLocalizableResourceString(nameof(DynamicInterfaceCastableImplementationUnsupportedDescription)),
                isPortedFxCopRule: false,
                isDataflowRule: false);

        internal const string InterfaceMembersMissingImplementationRuleId = "CA2256";

        private static readonly DiagnosticDescriptor InterfaceMembersMissingImplementation =
            DiagnosticDescriptorHelper.Create(
                InterfaceMembersMissingImplementationRuleId,
                CreateLocalizableResourceString(nameof(InterfaceMembersMissingImplementationTitle)),
                CreateLocalizableResourceString(nameof(InterfaceMembersMissingImplementationMessage)),
                DiagnosticCategory.Usage,
                RuleLevel.BuildWarning,
                CreateLocalizableResourceString(nameof(InterfaceMembersMissingImplementationDescription)),
                isPortedFxCopRule: false,
                isDataflowRule: false);

        internal const string MembersDeclaredOnImplementationTypeMustBeStaticRuleId = "CA2257";

        private static readonly DiagnosticDescriptor MembersDeclaredOnImplementationTypeMustBeStatic =
            DiagnosticDescriptorHelper.Create(
                MembersDeclaredOnImplementationTypeMustBeStaticRuleId,
                CreateLocalizableResourceString(nameof(MembersDeclaredOnImplementationTypeMustBeStaticTitle)),
                CreateLocalizableResourceString(nameof(MembersDeclaredOnImplementationTypeMustBeStaticMessage)),
                DiagnosticCategory.Usage,
                RuleLevel.BuildWarning,
                CreateLocalizableResourceString(nameof(MembersDeclaredOnImplementationTypeMustBeStaticDescription)),
                isPortedFxCopRule: false,
                isDataflowRule: false);

        internal const string NonStaticMemberIsMethodKey = nameof(NonStaticMemberIsMethodKey);

        public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(
            DynamicInterfaceCastableImplementationUnsupported,
            InterfaceMembersMissingImplementation,
            MembersDeclaredOnImplementationTypeMustBeStatic);

        public override void Initialize(AnalysisContext context)
        {
            context.EnableConcurrentExecution();
            context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics);
            context.RegisterCompilationStartAction(context =>
            {
                if (context.Compilation.TryGetOrCreateTypeByMetadataName(WellKnownTypeNames.SystemRuntimeInteropServicesDynamicInterfaceCastableImplementationAttribute, out INamedTypeSymbol? dynamicInterfaceCastableImplementationAttribute))
                {
                    context.RegisterSymbolAction(context => AnalyzeType(context, dynamicInterfaceCastableImplementationAttribute), SymbolKind.NamedType);
                }
            });
        }

        private static void AnalyzeType(SymbolAnalysisContext context, INamedTypeSymbol dynamicInterfaceCastableImplementationAttribute)
        {
            INamedTypeSymbol targetType = (INamedTypeSymbol)context.Symbol;

            if (targetType.TypeKind != TypeKind.Interface)
            {
                return;
            }

            if (!targetType.HasAnyAttribute(dynamicInterfaceCastableImplementationAttribute))
            {
                return;
            }

            // Default Interface Methods are required to provide an IDynamicInterfaceCastable implementation type.
            // Since Visual Basic does not support DIMs, an implementation type cannot be correctly provided in VB.
            if (context.Compilation.Language == LanguageNames.VisualBasic)
            {
                context.ReportDiagnostic(targetType.CreateDiagnostic(DynamicInterfaceCastableImplementationUnsupported));
                return;
            }

            bool missingMethodImplementations = false;
            foreach (var iface in targetType.AllInterfaces)
            {
                foreach (var member in iface.GetMembers())
                {
                    if (member.IsAbstract
                        && member.Kind != SymbolKind.NamedType
                        && context.Compilation.IsSymbolAccessibleWithin(member, targetType)
                        && targetType.FindImplementationForInterfaceMember(member) is null)
                    {
                        missingMethodImplementations = true;
                        break;
                    }
                }

                // Once we find one missing method implementation, we can stop searching for missing implementations.
                if (missingMethodImplementations)
                {
                    break;
                }
            }

            if (missingMethodImplementations)
            {
                context.ReportDiagnostic(targetType.CreateDiagnostic(InterfaceMembersMissingImplementation, targetType.ToDisplayString()));
            }

            foreach (var member in targetType.GetMembers())
            {
                if (!member.IsImplementationOfAnyExplicitInterfaceMember() && !member.IsStatic && !member.IsType())
                {
                    // We don't want to emit diagnostics when the member is an accessor method.
                    if (member is not IMethodSymbol { AssociatedSymbol: IPropertySymbol or IEventSymbol })
                    {
                        // Emit diagnostic for non-concrete method on implementation interface
                        ImmutableDictionary<string, string?> propertyBag = ImmutableDictionary<string, string?>.Empty;
                        if (member is IMethodSymbol)
                        {
                            propertyBag = propertyBag.Add(NonStaticMemberIsMethodKey, string.Empty);
                        }

                        context.ReportDiagnostic(member.CreateDiagnostic(MembersDeclaredOnImplementationTypeMustBeStatic, propertyBag, member.ToDisplayString(), targetType.ToDisplayString()));
                    }
                }
            }
        }
    }
}