File: Library\ObjectBrowser\AbstractListItemFactory.cs
Web Access
Project: src\src\VisualStudio\Core\Def\Microsoft.VisualStudio.LanguageServices_su2z3x3s_wpftmp.csproj (Microsoft.VisualStudio.LanguageServices)
// 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.
 
#nullable disable
 
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Microsoft.VisualStudio.LanguageServices.Implementation.Library.ObjectBrowser.Lists;
using Roslyn.Utilities;
 
namespace Microsoft.VisualStudio.LanguageServices.Implementation.Library.ObjectBrowser;
 
internal abstract class AbstractListItemFactory
{
    private static readonly SymbolDisplayFormat s_searchFormat =
        new(typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameOnly);
 
    private static readonly SymbolDisplayFormat s_simplePredefinedTypeDisplay =
        new(
            typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypes);
 
    private static readonly SymbolDisplayFormat s_simpleNormalTypeDisplay =
        new(
            typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypes,
            genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters | SymbolDisplayGenericsOptions.IncludeVariance,
            miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes);
 
    private static readonly SymbolDisplayFormat s_simplePredefinedTypeFullName =
        new(
            typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces);
 
    private static readonly SymbolDisplayFormat s_simpleNormalTypeFullName =
        new(
            typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
            genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters | SymbolDisplayGenericsOptions.IncludeVariance,
            miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes);
 
    private static readonly SymbolDisplayFormat s_predefinedTypeDisplay =
        new(
            typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces);
 
    private static readonly SymbolDisplayFormat s_normalTypeDisplay =
        new(
            typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
            genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters | SymbolDisplayGenericsOptions.IncludeVariance,
            miscellaneousOptions: SymbolDisplayMiscellaneousOptions.UseSpecialTypes);
 
    protected static string GetSimpleDisplayText(INamedTypeSymbol namedTypeSymbol)
    {
        return namedTypeSymbol.SpecialType.ToPredefinedType() != PredefinedType.None
            ? namedTypeSymbol.ToDisplayString(s_simplePredefinedTypeDisplay)
            : namedTypeSymbol.ToDisplayString(s_simpleNormalTypeDisplay);
    }
 
    protected abstract string GetMemberDisplayString(ISymbol memberSymbol);
    protected abstract string GetMemberAndTypeDisplayString(ISymbol memberSymbol);
 
    protected MemberListItem CreateFullyQualifiedMemberListItem(ISymbol memberSymbol, ProjectId projectId, bool hidden)
    {
        var displayText = GetMemberAndTypeDisplayString(memberSymbol);
        var fullNameText = displayText;
        var searchText = memberSymbol.ToDisplayString(s_searchFormat);
 
        return new MemberListItem(projectId, memberSymbol, displayText, fullNameText, searchText, hidden, isInherited: true);
    }
 
    protected MemberListItem CreateInheritedMemberListItem(ISymbol memberSymbol, ProjectId projectId, bool hidden)
        => CreateMemberListItem(memberSymbol, projectId, hidden, isInherited: true);
 
    protected MemberListItem CreateSimpleMemberListItem(ISymbol memberSymbol, ProjectId projectId, bool hidden)
        => CreateMemberListItem(memberSymbol, projectId, hidden, isInherited: false);
 
    private MemberListItem CreateMemberListItem(ISymbol memberSymbol, ProjectId projectId, bool hidden, bool isInherited)
    {
        var displayText = GetMemberDisplayString(memberSymbol);
 
        var containingType = memberSymbol.ContainingType;
        var fullNameText = containingType != null
            ? GetSimpleDisplayText(containingType) + "." + displayText
            : displayText;
 
        var searchText = memberSymbol.ToDisplayString(s_searchFormat);
 
        return new MemberListItem(projectId, memberSymbol, displayText, fullNameText, searchText, hidden, isInherited: isInherited);
    }
 
    protected TypeListItem CreateSimpleTypeListItem(INamedTypeSymbol namedTypeSymbol, ProjectId projectId, bool hidden)
    {
        var displayText = GetSimpleDisplayText(namedTypeSymbol);
 
        var fullNameText = namedTypeSymbol.SpecialType.ToPredefinedType() != PredefinedType.None
            ? namedTypeSymbol.ToDisplayString(s_simplePredefinedTypeFullName)
            : namedTypeSymbol.ToDisplayString(s_simpleNormalTypeFullName);
 
        var searchText = namedTypeSymbol.ToDisplayString(s_searchFormat);
 
        return new TypeListItem(projectId, namedTypeSymbol, displayText, fullNameText, searchText, hidden);
    }
 
    protected TypeListItem CreateFullyQualifiedTypeListItem(INamedTypeSymbol namedTypeSymbol, ProjectId projectId, bool hidden)
    {
        var displayText = namedTypeSymbol.SpecialType.ToPredefinedType() != PredefinedType.None
            ? namedTypeSymbol.ToDisplayString(s_predefinedTypeDisplay)
            : namedTypeSymbol.ToDisplayString(s_normalTypeDisplay);
 
        var fullNameText = displayText;
 
        var searchText = namedTypeSymbol.ToDisplayString(s_searchFormat);
 
        return new TypeListItem(projectId, namedTypeSymbol, displayText, fullNameText, searchText, hidden);
    }
 
    protected NamespaceListItem CreateNamespaceListItem(INamespaceSymbol namespaceSymbol, ProjectId projectId)
    {
        var text = namespaceSymbol.ToDisplayString();
 
        return new NamespaceListItem(projectId, namespaceSymbol, displayText: text, fullNameText: text, searchText: text);
    }
 
    private static bool IncludeSymbol(ISymbol symbol)
    {
        return symbol.IsErrorType()
            || symbol.Locations.Any(static l => l.IsInSource || l.IsInMetadata);
    }
 
    private static bool IncludeMemberSymbol(ISymbol symbol, IAssemblySymbol assemblySymbol)
    {
        if (symbol.Kind == SymbolKind.NamedType)
        {
            return false;
        }
 
        if (symbol.IsImplicitlyDeclared || symbol.IsAccessor())
        {
            return false;
        }
 
        if (symbol.Locations.Any(static l => l.IsInSource))
        {
            return true;
        }
 
        if (symbol.Locations.Any(static l => l.IsInMetadata))
        {
            // We want to display protected members because we don't really have through
            // type to pass along for protected checks. Besides, protected members are 
            // interesting for the user to know about since they could create a sub class 
            // and access them.
            return symbol.DeclaredAccessibility is Accessibility.Protected or Accessibility.ProtectedOrInternal
                || symbol.IsAccessibleWithin(assemblySymbol);
        }
 
        return false;
    }
 
    private static ImmutableArray<ObjectListItem> CreateListItemsFromSymbols<TSymbol>(
        ImmutableArray<TSymbol> symbols,
        Compilation compilation,
        ProjectId projectId,
        Func<TSymbol, ProjectId, bool, ObjectListItem> listItemCreator)
        where TSymbol : class, ISymbol
    {
        var builder = ImmutableArray.CreateBuilder<ObjectListItem>(symbols.Length);
 
        AddListItemsFromSymbols(symbols, compilation, projectId, listItemCreator, builder);
 
        return builder.ToImmutableAndClear();
    }
 
    private static void AddListItemsFromSymbols<TSymbol>(
        IEnumerable<TSymbol> symbols,
        Compilation compilation,
        ProjectId projectId,
        Func<TSymbol, ProjectId, bool, ObjectListItem> listItemCreator,
        ImmutableArray<ObjectListItem>.Builder builder)
        where TSymbol : class, ISymbol
    {
        var editorBrowsableInfo = new EditorBrowsableHelpers.EditorBrowsableInfo(compilation);
 
        foreach (var symbol in symbols)
        {
            if (!IncludeSymbol(symbol))
            {
                continue;
            }
 
            var hideAdvancedMembers = false;
            var isHidden = !symbol.IsEditorBrowsable(hideAdvancedMembers, compilation, editorBrowsableInfo);
 
            builder.Add(listItemCreator(symbol, projectId, isHidden));
        }
    }
 
    private ImmutableArray<ObjectListItem> GetBaseTypeListItems(INamedTypeSymbol namedTypeSymbol, Compilation compilation, ProjectId projectId)
    {
        // Special case: System.Object doesn't have a base type
        if (namedTypeSymbol.SpecialType == SpecialType.System_Object)
        {
            return ImmutableArray<ObjectListItem>.Empty;
        }
 
        var symbolBuilder = ImmutableArray.CreateBuilder<INamedTypeSymbol>();
 
        // Add base if not an interface
        if (namedTypeSymbol.TypeKind != TypeKind.Interface)
        {
            symbolBuilder.Add(namedTypeSymbol.BaseType);
        }
 
        foreach (var interfaceSymbol in namedTypeSymbol.Interfaces)
        {
            symbolBuilder.Add(interfaceSymbol);
        }
 
        return CreateListItemsFromSymbols(symbolBuilder.ToImmutable(), compilation, projectId, CreateSimpleTypeListItem);
    }
 
    public ImmutableArray<ObjectListItem> GetBaseTypeListItems(ObjectListItem parentListItem, Compilation compilation)
    {
        Debug.Assert(parentListItem != null);
        Debug.Assert(parentListItem is TypeListItem);
        Debug.Assert(compilation != null);
 
        if (parentListItem is not TypeListItem parentTypeItem)
        {
            return ImmutableArray<ObjectListItem>.Empty;
        }
 
        var typeSymbol = parentTypeItem.ResolveTypedSymbol(compilation);
 
        return GetBaseTypeListItems(typeSymbol, compilation, parentListItem.ProjectId);
    }
 
    public ImmutableArray<ObjectListItem> GetFolderListItems(ObjectListItem parentListItem, Compilation compilation)
    {
        Debug.Assert(parentListItem != null);
        Debug.Assert(parentListItem is TypeListItem or
                     ProjectListItem);
        Debug.Assert(compilation != null);
 
        // Hierarchies are parented by either a project or a type. In the case that it's a project, we show a folder
        // for "Project References". For types, we show a "Base Types" folder.
 
        var builder = ImmutableArray.CreateBuilder<ObjectListItem>();
 
        if (parentListItem is ProjectListItem)
        {
            builder.Add(new FolderListItem(parentListItem.ProjectId, ServicesVSResources.Project_References));
        }
 
        if (parentListItem is TypeListItem parentTypeItem)
        {
            var typeSymbol = parentTypeItem.ResolveTypedSymbol(compilation);
 
            var addBaseTypes = false;
 
            if (typeSymbol.TypeKind == TypeKind.Interface)
            {
                if (typeSymbol.Interfaces.Length > 0)
                {
                    addBaseTypes = true;
                }
            }
            else if (typeSymbol.TypeKind != TypeKind.Module &&
                     typeSymbol.SpecialType != SpecialType.System_Object)
            {
                addBaseTypes = true;
            }
 
            if (addBaseTypes)
            {
                builder.Add(new FolderListItem(parentListItem.ProjectId, ServicesVSResources.Base_Types));
            }
        }
 
        return builder.ToImmutableAndClear();
    }
 
    private ImmutableArray<ObjectListItem> GetMemberListItems(
        INamedTypeSymbol namedTypeSymbol,
        Compilation compilation,
        ProjectId projectId,
        bool fullyQualified = false)
    {
        var builder = ImmutableArray.CreateBuilder<ObjectListItem>();
 
        var immediateMembers = GetMemberSymbols(namedTypeSymbol, compilation);
 
        if (fullyQualified)
        {
            AddListItemsFromSymbols(immediateMembers, compilation, projectId, CreateFullyQualifiedMemberListItem, builder);
        }
        else
        {
            AddListItemsFromSymbols(immediateMembers, compilation, projectId, CreateSimpleMemberListItem, builder);
 
            var inheritedMembers = GetInheritedMemberSymbols(namedTypeSymbol, compilation);
 
            AddListItemsFromSymbols(inheritedMembers, compilation, projectId, CreateInheritedMemberListItem, builder);
        }
 
        return builder.ToImmutableAndClear();
    }
 
    private static ImmutableArray<ISymbol> GetMemberSymbols(INamedTypeSymbol namedTypeSymbol, Compilation compilation)
    {
        var members = namedTypeSymbol.GetMembers();
        var symbolBuilder = ImmutableArray.CreateBuilder<ISymbol>(members.Length);
 
        foreach (var member in members)
        {
            if (IncludeMemberSymbol(member, compilation.Assembly))
            {
                symbolBuilder.Add(member);
            }
        }
 
        return symbolBuilder.ToImmutableAndClear();
    }
 
    private static ImmutableArray<ISymbol> GetInheritedMemberSymbols(INamedTypeSymbol namedTypeSymbol, Compilation compilation)
    {
        var symbolBuilder = ImmutableArray.CreateBuilder<ISymbol>();
 
        HashSet<ISymbol> overriddenMembers = null;
        AddOverriddenMembers(namedTypeSymbol, ref overriddenMembers);
 
        foreach (var baseType in namedTypeSymbol.GetBaseTypes())
        {
            AddOverriddenMembers(baseType, ref overriddenMembers);
 
            foreach (var member in baseType.GetMembers())
            {
                if (member is IMethodSymbol methodSymbol)
                {
                    if (methodSymbol.MethodKind is MethodKind.Destructor or MethodKind.Constructor ||
                        methodSymbol.IsImplicitlyDeclared)
                    {
                        continue;
                    }
                }
 
                if (!member.IsAccessibleWithin(namedTypeSymbol))
                {
                    continue;
                }
 
                if (overriddenMembers != null && overriddenMembers.Contains(member))
                {
                    continue;
                }
 
                if (IncludeMemberSymbol(member, compilation.Assembly))
                {
                    symbolBuilder.Add(member);
                }
            }
        }
 
        return symbolBuilder.ToImmutableAndClear();
    }
 
    private static void AddOverriddenMembers(INamedTypeSymbol namedTypeSymbol, ref HashSet<ISymbol> overriddenMembers)
    {
        foreach (var member in namedTypeSymbol.GetMembers())
        {
            if (member.IsOverride)
            {
                for (var overriddenMember = member.GetOverriddenMember(); overriddenMember != null; overriddenMember = overriddenMember.GetOverriddenMember())
                {
                    overriddenMembers ??= [];
                    overriddenMembers.Add(overriddenMember);
                }
            }
        }
    }
 
    public ImmutableArray<ObjectListItem> GetMemberListItems(ObjectListItem parentListItem, Compilation compilation)
    {
        Debug.Assert(parentListItem != null);
        Debug.Assert(parentListItem is TypeListItem);
        Debug.Assert(compilation != null);
 
        if (parentListItem is not TypeListItem parentTypeItem)
        {
            return ImmutableArray<ObjectListItem>.Empty;
        }
 
        var typeSymbol = parentTypeItem.ResolveTypedSymbol(compilation);
 
        return GetMemberListItems(typeSymbol, compilation, parentListItem.ProjectId);
    }
 
    public void CollectNamespaceListItems(IAssemblySymbol assemblySymbol, ProjectId projectId, ImmutableArray<ObjectListItem>.Builder builder, string searchString)
    {
        Debug.Assert(assemblySymbol != null);
 
        using var _ = ArrayBuilder<INamespaceSymbol>.GetInstance(out var stack);
        stack.Push(assemblySymbol.GlobalNamespace);
 
        while (stack.TryPop(out var namespaceSymbol))
        {
            // Only add non-global namespaces that contain accessible type symbols.
            if (!namespaceSymbol.IsGlobalNamespace &&
                ContainsAccessibleTypeMember(namespaceSymbol, assemblySymbol))
            {
                var namespaceListItem = CreateNamespaceListItem(namespaceSymbol, projectId);
 
                if (searchString == null)
                {
                    builder.Add(namespaceListItem);
                }
                else if (namespaceListItem.SearchText.IndexOf(searchString, StringComparison.OrdinalIgnoreCase) >= 0)
                {
                    builder.Add(namespaceListItem);
                }
            }
 
            // Visit any nested namespaces
            foreach (var namespaceMember in namespaceSymbol.GetNamespaceMembers())
            {
                stack.Push(namespaceMember);
            }
        }
    }
 
    public ImmutableArray<ObjectListItem> GetNamespaceListItems(ObjectListItem parentListItem, Compilation compilation)
    {
        Debug.Assert(parentListItem != null);
        Debug.Assert(parentListItem is ProjectListItem or
                     ReferenceListItem);
        Debug.Assert(compilation != null);
 
        var assemblySymbol = parentListItem is ReferenceListItem
            ? ((ReferenceListItem)parentListItem).GetAssembly(compilation)
            : compilation.Assembly;
 
        var builder = ImmutableArray.CreateBuilder<ObjectListItem>();
 
        CollectNamespaceListItems(assemblySymbol, parentListItem.ProjectId, builder, searchString: null);
 
        return builder.ToImmutableAndClear();
    }
 
    private class AssemblySymbolComparer : IEqualityComparer<Tuple<ProjectId, IAssemblySymbol>>
    {
        public bool Equals(Tuple<ProjectId, IAssemblySymbol> x, Tuple<ProjectId, IAssemblySymbol> y)
            => x.Item2.Identity.Equals(y.Item2.Identity);
 
        public int GetHashCode(Tuple<ProjectId, IAssemblySymbol> obj)
            => obj.Item2.Identity.GetHashCode();
    }
 
    public ImmutableHashSet<Tuple<ProjectId, IAssemblySymbol>> GetAssemblySet(Solution solution, string languageName, CancellationToken cancellationToken)
    {
        var set = ImmutableHashSet.CreateBuilder(new AssemblySymbolComparer());
 
        foreach (var projectId in solution.ProjectIds)
        {
            cancellationToken.ThrowIfCancellationRequested();
 
            var project = solution.GetProject(projectId);
            if (project.Language != languageName)
            {
                continue;
            }
 
            if (project.IsVenus())
            {
                continue;
            }
 
            var compilation = project
                .GetCompilationAsync(cancellationToken)
                .WaitAndGetResult(cancellationToken);
 
            if (compilation != null &&
                compilation.Assembly != null)
            {
                set.Add(Tuple.Create(projectId, compilation.Assembly));
 
                foreach (var reference in project.MetadataReferences)
                {
                    cancellationToken.ThrowIfCancellationRequested();
 
                    if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol referenceAssembly)
                    {
                        set.Add(Tuple.Create(projectId, referenceAssembly));
                    }
                }
            }
        }
 
        return set.ToImmutable();
    }
 
    public ImmutableHashSet<Tuple<ProjectId, IAssemblySymbol>> GetAssemblySet(Project project, bool lookInReferences, CancellationToken cancellationToken)
    {
        var set = ImmutableHashSet.CreateBuilder(new AssemblySymbolComparer());
 
        var compilation = project
            .GetCompilationAsync(cancellationToken)
            .WaitAndGetResult(cancellationToken);
 
        if (compilation != null &&
            compilation.Assembly != null)
        {
            set.Add(Tuple.Create(project.Id, compilation.Assembly));
 
            if (lookInReferences)
            {
                foreach (var reference in project.MetadataReferences)
                {
                    cancellationToken.ThrowIfCancellationRequested();
 
                    if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol referenceAssembly)
                    {
                        set.Add(Tuple.Create(project.Id, referenceAssembly));
                    }
                }
            }
        }
 
        return set.ToImmutable();
    }
 
    private static bool ContainsAccessibleTypeMember(INamespaceOrTypeSymbol namespaceOrTypeSymbol, IAssemblySymbol assemblySymbol)
    {
        foreach (var typeMember in namespaceOrTypeSymbol.GetTypeMembers())
        {
            if (IncludeSymbol(typeMember) && typeMember.IsAccessibleWithin(assemblySymbol))
            {
                return true;
            }
        }
 
        return false;
    }
 
    private static ImmutableArray<INamedTypeSymbol> GetAccessibleTypeMembers(INamespaceOrTypeSymbol namespaceOrTypeSymbol, IAssemblySymbol assemblySymbol)
    {
        var typeMembers = namespaceOrTypeSymbol.GetTypeMembers();
        var builder = ImmutableArray.CreateBuilder<INamedTypeSymbol>(typeMembers.Length);
 
        foreach (var typeMember in typeMembers)
        {
            if (IncludeTypeMember(typeMember, assemblySymbol))
            {
                builder.Add(typeMember);
            }
        }
 
        return builder.ToImmutableAndClear();
    }
 
    private static bool IncludeTypeMember(INamedTypeSymbol typeMember, IAssemblySymbol assemblySymbol)
    {
        if (!IncludeSymbol(typeMember))
        {
            return false;
        }
 
        if (typeMember.Locations.Any(static l => l.IsInSource))
        {
            return true;
        }
 
        if (typeMember.Locations.Any(static l => l.IsInMetadata))
        {
            return typeMember.IsAccessibleWithin(assemblySymbol);
        }
 
        return false;
    }
 
    public ImmutableArray<ObjectListItem> GetProjectListItems(Solution solution, string languageName, uint listFlags)
    {
        var projectIds = solution.ProjectIds;
        if (!projectIds.Any())
        {
            return ImmutableArray<ObjectListItem>.Empty;
        }
 
        var projectListItemBuilder = ImmutableArray.CreateBuilder<ObjectListItem>();
        var referenceListItemBuilder = ImmutableArray.CreateBuilder<ObjectListItem>();
        HashSet<AssemblyIdentity> assemblyIdentitySet = null;
        var visitedAssemblies = new Dictionary<string, AssemblyIdentity>();
 
        foreach (var projectId in projectIds)
        {
            var project = solution.GetProject(projectId);
            if (project.Language != languageName)
            {
                continue;
            }
 
            if (project.IsVenus())
            {
                continue;
            }
 
            projectListItemBuilder.Add(new ProjectListItem(project));
 
            if (Helpers.IsObjectBrowser(listFlags))
            {
                assemblyIdentitySet ??= [];
 
                foreach (var reference in project.MetadataReferences)
                {
                    if (reference is PortableExecutableReference portableExecutableReference)
                    {
                        var assemblyIdentity = visitedAssemblies.GetOrAdd(portableExecutableReference.FilePath, filePath => AssemblyIdentityUtils.TryGetAssemblyIdentity(filePath));
                        if (assemblyIdentity != null && !assemblyIdentitySet.Contains(assemblyIdentity))
                        {
                            assemblyIdentitySet.Add(assemblyIdentity);
 
                            var referenceListItem = new ReferenceListItem(projectId, assemblyIdentity.Name, reference);
                            referenceListItemBuilder.Add(referenceListItem);
                        }
                    }
                }
            }
        }
 
        projectListItemBuilder.AddRange(referenceListItemBuilder);
 
        return projectListItemBuilder.ToImmutableAndClear();
    }
 
    public ImmutableArray<ObjectListItem> GetReferenceListItems(ObjectListItem parentListItem, Compilation compilation)
    {
        Debug.Assert(parentListItem != null);
        Debug.Assert(parentListItem is ProjectListItem);
        Debug.Assert(compilation != null);
 
        if (!compilation.References.Any())
        {
            return ImmutableArray<ObjectListItem>.Empty;
        }
 
        var builder = ArrayBuilder<ObjectListItem>.GetInstance();
 
        foreach (var reference in compilation.References)
        {
 
            if (compilation.GetAssemblyOrModuleSymbol(reference) is IAssemblySymbol assemblySymbol)
            {
                builder.Add(new ReferenceListItem(parentListItem.ProjectId, assemblySymbol.Name, reference));
            }
        }
 
        return builder.ToImmutableAndFree();
    }
 
    private static ImmutableArray<INamedTypeSymbol> GetAccessibleTypes(INamespaceSymbol namespaceSymbol, Compilation compilation)
    {
        var typeMembers = GetAccessibleTypeMembers(namespaceSymbol, compilation.Assembly);
        var builder = ImmutableArray.CreateBuilder<INamedTypeSymbol>(typeMembers.Length);
        using var _ = ArrayBuilder<INamedTypeSymbol>.GetInstance(out var stack);
 
        foreach (var typeMember in typeMembers)
        {
            stack.Push(typeMember);
 
            while (stack.TryPop(out var typeSymbol))
            {
                builder.Add(typeSymbol);
 
                foreach (var nestedTypeMember in GetAccessibleTypeMembers(typeSymbol, compilation.Assembly))
                {
                    stack.Push(nestedTypeMember);
                }
            }
        }
 
        return builder.ToImmutableAndClear();
    }
 
    private ImmutableArray<ObjectListItem> GetTypeListItems(
        INamespaceSymbol namespaceSymbol,
        Compilation compilation,
        ProjectId projectId,
        string searchString,
        bool fullyQualified = false)
    {
        var types = GetAccessibleTypes(namespaceSymbol, compilation);
 
        var listItems = fullyQualified
            ? CreateListItemsFromSymbols(types, compilation, projectId, CreateFullyQualifiedTypeListItem)
            : CreateListItemsFromSymbols(types, compilation, projectId, CreateSimpleTypeListItem);
 
        if (searchString == null)
        {
            return listItems;
        }
 
        var finalBuilder = ImmutableArray.CreateBuilder<ObjectListItem>();
 
        foreach (var listItem in listItems)
        {
            if (listItem.DisplayText.IndexOf(searchString, StringComparison.OrdinalIgnoreCase) >= 0)
            {
                finalBuilder.Add(listItem);
            }
        }
 
        return finalBuilder.ToImmutableAndClear();
    }
 
    public ImmutableArray<ObjectListItem> GetTypeListItems(ObjectListItem parentListItem, Compilation compilation)
    {
        Debug.Assert(parentListItem != null);
        Debug.Assert(parentListItem is NamespaceListItem or
                     ProjectListItem or
                     ReferenceListItem);
        Debug.Assert(compilation != null);
 
        INamespaceSymbol namespaceSymbol;
        if (parentListItem is NamespaceListItem namespaceList)
        {
            namespaceSymbol = namespaceList.ResolveTypedSymbol(compilation);
        }
        else if (parentListItem is ReferenceListItem referenceList)
        {
            namespaceSymbol = referenceList.GetAssembly(compilation).GlobalNamespace;
        }
        else
        {
            namespaceSymbol = compilation.Assembly.GlobalNamespace;
        }
 
        return GetTypeListItems(namespaceSymbol, compilation, parentListItem.ProjectId, searchString: null);
    }
 
    public void CollectTypeListItems(IAssemblySymbol assemblySymbol, Compilation compilation, ProjectId projectId, ImmutableArray<ObjectListItem>.Builder builder, string searchString)
    {
        Debug.Assert(assemblySymbol != null);
        Debug.Assert(compilation != null);
 
        using var _ = ArrayBuilder<INamespaceSymbol>.GetInstance(out var stack);
        stack.Push(assemblySymbol.GlobalNamespace);
 
        while (stack.TryPop(out var namespaceSymbol))
        {
            var typeListItems = GetTypeListItems(namespaceSymbol, compilation, projectId, searchString, fullyQualified: true);
 
            foreach (var typeListItem in typeListItems)
            {
                if (searchString == null)
                {
                    builder.Add(typeListItem);
                }
                else if (typeListItem.SearchText.IndexOf(searchString, StringComparison.OrdinalIgnoreCase) >= 0)
                {
                    builder.Add(typeListItem);
                }
            }
 
            // Visit any nested namespaces
            foreach (var namespaceMember in namespaceSymbol.GetNamespaceMembers())
            {
                stack.Push(namespaceMember);
            }
        }
    }
 
    public void CollectMemberListItems(IAssemblySymbol assemblySymbol, Compilation compilation, ProjectId projectId, ImmutableArray<ObjectListItem>.Builder builder, string searchString)
    {
        Debug.Assert(assemblySymbol != null);
        Debug.Assert(compilation != null);
 
        using var _ = ArrayBuilder<INamespaceSymbol>.GetInstance(out var namespaceStack);
        namespaceStack.Push(assemblySymbol.GlobalNamespace);
 
        while (namespaceStack.TryPop(out var namespaceSymbol))
        {
            var types = GetAccessibleTypes(namespaceSymbol, compilation);
 
            foreach (var type in types)
            {
                var memberListItems = GetMemberListItems(type, compilation, projectId, fullyQualified: true);
                foreach (var memberListItem in memberListItems)
                {
                    if (searchString == null)
                    {
                        builder.Add(memberListItem);
                    }
                    else if (memberListItem.SearchText.IndexOf(searchString, StringComparison.OrdinalIgnoreCase) >= 0)
                    {
                        builder.Add(memberListItem);
                    }
                }
            }
 
            // Visit any nested namespaces
            foreach (var namespaceMember in namespaceSymbol.GetNamespaceMembers())
            {
                namespaceStack.Push(namespaceMember);
            }
        }
    }
}