File: Shared\Extensions\INamespaceSymbolExtensions.cs
Web Access
Project: src\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.Shared.Extensions;
 
internal static partial class INamespaceSymbolExtensions
{
    private static readonly ConditionalWeakTable<INamespaceSymbol, List<string>> s_namespaceToNameMap = new();
 
    public static readonly Comparison<INamespaceSymbol> CompareNamespaces = CompareTo;
    public static readonly IEqualityComparer<INamespaceSymbol> EqualityComparer = new Comparer();
 
    private static List<string> GetNameParts(INamespaceSymbol? namespaceSymbol)
    {
        var result = new List<string>();
        GetNameParts(namespaceSymbol, result);
        return result;
    }
 
    private static void GetNameParts(INamespaceSymbol? namespaceSymbol, List<string> result)
    {
        if (namespaceSymbol == null || namespaceSymbol.IsGlobalNamespace)
        {
            return;
        }
 
        GetNameParts(namespaceSymbol.ContainingNamespace, result);
        result.Add(namespaceSymbol.Name);
    }
 
    public static int CompareTo(this INamespaceSymbol n1, INamespaceSymbol n2)
    {
        var names1 = s_namespaceToNameMap.GetValue(n1, GetNameParts);
        var names2 = s_namespaceToNameMap.GetValue(n2, GetNameParts);
 
        for (var i = 0; i < Math.Min(names1.Count, names2.Count); i++)
        {
            var comp = names1[i].CompareTo(names2[i]);
            if (comp != 0)
            {
                return comp;
            }
        }
 
        return names1.Count - names2.Count;
    }
 
    public static IEnumerable<INamespaceOrTypeSymbol> GetAllNamespacesAndTypes(
        this INamespaceSymbol namespaceSymbol,
        CancellationToken cancellationToken)
    {
        using var _ = ArrayBuilder<INamespaceOrTypeSymbol>.GetInstance(out var stack);
        stack.Push(namespaceSymbol);
 
        while (stack.TryPop(out var current))
        {
            cancellationToken.ThrowIfCancellationRequested();
            if (current is INamespaceSymbol childNamespace)
            {
                stack.AddRange(childNamespace.GetMembers());
                yield return childNamespace;
            }
            else
            {
                var child = (INamedTypeSymbol)current;
                stack.AddRange(child.GetTypeMembers());
                yield return child;
            }
        }
    }
 
    public static IEnumerable<INamespaceSymbol> GetAllNamespaces(
        this INamespaceSymbol namespaceSymbol,
        CancellationToken cancellationToken)
    {
        using var _ = ArrayBuilder<INamespaceSymbol>.GetInstance(out var stack);
        stack.Push(namespaceSymbol);
 
        while (stack.TryPop(out var childNamespace))
        {
            cancellationToken.ThrowIfCancellationRequested();
            stack.AddRange(childNamespace.GetNamespaceMembers());
            yield return childNamespace;
        }
    }
 
    public static IEnumerable<INamedTypeSymbol> GetAllTypes(
        this IEnumerable<INamespaceSymbol> namespaceSymbols,
        CancellationToken cancellationToken)
    {
        return namespaceSymbols.SelectMany(n => n.GetAllTypes(cancellationToken));
    }
 
    /// <summary>
    /// Searches the namespace for namespaces with the provided name.
    /// </summary>
    public static IEnumerable<INamespaceSymbol> FindNamespaces(
        this INamespaceSymbol namespaceSymbol,
        string namespaceName,
        CancellationToken cancellationToken)
    {
        cancellationToken.ThrowIfCancellationRequested();
 
        using var _ = ArrayBuilder<INamespaceSymbol>.GetInstance(out var stack);
        stack.Push(namespaceSymbol);
 
        while (stack.TryPop(out var current))
        {
            cancellationToken.ThrowIfCancellationRequested();
            var matchingChildren = current.GetMembers(namespaceName).OfType<INamespaceSymbol>();
            foreach (var child in matchingChildren)
                yield return child;
 
            stack.AddRange(current.GetNamespaceMembers());
        }
    }
 
    public static bool ContainsAccessibleTypesOrNamespaces(
        this INamespaceSymbol namespaceSymbol,
        IAssemblySymbol assembly)
    {
        using var namespaceQueue = SharedPools.Default<Queue<INamespaceOrTypeSymbol>>().GetPooledObject();
        return ContainsAccessibleTypesOrNamespacesWorker(namespaceSymbol, assembly, namespaceQueue.Object);
    }
 
    public static INamespaceSymbol? GetQualifiedNamespace(
        this INamespaceSymbol globalNamespace,
        string namespaceName)
    {
        var namespaceSymbol = globalNamespace;
        foreach (var name in namespaceName.Split('.'))
        {
            var members = namespaceSymbol.GetMembers(name);
            namespaceSymbol = members.Count() == 1
                    ? members.First() as INamespaceSymbol
                    : null;
 
            if (namespaceSymbol is null)
            {
                break;
            }
        }
 
        return namespaceSymbol;
    }
 
    private static bool ContainsAccessibleTypesOrNamespacesWorker(
        this INamespaceSymbol namespaceSymbol,
        IAssemblySymbol assembly,
        Queue<INamespaceOrTypeSymbol> namespaceQueue)
    {
        // Note: we only store INamespaceSymbols in here, even though we type it as 
        // INamespaceOrTypeSymbol.  This is because when we call GetMembers below we
        // want it to return an ImmutableArray so we don't incur any costs to iterate
        // over it.
 
        foreach (var constituent in namespaceSymbol.ConstituentNamespaces)
        {
            // Assume that any namespace in our own assembly is accessible to us.  This saves a
            // lot of cpu time checking namespaces.
            if (Equals(constituent.ContainingAssembly, assembly))
            {
                return true;
            }
 
            namespaceQueue.Enqueue(constituent);
        }
 
        while (namespaceQueue.Count > 0)
        {
            var ns = namespaceQueue.Dequeue();
 
            // Upcast so we call the 'GetMembers' method that returns an ImmutableArray.
            var members = ns.GetMembers();
 
            foreach (var namespaceOrType in members)
            {
                if (namespaceOrType.Kind == SymbolKind.NamedType)
                {
                    if (namespaceOrType.IsAccessibleWithin(assembly))
                    {
                        return true;
                    }
                }
                else
                {
                    namespaceQueue.Enqueue((INamespaceSymbol)namespaceOrType);
                }
            }
        }
 
        return false;
    }
}