File: InheritanceMargin\AbstractInheritanceMarginService_Helpers.cs
Web Access
Project: src\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.Features)
// 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.Immutable;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.FindSymbols.FindReferences;
using Microsoft.CodeAnalysis.FindUsages;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Microsoft.CodeAnalysis.SymbolMapping;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.InheritanceMargin;
 
using SymbolAndLineNumberArray = ImmutableArray<(ISymbol symbol, int lineNumber)>;
 
internal abstract partial class AbstractInheritanceMarginService
{
    private static readonly SymbolDisplayFormat s_displayFormat = new(
        globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.OmittedAsContaining,
        typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypes,
        genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters,
        memberOptions:
            SymbolDisplayMemberOptions.IncludeContainingType |
            SymbolDisplayMemberOptions.IncludeExplicitInterface,
        propertyStyle: SymbolDisplayPropertyStyle.NameOnly,
        miscellaneousOptions:
            SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers |
            SymbolDisplayMiscellaneousOptions.UseSpecialTypes |
            SymbolDisplayMiscellaneousOptions.UseErrorTypeSymbolName |
            SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier);
 
    private static async ValueTask<ImmutableArray<InheritanceMarginItem>> GetSymbolInheritanceChainItemsAsync(
        Project project,
        Document? document,
        SymbolAndLineNumberArray symbolAndLineNumbers,
        bool frozenPartialSemantics,
        CancellationToken cancellationToken)
    {
        // If we're starting from a document, use it to go to a frozen partial version of it to lower the amount of
        // work we need to do running source generators or producing skeleton references.
        if (document != null && frozenPartialSemantics)
        {
            document = document.WithFrozenPartialSemantics(cancellationToken);
            project = document.Project;
        }
 
        var solution = project.Solution;
        using var _ = ArrayBuilder<InheritanceMarginItem>.GetInstance(out var builder);
        foreach (var (symbol, lineNumber) in symbolAndLineNumbers)
        {
            if (symbol is INamedTypeSymbol namedTypeSymbol)
            {
                await AddInheritanceMemberItemsForNamedTypeAsync(solution, namedTypeSymbol, lineNumber, builder, cancellationToken).ConfigureAwait(false);
            }
 
            if (symbol is IEventSymbol or IPropertySymbol or IMethodSymbol)
            {
                await AddInheritanceMemberItemsForMembersAsync(solution, symbol, lineNumber, builder, cancellationToken).ConfigureAwait(false);
            }
        }
 
        return builder.ToImmutableAndClear();
    }
 
    private async ValueTask<(Project remapped, SymbolAndLineNumberArray symbolAndLineNumbers)> GetMemberSymbolsAsync(
        Document document,
        TextSpan spanToSearch,
        CancellationToken cancellationToken)
    {
        var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
        var allDeclarationNodes = GetMembers(root.DescendantNodes(spanToSearch));
        if (!allDeclarationNodes.IsEmpty)
        {
            var sourceText = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false);
            var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
 
            var mappingService = document.Project.Solution.Services.GetRequiredService<ISymbolMappingService>();
            using var _ = ArrayBuilder<(ISymbol symbol, int lineNumber)>.GetInstance(out var builder);
 
            Project? project = null;
 
            foreach (var memberDeclarationNode in allDeclarationNodes)
            {
                // Make sure the identifier declaration's span is within the spanToSearch.
                // This is important because for example, for a class with big body, when the tagger is asking for the tag within the body of the class,
                // we don't want to return the tag of the class identifier.
                var declarationToken = GetDeclarationToken(memberDeclarationNode);
                if (!spanToSearch.Contains(declarationToken.Span))
                    continue;
 
                var member = semanticModel.GetDeclaredSymbol(memberDeclarationNode, cancellationToken);
                if (member == null || !CanHaveInheritanceTarget(member))
                    continue;
 
                // Use mapping service to find correct solution & symbol. (e.g. metadata symbol)
                var mappingResult = await mappingService.MapSymbolAsync(document, member, cancellationToken).ConfigureAwait(false);
                if (mappingResult == null)
                    continue;
 
                // All the symbols here are declared in the same document, they should belong to the same project.
                // So here it is enough to get the project once.
                project ??= mappingResult.Project;
                builder.Add((mappingResult.Symbol,
                    sourceText.Lines.GetLineFromPosition(declarationToken.SpanStart).LineNumber));
            }
 
            if (project != null)
                return (project, builder.ToImmutable());
        }
 
        return (document.Project, SymbolAndLineNumberArray.Empty);
    }
 
    private async Task<ImmutableArray<InheritanceMarginItem>> GetInheritanceMarginItemsInProcessAsync(
        Document document,
        TextSpan spanToSearch,
        bool includeGlobalImports,
        bool frozenPartialSemantics,
        CancellationToken cancellationToken)
    {
        var (remappedProject, symbolAndLineNumbers) = await GetMemberSymbolsAsync(document, spanToSearch, cancellationToken).ConfigureAwait(false);
 
        // if we didn't remap the symbol to another project (e.g. remapping from a metadata-as-source symbol back to
        // the originating project), then we're in teh same project and we should try to get global import
        // information to display.
        var remapped = remappedProject != document.Project;
 
        using var _ = ArrayBuilder<InheritanceMarginItem>.GetInstance(out var result);
 
        if (includeGlobalImports && !remapped)
            result.AddRange(await GetGlobalImportsItemsAsync(document, spanToSearch, frozenPartialSemantics, cancellationToken).ConfigureAwait(false));
 
        if (!symbolAndLineNumbers.IsEmpty)
        {
            result.AddRange(await GetSymbolInheritanceChainItemsAsync(
                remappedProject,
                document: remapped ? null : document,
                symbolAndLineNumbers,
                frozenPartialSemantics,
                cancellationToken).ConfigureAwait(false));
        }
 
        return result.ToImmutableAndClear();
    }
 
    private async Task<ImmutableArray<InheritanceMarginItem>> GetGlobalImportsItemsAsync(
        Document document,
        TextSpan spanToSearch,
        bool frozenPartialSemantics,
        CancellationToken cancellationToken)
    {
        if (frozenPartialSemantics)
            document = document.WithFrozenPartialSemantics(cancellationToken);
 
        var root = await document.GetRequiredSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
 
        var syntaxFacts = document.GetRequiredLanguageService<ISyntaxFactsService>();
        var imports = syntaxFacts.GetImportsOfCompilationUnit(root);
 
        // Place the imports item on the start of the first import in the file.  Or, if there is no import, then on
        // the first line.
        var spanStart = imports.Count > 0 ? imports[0].SpanStart : 0;
 
        // if that location doesn't intersect with the lines of interest, immediately bail out.
        if (!spanToSearch.IntersectsWith(spanStart))
            return [];
 
        var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
        var scopes = semanticModel.GetImportScopes(root.FullSpan.End, cancellationToken);
 
        // If we have global imports they would only be in the last scope in the scopes array.  All other scopes
        // correspond to inner scopes for either the compilation unit or namespace.
        var lastScope = scopes.LastOrDefault();
        if (lastScope == null)
            return [];
 
        // Pull in any project level imports, or imports from other files (e.g. global usings).
        var syntaxTree = semanticModel.SyntaxTree;
        var nonLocalImports = lastScope.Imports
            .WhereAsArray(i => i.DeclaringSyntaxReference?.SyntaxTree != syntaxTree)
            .Sort((i1, i2) =>
            {
                return (i1.DeclaringSyntaxReference, i2.DeclaringSyntaxReference) switch
                {
                    // Both are project level imports.  Sort by name of symbol imported.
                    (null, null) => i1.NamespaceOrType.ToDisplayString().CompareTo(i2.NamespaceOrType.ToDisplayString()),
                    // project level imports come first.
                    (null, not null) => -1,
                    (not null, null) => 1,
                    // both are from different files.  Sort by file path first, then location in file if same file path.
                    ({ SyntaxTree: var syntaxTree1, Span: var span1 }, { SyntaxTree: var syntaxTree2, Span: var span2 })
                        => syntaxTree1.FilePath != syntaxTree2.FilePath
                            ? StringComparer.OrdinalIgnoreCase.Compare(syntaxTree1.FilePath, syntaxTree2.FilePath)
                            : span1.CompareTo(span2),
                };
            });
 
        if (nonLocalImports.Length == 0)
            return [];
 
        var text = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false);
        var lineNumber = text.Lines.GetLineFromPosition(spanStart).LineNumber;
 
        var projectState = document.Project.State;
        var projectName = projectState.NameAndFlavor.name ?? projectState.Name;
        var languageGlyph = document.Project.Language switch
        {
            LanguageNames.CSharp => Glyph.CSharpFile,
            LanguageNames.VisualBasic => Glyph.BasicFile,
            _ => throw ExceptionUtilities.UnexpectedValue(document.Project.Language),
        };
 
        using var _1 = ArrayBuilder<InheritanceMarginItem>.GetInstance(out var items);
 
        foreach (var group in nonLocalImports.GroupBy(i => i.DeclaringSyntaxReference?.SyntaxTree))
        {
            var groupSyntaxTree = group.Key;
            if (groupSyntaxTree is null)
            {
                using var _2 = ArrayBuilder<InheritanceTargetItem>.GetInstance(out var targetItems);
 
                foreach (var import in group)
                {
                    var item = DefinitionItem.CreateNonNavigableItem(tags: [], displayParts: []);
                    targetItems.Add(new InheritanceTargetItem(
                        InheritanceRelationship.InheritedImport, item.Detach(), Glyph.None, languageGlyph,
                        import.NamespaceOrType.ToDisplayString(), projectName));
                }
 
                items.Add(new InheritanceMarginItem(
                    lineNumber, this.GlobalImportsTitle, [new TaggedText(TextTags.Text, this.GlobalImportsTitle)],
                    Glyph.Namespace, targetItems.ToImmutable()));
            }
            else
            {
                var destinationDocument = document.Project.Solution.GetDocument(groupSyntaxTree);
                if (destinationDocument is null)
                    continue;
 
                using var _ = ArrayBuilder<InheritanceTargetItem>.GetInstance(out var targetItems);
 
                foreach (var import in group)
                {
                    var item = DefinitionItem.Create(
                        tags: [],
                        displayParts: [],
                        sourceSpans: [new DocumentSpan(destinationDocument, import.DeclaringSyntaxReference!.Span)],
                        classifiedSpans: [],
                        metadataLocations: [],
                        nameDisplayParts: default,
                        displayIfNoReferences: true);
 
                    targetItems.Add(new InheritanceTargetItem(
                        InheritanceRelationship.InheritedImport, item.Detach(), Glyph.None, languageGlyph,
                        import.NamespaceOrType.ToDisplayString(), projectName));
                }
 
                var filePath = groupSyntaxTree.FilePath;
                var fileName = filePath == null ? null : IOUtilities.PerformIO(() => Path.GetFileName(filePath)) ?? filePath;
                var taggedText = new TaggedText(TextTags.Text, string.Format(FeaturesResources.Directives_from_0, fileName));
 
                items.Add(new InheritanceMarginItem(
                    lineNumber, this.GlobalImportsTitle, [taggedText], Glyph.Namespace, targetItems.ToImmutable()));
            }
        }
 
        return items.ToImmutableAndClear();
    }
 
    private static async ValueTask AddInheritanceMemberItemsForNamedTypeAsync(
        Solution solution,
        INamedTypeSymbol memberSymbol,
        int lineNumber,
        ArrayBuilder<InheritanceMarginItem> builder,
        CancellationToken cancellationToken)
    {
        // Get all base types.
        var allBaseSymbols = BaseTypeFinder.FindBaseTypesAndInterfaces(memberSymbol);
 
        // Filter out
        // 1. System.Object. (otherwise margin would be shown for all classes)
        // 2. System.ValueType. (otherwise margin would be shown for all structs)
        // 3. System.Enum. (otherwise margin would be shown for all enum)
        // 4. Error type.
        // For example, if user has code like this,
        // class Bar : ISomethingIsNotDone { }
        // The interface has not been declared yet, so don't show this error type to user.
        var baseSymbols = allBaseSymbols
            .WhereAsArray(symbol => !symbol.IsErrorType() && symbol.SpecialType is not (SpecialType.System_Object or SpecialType.System_ValueType or SpecialType.System_Enum));
 
        // Get all derived types
        var allDerivedSymbols = await GetDerivedTypesAndImplementationsAsync(
            solution,
            memberSymbol,
            cancellationToken).ConfigureAwait(false);
 
        // Ensure the user won't be able to see symbol outside the solution for derived symbols.
        // For example, if user is viewing 'IEnumerable interface' from metadata, we don't want to tell
        // the user all the derived types under System.Collections
        var derivedSymbols = allDerivedSymbols.WhereAsArray(symbol => symbol.Locations.Any(static l => l.IsInSource));
 
        if (baseSymbols.Any() || derivedSymbols.Any())
        {
            if (memberSymbol.TypeKind == TypeKind.Interface)
            {
                var item = CreateInheritanceMemberItemForInterface(
                    solution,
                    memberSymbol,
                    lineNumber,
                    baseSymbols: baseSymbols.CastArray<ISymbol>(),
                    derivedTypesSymbols: derivedSymbols.CastArray<ISymbol>(),
                    cancellationToken);
                builder.AddIfNotNull(item);
            }
            else
            {
                Debug.Assert(memberSymbol.TypeKind is TypeKind.Class or TypeKind.Struct);
                var item = CreateInheritanceItemForClassAndStructure(
                    solution,
                    memberSymbol,
                    lineNumber,
                    baseSymbols: baseSymbols.CastArray<ISymbol>(),
                    derivedTypesSymbols: derivedSymbols.CastArray<ISymbol>(),
                    cancellationToken);
                builder.AddIfNotNull(item);
            }
        }
    }
 
    private static async ValueTask AddInheritanceMemberItemsForMembersAsync(
        Solution solution,
        ISymbol memberSymbol,
        int lineNumber,
        ArrayBuilder<InheritanceMarginItem> builder,
        CancellationToken cancellationToken)
    {
        if (memberSymbol.ContainingSymbol.IsInterfaceType())
        {
            // Go down the inheritance chain to find all the implementing targets.
            var allImplementingSymbols = await GetImplementingSymbolsForTypeMemberAsync(solution, memberSymbol, cancellationToken).ConfigureAwait(false);
 
            // For all implementing symbols, make sure it is in source.
            // For example, if the user is viewing IEnumerable from metadata,
            // then don't show the derived overriden & implemented types in System.Collections
            var implementingSymbols = allImplementingSymbols.WhereAsArray(symbol => symbol.Locations.Any(static l => l.IsInSource));
 
            if (implementingSymbols.Any())
            {
                var item = CreateInheritanceMemberItemForInterfaceMember(
                    solution,
                    memberSymbol,
                    lineNumber,
                    implementingMembers: implementingSymbols,
                    cancellationToken);
                builder.AddIfNotNull(item);
            }
        }
        else
        {
            // Go down the inheritance chain to find all the overriding targets.
            var allOverridingSymbols = await SymbolFinder.FindOverridesArrayAsync(memberSymbol, solution, cancellationToken: cancellationToken).ConfigureAwait(false);
 
            // Go up the inheritance chain to find all overridden targets
            var overriddenSymbols = GetOverriddenSymbols(memberSymbol, allowLooseMatch: true);
 
            // Go up the inheritance chain to find all the implemented targets.
            var implementedSymbols = GetImplementedSymbolsForTypeMember(memberSymbol, overriddenSymbols);
 
            // For all overriding symbols, make sure it is in source.
            // For example, if the user is viewing System.Threading.SynchronizationContext from metadata,
            // then don't show the derived overriden & implemented method in the default implementation for System.Threading.SynchronizationContext in metadata
            var overridingSymbols = allOverridingSymbols.WhereAsArray(symbol => symbol.Locations.Any(static l => l.IsInSource));
 
            if (overridingSymbols.Any() || overriddenSymbols.Any() || implementedSymbols.Any())
            {
                var item = CreateInheritanceMemberItemForClassOrStructMember(solution,
                    memberSymbol,
                    lineNumber,
                    implementedMembers: implementedSymbols,
                    overridingMembers: overridingSymbols,
                    overriddenMembers: overriddenSymbols,
                    cancellationToken);
                builder.AddIfNotNull(item);
            }
        }
    }
 
    private static InheritanceMarginItem? CreateInheritanceMemberItemForInterface(
        Solution solution,
        INamedTypeSymbol interfaceSymbol,
        int lineNumber,
        ImmutableArray<ISymbol> baseSymbols,
        ImmutableArray<ISymbol> derivedTypesSymbols,
        CancellationToken cancellationToken)
    {
        var baseSymbolItems = baseSymbols
            .SelectAsArray(symbol => symbol.OriginalDefinition)
            .Distinct()
            .SelectAsArray(static (symbol, tuple) => CreateInheritanceItem(
                tuple.solution,
                symbol,
                InheritanceRelationship.InheritedInterface,
                tuple.cancellationToken), (solution, cancellationToken));
 
        var derivedTypeItems = derivedTypesSymbols
            .SelectAsArray(symbol => symbol.OriginalDefinition)
            .Distinct()
            .SelectAsArray(static (symbol, tuple) => CreateInheritanceItem(
                tuple.solution,
                symbol,
                InheritanceRelationship.ImplementingType,
                tuple.cancellationToken), (solution, cancellationToken));
 
        var nonNullBaseSymbolItems = GetNonNullTargetItems(baseSymbolItems);
        var nonNullDerivedTypeItems = GetNonNullTargetItems(derivedTypeItems);
 
        return InheritanceMarginItem.CreateOrdered(
            lineNumber,
            topLevelDisplayText: null,
            FindUsagesHelpers.GetDisplayParts(interfaceSymbol),
            interfaceSymbol.GetGlyph(),
            nonNullBaseSymbolItems.Concat(nonNullDerivedTypeItems));
    }
 
    private static InheritanceMarginItem? CreateInheritanceMemberItemForInterfaceMember(
        Solution solution,
        ISymbol memberSymbol,
        int lineNumber,
        ImmutableArray<ISymbol> implementingMembers,
        CancellationToken cancellationToken)
    {
        var implementedMemberItems = implementingMembers
            .SelectAsArray(symbol => symbol.OriginalDefinition)
            .Distinct()
            .SelectAsArray(static (symbol, tuple) => CreateInheritanceItem(
                tuple.solution,
                symbol,
                InheritanceRelationship.ImplementingMember,
                tuple.cancellationToken), (solution, cancellationToken));
 
        var nonNullImplementedMemberItems = GetNonNullTargetItems(implementedMemberItems);
        return InheritanceMarginItem.CreateOrdered(
            lineNumber,
            topLevelDisplayText: null,
            FindUsagesHelpers.GetDisplayParts(memberSymbol),
            memberSymbol.GetGlyph(),
            nonNullImplementedMemberItems);
    }
 
    private static InheritanceMarginItem? CreateInheritanceItemForClassAndStructure(
        Solution solution,
        INamedTypeSymbol memberSymbol,
        int lineNumber,
        ImmutableArray<ISymbol> baseSymbols,
        ImmutableArray<ISymbol> derivedTypesSymbols,
        CancellationToken cancellationToken)
    {
        // If the target is an interface, it would be shown as 'Inherited interface',
        // and if it is an class/struct, it whould be shown as 'Base Type'
        var baseSymbolItems = baseSymbols
            .SelectAsArray(symbol => symbol.OriginalDefinition)
            .Distinct()
            .SelectAsArray(static (symbol, tuple) => CreateInheritanceItem(
                tuple.solution,
                symbol,
                symbol.IsInterfaceType() ? InheritanceRelationship.ImplementedInterface : InheritanceRelationship.BaseType,
                tuple.cancellationToken), (solution, cancellationToken));
 
        var derivedTypeItems = derivedTypesSymbols
            .SelectAsArray(symbol => symbol.OriginalDefinition)
            .Distinct()
            .SelectAsArray(static (symbol, tuple) => CreateInheritanceItem(
                tuple.solution,
                symbol,
                InheritanceRelationship.DerivedType,
                tuple.cancellationToken), (solution, cancellationToken));
 
        var nonNullBaseSymbolItems = GetNonNullTargetItems(baseSymbolItems);
        var nonNullDerivedTypeItems = GetNonNullTargetItems(derivedTypeItems);
 
        return InheritanceMarginItem.CreateOrdered(
            lineNumber,
            topLevelDisplayText: null,
            FindUsagesHelpers.GetDisplayParts(memberSymbol),
            memberSymbol.GetGlyph(),
            nonNullBaseSymbolItems.Concat(nonNullDerivedTypeItems));
    }
 
    private static InheritanceMarginItem? CreateInheritanceMemberItemForClassOrStructMember(
        Solution solution,
        ISymbol memberSymbol,
        int lineNumber,
        ImmutableArray<ISymbol> implementedMembers,
        ImmutableArray<ISymbol> overridingMembers,
        ImmutableArray<ISymbol> overriddenMembers,
        CancellationToken cancellationToken)
    {
        var implementedMemberItems = implementedMembers
            .SelectAsArray(symbol => symbol.OriginalDefinition)
            .Distinct()
            .SelectAsArray(static (symbol, tuple) => CreateInheritanceItem(
                tuple.solution,
                symbol,
                InheritanceRelationship.ImplementedMember,
                tuple.cancellationToken), (solution, cancellationToken));
 
        var overriddenMemberItems = overriddenMembers
            .SelectAsArray(symbol => symbol.OriginalDefinition)
            .Distinct()
            .SelectAsArray(static (symbol, tuple) => CreateInheritanceItem(
                tuple.solution,
                symbol,
                InheritanceRelationship.OverriddenMember,
                tuple.cancellationToken), (solution, cancellationToken));
 
        var overridingMemberItems = overridingMembers
            .SelectAsArray(symbol => symbol.OriginalDefinition)
            .Distinct()
            .SelectAsArray(static (symbol, tuple) => CreateInheritanceItem(
                tuple.solution,
                symbol,
                InheritanceRelationship.OverridingMember,
                tuple.cancellationToken), (solution, cancellationToken));
 
        var nonNullImplementedMemberItems = GetNonNullTargetItems(implementedMemberItems);
        var nonNullOverriddenMemberItems = GetNonNullTargetItems(overriddenMemberItems);
        var nonNullOverridingMemberItems = GetNonNullTargetItems(overridingMemberItems);
 
        return InheritanceMarginItem.CreateOrdered(
            lineNumber,
            topLevelDisplayText: null,
            FindUsagesHelpers.GetDisplayParts(memberSymbol),
            memberSymbol.GetGlyph(),
            nonNullImplementedMemberItems.Concat(nonNullOverriddenMemberItems, nonNullOverridingMemberItems));
    }
 
    private static InheritanceTargetItem? CreateInheritanceItem(
        Solution solution,
        ISymbol targetSymbol,
        InheritanceRelationship inheritanceRelationship,
        CancellationToken cancellationToken)
    {
        var symbolInSource = SymbolFinder.FindSourceDefinition(targetSymbol, solution, cancellationToken);
        targetSymbol = symbolInSource ?? targetSymbol;
 
        // Right now the targets are not shown in a classified way.
        var definition = ToSlimDefinitionItem(targetSymbol, solution);
        if (definition == null)
            return null;
 
        var displayName = targetSymbol.ToDisplayString(s_displayFormat);
 
        var projectState = definition.SourceSpans.Length > 0
            ? definition.SourceSpans[0].Document.Project.State
            : null;
 
        var languageGlyph = targetSymbol.Language switch
        {
            LanguageNames.CSharp => Glyph.CSharpFile,
            LanguageNames.VisualBasic => Glyph.BasicFile,
            _ => throw ExceptionUtilities.UnexpectedValue(targetSymbol.Language),
        };
 
        return new InheritanceTargetItem(
            inheritanceRelationship,
            definition.Detach(),
            targetSymbol.GetGlyph(),
            languageGlyph,
            displayName,
            projectState?.NameAndFlavor.name ?? projectState?.Name);
    }
 
    private static ImmutableArray<ISymbol> GetImplementedSymbolsForTypeMember(
        ISymbol memberSymbol,
        ImmutableArray<ISymbol> overriddenSymbols)
    {
        if (memberSymbol is IMethodSymbol or IEventSymbol or IPropertySymbol)
        {
            using var _ = ArrayBuilder<ISymbol>.GetInstance(out var builder);
 
            // 1. Get the direct implementing symbols in interfaces.
            var directImplementingSymbols = memberSymbol.ExplicitOrImplicitInterfaceImplementations();
            builder.AddRange(directImplementingSymbols);
 
            // 2. Also add the direct implementing symbols for the overridden symbols.
            // For example:
            // interface IBar { void Foo(); }
            // class Bar : IBar { public override void Foo() { } }
            // class Bar2 : Bar { public override void Foo() { } }
            // For 'Bar2.Foo()',  we need to find 'IBar.Foo()'
            foreach (var symbol in overriddenSymbols)
            {
                builder.AddRange(symbol.ExplicitOrImplicitInterfaceImplementations());
            }
 
            return builder.ToImmutableArray();
        }
 
        return [];
    }
 
    /// <summary>
    /// For the <param name="memberSymbol"/>, get all the implementing symbols.
    /// </summary>
    private static async Task<ImmutableArray<ISymbol>> GetImplementingSymbolsForTypeMemberAsync(
        Solution solution,
        ISymbol memberSymbol,
        CancellationToken cancellationToken)
    {
        if (memberSymbol is IMethodSymbol or IEventSymbol or IPropertySymbol
            && memberSymbol.ContainingSymbol.IsInterfaceType())
        {
            using var _ = ArrayBuilder<ISymbol>.GetInstance(out var builder);
            // 1. Find all direct implementations for this member
            var implementationSymbols = await SymbolFinder.FindMemberImplementationsArrayAsync(
                memberSymbol,
                solution,
                cancellationToken: cancellationToken).ConfigureAwait(false);
            builder.AddRange(implementationSymbols);
 
            // 2. Continue searching the overriden symbols. For example:
            // interface IBar { void Foo(); }
            // class Bar : IBar { public virtual void Foo() { } }
            // class Bar2 : IBar { public override void Foo() { } }
            // For 'IBar.Foo()', we need to find 'Bar2.Foo()'
            foreach (var implementationSymbol in implementationSymbols)
            {
                builder.AddRange(await SymbolFinder.FindOverridesArrayAsync(implementationSymbol, solution, cancellationToken: cancellationToken).ConfigureAwait(false));
            }
 
            return builder.ToImmutableArray();
        }
 
        return [];
    }
 
    /// <summary>
    /// Get overridden members the <param name="memberSymbol"/>.
    /// </summary>
    private static ImmutableArray<ISymbol> GetOverriddenSymbols(ISymbol memberSymbol, bool allowLooseMatch)
    {
        if (memberSymbol is INamedTypeSymbol)
            return [];
 
        using var _ = ArrayBuilder<ISymbol>.GetInstance(out var builder);
        for (var overriddenMember = memberSymbol.GetOverriddenMember(allowLooseMatch);
             overriddenMember != null;
             overriddenMember = overriddenMember.GetOverriddenMember(allowLooseMatch))
        {
            builder.Add(overriddenMember.OriginalDefinition);
        }
 
        return builder.ToImmutableAndClear();
    }
 
    /// <summary>
    /// Get the derived interfaces and derived classes for <param name="typeSymbol"/>.
    /// </summary>
    private static async Task<ImmutableArray<INamedTypeSymbol>> GetDerivedTypesAndImplementationsAsync(
        Solution solution,
        INamedTypeSymbol typeSymbol,
        CancellationToken cancellationToken)
    {
        if (typeSymbol.IsInterfaceType())
        {
            var allDerivedInterfaces = await SymbolFinder.FindDerivedInterfacesArrayAsync(
                typeSymbol,
                solution,
                transitive: true,
                cancellationToken: cancellationToken).ConfigureAwait(false);
            var allImplementations = await SymbolFinder.FindImplementationsArrayAsync(
                typeSymbol,
                solution,
                transitive: true,
                cancellationToken: cancellationToken).ConfigureAwait(false);
            return allDerivedInterfaces.Concat(allImplementations);
        }
        else
        {
            return await SymbolFinder.FindDerivedClassesArrayAsync(
                typeSymbol,
                solution,
                transitive: true,
                cancellationToken: cancellationToken).ConfigureAwait(false);
        }
    }
 
    /// <summary>
    /// Create the DefinitionItem based on the numbers of locations for <paramref name="symbol"/>.
    /// If there is only one location, create the DefinitionItem contains only the documentSpan or symbolKey to save memory.
    /// Because in such case, when clicking InheritanceMarginGlpph, it will directly navigate to the symbol.
    /// Otherwise, create the full non-classified DefinitionItem. Because in such case we want to display all the locations to the user
    /// by reusing the FAR window.
    /// </summary>
    private static DefinitionItem? ToSlimDefinitionItem(ISymbol symbol, Solution solution)
    {
        var locations = symbol.Locations;
        if (locations.Length > 1)
        {
            return symbol.ToNonClassifiedDefinitionItem(
                solution,
                FindReferencesSearchOptions.Default with { UnidirectionalHierarchyCascade = true },
                includeHiddenLocations: false);
        }
 
        if (locations is [var location])
        {
            if (location.IsInMetadata)
            {
                Contract.ThrowIfNull(symbol.ContainingAssembly);
 
                var metadataLocation = DefinitionItemFactory.GetMetadataLocation(symbol.ContainingAssembly, solution, out var originatingProjectId);
 
                return DefinitionItem.Create(
                    tags: [],
                    displayParts: [],
                    sourceSpans: [],
                    classifiedSpans: [],
                    metadataLocations: [metadataLocation],
                    properties: ImmutableDictionary<string, string>.Empty.WithMetadataSymbolProperties(symbol, originatingProjectId));
            }
 
            if (location.IsInSource && location.IsVisibleSourceLocation() && solution.GetDocument(location.SourceTree) is { } document)
            {
                return DefinitionItem.Create(
                    tags: [],
                    displayParts: [],
                    sourceSpans: [new DocumentSpan(document, location.SourceSpan)],
                    classifiedSpans: [],
                    metadataLocations: []);
            }
        }
 
        return null;
    }
 
    private static ImmutableArray<InheritanceTargetItem> GetNonNullTargetItems(ImmutableArray<InheritanceTargetItem?> inheritanceTargetItems)
    {
        using var _ = ArrayBuilder<InheritanceTargetItem>.GetInstance(out var builder);
        foreach (var item in inheritanceTargetItems)
        {
            if (item.HasValue)
            {
                builder.Add(item.Value);
            }
        }
 
        return builder.ToImmutableAndClear();
    }
}