File: src\Generators\Shared\RoslynExtensions.cs
Web Access
Project: src\src\Generators\Microsoft.Gen.Metrics\Microsoft.Gen.Metrics.csproj (Microsoft.Gen.Metrics)
// 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;
 
#pragma warning disable CA1716
namespace Microsoft.Gen.Shared;
#pragma warning restore CA1716
 
#if !SHARED_PROJECT
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
#endif
internal static class RoslynExtensions
{
    /// <summary>
    /// Gets a type by its metadata name to use for code analysis within a <see cref="Compilation"/>. This method
    /// attempts to find the "best" symbol to use for code analysis, which is the symbol matching the first of the
    /// following rules.
    ///
    /// <list type="number">
    ///   <item><description>
    ///     If only one type with the given name is found within the compilation and its referenced assemblies, that
    ///     type is returned regardless of accessibility.
    ///   </description></item>
    ///   <item><description>
    ///     If the current <paramref name="compilation"/> defines the symbol, that symbol is returned.
    ///   </description></item>
    ///   <item><description>
    ///     If exactly one referenced assembly defines the symbol in a manner that makes it visible to the current
    ///     <paramref name="compilation"/>, that symbol is returned.
    ///   </description></item>
    ///   <item><description>
    ///     Otherwise, this method returns <see langword="null"/>.
    ///   </description></item>
    /// </list>
    /// </summary>
    /// <param name="compilation">The <see cref="Compilation"/> to consider for analysis.</param>
    /// <param name="fullyQualifiedMetadataName">The fully-qualified metadata type name to find.</param>
    /// <returns>The symbol to use for code analysis; otherwise, <see langword="null"/>.</returns>
    // Copied from: https://github.com/dotnet/roslyn/blob/af7b0ebe2b0ed5c335a928626c25620566372dd1/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/CompilationExtensions.cs
    public static INamedTypeSymbol? GetBestTypeByMetadataName(this Compilation compilation, string fullyQualifiedMetadataName)
    {
        // Try to get the unique type with this name, ignoring accessibility
        var type = compilation.GetTypeByMetadataName(fullyQualifiedMetadataName);
 
        // Otherwise, try to get the unique type with this name originally defined in 'compilation'
        type ??= compilation.Assembly?.GetTypeByMetadataName(fullyQualifiedMetadataName);
 
        // Otherwise, try to get the unique accessible type with this name from a reference
        if (type is null)
        {
            if (compilation.Assembly is null)
            {
                // nothing to do
                return null;
            }
 
            foreach (var module in compilation.Assembly.Modules)
            {
                foreach (var referencedAssembly in module.ReferencedAssemblySymbols)
                {
                    var currentType = referencedAssembly.GetTypeByMetadataName(fullyQualifiedMetadataName);
                    if (currentType is null)
                    {
                        continue;
                    }
 
                    switch (currentType.GetResultantVisibility())
                    {
                        case SymbolVisibility.Public:
                        case SymbolVisibility.Internal when referencedAssembly.GivesAccessTo(compilation.Assembly):
                            break;
 
                        default:
                            continue;
                    }
 
                    if (type is object)
                    {
                        // Multiple visible types with the same metadata name are present
                        return null;
                    }
 
                    type = currentType;
                }
            }
        }
 
        return type;
    }
 
    /// <summary>
    /// A thin wrapper over <see cref="GetBestTypeByMetadataName(Compilation, string)"/>,
    /// but taking the type itself rather than the fully-qualified metadata type name.
    /// </summary>
    /// <param name="compilation">The <see cref="Compilation"/> to consider for analysis.</param>
    /// <param name="type">The type to find.</param>
    public static INamedTypeSymbol? GetBestTypeByMetadataName(this Compilation compilation, Type type) =>
        type.IsArray || type.FullName is null
            ? throw new ArgumentException("The input type must correspond to a named type symbol.")
            : GetBestTypeByMetadataName(compilation, type.FullName);
 
    public static ImmutableArray<T> ToImmutableArray<T>(this ReadOnlySpan<T> span)
    {
#pragma warning disable S109 // Magic numbers should not be used
        switch (span.Length)
        {
            case 0:
                return ImmutableArray<T>.Empty;
            case 1:
                return ImmutableArray.Create(span[0]);
            case 2:
                return ImmutableArray.Create(span[0], span[1]);
            case 3:
                return ImmutableArray.Create(span[0], span[1], span[2]);
            case 4:
                return ImmutableArray.Create(span[0], span[1], span[2], span[3]);
            default:
                var builder = ImmutableArray.CreateBuilder<T>(span.Length);
                foreach (var item in span)
                {
                    builder.Add(item);
                }
 
                return builder.MoveToImmutable();
        }
#pragma warning restore S109 // Magic numbers should not be used
    }
 
    public static SimpleNameSyntax GetUnqualifiedName(this NameSyntax name)
        => name switch
        {
            AliasQualifiedNameSyntax alias => alias.Name,
            QualifiedNameSyntax qualified => qualified.Right,
            SimpleNameSyntax simple => simple,
            _ => throw new InvalidOperationException("Unreachable"),
        };
 
    // Copied from: https://github.com/dotnet/roslyn/blob/af7b0ebe2b0ed5c335a928626c25620566372dd1/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Extensions/ISymbolExtensions.cs
    private static SymbolVisibility GetResultantVisibility(this ISymbol symbol)
    {
        // Start by assuming it's visible.
        SymbolVisibility visibility = SymbolVisibility.Public;
 
        switch (symbol.Kind)
        {
            case SymbolKind.Alias:
                // Aliases are uber private.  They're only visible in the same file that they
                // were declared in.
                return SymbolVisibility.Private;
 
            case SymbolKind.Parameter:
                // Parameters are only as visible as their containing symbol
                return GetResultantVisibility(symbol.ContainingSymbol);
 
            case SymbolKind.TypeParameter:
                // Type Parameters are private.
                return SymbolVisibility.Private;
        }
 
        while (symbol is not null && symbol.Kind != SymbolKind.Namespace)
        {
            switch (symbol.DeclaredAccessibility)
            {
                // If we see anything private, then the symbol is private.
                case Accessibility.NotApplicable:
                case Accessibility.Private:
                    return SymbolVisibility.Private;
 
                // If we see anything internal, then knock it down from public to
                // internal.
                case Accessibility.Internal:
                case Accessibility.ProtectedAndInternal:
                    visibility = SymbolVisibility.Internal;
                    break;
 
                    // For anything else (Public, Protected, ProtectedOrInternal), the
                    // symbol stays at the level we've gotten so far.
            }
 
            symbol = symbol.ContainingSymbol;
        }
 
        return visibility;
    }
 
    // Copied from: https://github.com/dotnet/roslyn/blob/af7b0ebe2b0ed5c335a928626c25620566372dd1/src/Workspaces/SharedUtilitiesAndExtensions/Compiler/Core/Utilities/SymbolVisibility.cs
#pragma warning disable CA1027 // Mark enums with FlagsAttribute
    private enum SymbolVisibility
#pragma warning restore CA1027 // Mark enums with FlagsAttribute
    {
        Public = 0,
        Internal = 1,
        Private = 2,
        Friend = Internal,
    }
 
    internal static bool HasAttributeSuffix(this string name, bool isCaseSensitive)
    {
        const string AttributeSuffix = "Attribute";
 
        var comparison = isCaseSensitive ? StringComparison.Ordinal : StringComparison.OrdinalIgnoreCase;
        return name.Length > AttributeSuffix.Length && name.EndsWith(AttributeSuffix, comparison);
    }
}