File: Analyzers\GeneratedComInterfaceAttributeAnalyzer.cs
Web Access
Project: src\src\libraries\System.Runtime.InteropServices\gen\ComInterfaceGenerator\ComInterfaceGenerator.csproj (Microsoft.Interop.ComInterfaceGenerator)
// 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 System.Diagnostics.CodeAnalysis;
using System.Runtime.InteropServices;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Diagnostics;
 
namespace Microsoft.Interop.Analyzers
{
    /// <summary>
    /// Validates that if an interface has GeneratedComInterfaceAttribute and <see cref="InterfaceTypeAttribute"/>,
    /// the <see cref="InterfaceTypeAttribute"/> is given a <see cref="ComInterfaceType"/> that is supported by the generator.
    /// </summary>
    [DiagnosticAnalyzer(LanguageNames.CSharp)]
    public class GeneratedComInterfaceAttributeAnalyzer : DiagnosticAnalyzer
    {
        public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; }
            = ImmutableArray.Create(GeneratorDiagnostics.InterfaceTypeNotSupported);
 
        public static readonly ImmutableArray<ComInterfaceType> SupportedComInterfaceTypes = ImmutableArray.Create(ComInterfaceType.InterfaceIsIUnknown);
 
        public override void Initialize(AnalysisContext context)
        {
            context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
            context.EnableConcurrentExecution();
            context.RegisterSymbolAction((context) =>
            {
                INamedTypeSymbol typeSymbol = (INamedTypeSymbol)context.Symbol;
                if (typeSymbol.TypeKind != TypeKind.Interface)
                    return;
 
                ImmutableArray<AttributeData> customAttributes = typeSymbol.GetAttributes();
                if (customAttributes.Length == 0)
                    return;
 
                // Interfaces with both GeneratedComInterfaceAttribute and InterfaceTypeAttribute should only have [InterfaceTypeAttribute(InterfaceIsIUnknown)]
                if (GetAttribute(typeSymbol, TypeNames.GeneratedComInterfaceAttribute, out _)
                    && GetAttribute(typeSymbol, TypeNames.InterfaceTypeAttribute, out AttributeData? comInterfaceAttribute)
                    && !InterfaceTypeAttributeIsSupported(comInterfaceAttribute, out string unsupportedValue))
                {
                    context.ReportDiagnostic(comInterfaceAttribute.CreateDiagnosticInfo(GeneratorDiagnostics.InterfaceTypeNotSupported, unsupportedValue).ToDiagnostic());
                }
            }, SymbolKind.NamedType);
        }
 
        private static bool InterfaceTypeAttributeIsSupported(AttributeData comInterfaceAttribute, out string argument)
        {
            if (comInterfaceAttribute.ConstructorArguments.IsEmpty)
            {
                argument = "<empty>";
                return false;
            }
            TypedConstant ctorArg0 = comInterfaceAttribute.ConstructorArguments[0];
            ComInterfaceType interfaceType;
 
            argument = ctorArg0.ToCSharpString();
            switch (ctorArg0.Type.ToDisplayString())
            {
                case TypeNames.ComInterfaceType:
                    interfaceType = (ComInterfaceType)ctorArg0.Value;
                    break;
                case TypeNames.System_Int16:
                case TypeNames.@short:
                    interfaceType = (ComInterfaceType)(short)ctorArg0.Value;
                    break;
                default:
                    return false;
            }
 
            return SupportedComInterfaceTypes.Contains(interfaceType);
        }
 
        private static bool GetAttribute(ISymbol symbol, string attributeDisplayName, [NotNullWhen(true)] out AttributeData? attribute)
        {
            foreach (AttributeData attr in symbol.GetAttributes())
            {
                if (attr.AttributeClass?.ToDisplayString() == attributeDisplayName)
                {
                    attribute = attr;
                    return true;
                }
            }
 
            attribute = null;
            return false;
        }
    }
}