File: FindUsages\DefinitionItem.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.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.Navigation;
using Microsoft.CodeAnalysis.Tags;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.FindUsages;
 
/// <summary>
/// Information about a symbol's definition that can be displayed in an editor
/// and used for navigation.
/// 
/// Standard implementations can be obtained through the various <see cref="DefinitionItem"/>.Create overloads.
/// 
/// Subclassing is also supported for scenarios that fall outside the bounds of
/// these common cases.
/// </summary>
internal abstract partial class DefinitionItem
{
    /// <summary>
    /// The definition item corresponding to the initial symbol the user was trying to find. This item should get
    /// prominent placement in the final UI for the user.
    /// </summary>
    internal const string Primary = nameof(Primary);
 
    // Existing behavior is to do up to two lookups for 3rd party navigation for FAR.  One
    // for the symbol itself and one for a 'fallback' symbol.  For example, if we're FARing
    // on a constructor, then the fallback symbol will be the actual type that the constructor
    // is contained within.
    internal const string RQNameKey1 = nameof(RQNameKey1);
    internal const string RQNameKey2 = nameof(RQNameKey2);
 
    /// <summary>
    /// For metadata symbols we encode information in the <see cref="Properties"/> so we can 
    /// retrieve the symbol later on when navigating.  This is needed so that we can go to
    /// metadata-as-source for metadata symbols.  We need to store the <see cref="SymbolKey"/>
    /// for the symbol and the project ID that originated the symbol.  With these we can correctly recover the symbol.
    /// </summary>
    internal const string MetadataSymbolKey = nameof(MetadataSymbolKey);
    internal const string MetadataSymbolOriginatingProjectIdGuid = nameof(MetadataSymbolOriginatingProjectIdGuid);
    internal const string MetadataSymbolOriginatingProjectIdDebugName = nameof(MetadataSymbolOriginatingProjectIdDebugName);
 
    /// <summary>
    /// If this item is something that cannot be navigated to.  We store this in our
    /// <see cref="Properties"/> to act as an explicit marker that navigation is not possible.
    /// </summary>
    private const string NonNavigable = nameof(NonNavigable);
 
    /// <summary>
    /// Descriptive tags from <see cref="WellKnownTags"/>. These tags may influence how the 
    /// item is displayed.
    /// </summary>
    public ImmutableArray<string> Tags { get; }
 
    /// <summary>
    /// Additional properties that can be attached to the definition for clients that want to
    /// keep track of additional data.
    /// </summary>
    public ImmutableDictionary<string, string> Properties { get; }
 
    /// <summary>
    /// Additional displayable properties that can be attached to the definition for clients that want to display
    /// additional data.
    /// </summary>
    public ImmutableArray<(string key, string value)> DisplayableProperties { get; }
 
    /// <summary>
    /// The DisplayParts just for the name of this definition.  Generally used only for 
    /// error messages.
    /// </summary>
    public ImmutableArray<TaggedText> NameDisplayParts { get; }
 
    /// <summary>
    /// The full display parts for this definition.  Displayed in a classified 
    /// manner when possible.
    /// </summary>
    public ImmutableArray<TaggedText> DisplayParts { get; }
 
    /// <summary>
    /// Additional locations to present in the UI.  A definition may have multiple locations 
    /// for cases like partial types/members.
    /// </summary>
    public ImmutableArray<DocumentSpan> SourceSpans { get; }
 
    /// <summary>
    /// Precomputed classified spans for the corresponding <see cref="SourceSpans"/>.
    /// </summary>
    public ImmutableArray<ClassifiedSpansAndHighlightSpan?> ClassifiedSpans { get; }
 
    /// <summary>
    /// Identities of assemblies that contain the metadata for this definition.
    /// </summary>
    public ImmutableArray<AssemblyLocation> MetadataLocations { get; }
 
    /// <summary>
    /// Whether or not this definition should be presented if we never found any references to
    /// it.  For example, when searching for a property, the FindReferences engine will cascade
    /// to the accessors in case any code specifically called those accessors (can happen in 
    /// cross-language cases).  However, in the normal case where there were no calls specifically
    /// to the accessor, we would not want to display them in the UI.  
    /// 
    /// For most definitions we will want to display them, even if no references were found.  
    /// This property allows for this customization in behavior.
    /// </summary>
    public bool DisplayIfNoReferences { get; }
 
    internal abstract bool IsExternal { get; }
 
    protected DefinitionItem(
        ImmutableArray<string> tags,
        ImmutableArray<TaggedText> displayParts,
        ImmutableArray<TaggedText> nameDisplayParts,
        ImmutableArray<DocumentSpan> sourceSpans,
        ImmutableArray<ClassifiedSpansAndHighlightSpan?> classifiedSpans,
        ImmutableArray<AssemblyLocation> metadataLocations,
        ImmutableDictionary<string, string>? properties,
        ImmutableArray<(string key, string value)> displayableProperties,
        bool displayIfNoReferences)
    {
        Tags = tags;
        DisplayParts = displayParts;
        NameDisplayParts = nameDisplayParts.IsDefaultOrEmpty ? displayParts : nameDisplayParts;
        SourceSpans = sourceSpans.NullToEmpty();
        ClassifiedSpans = classifiedSpans.NullToEmpty();
        MetadataLocations = metadataLocations.NullToEmpty();
        Properties = properties ?? ImmutableDictionary<string, string>.Empty;
        DisplayableProperties = displayableProperties.NullToEmpty();
        DisplayIfNoReferences = displayIfNoReferences;
 
        Contract.ThrowIfFalse(classifiedSpans.IsEmpty || sourceSpans.Length == classifiedSpans.Length);
 
        if (Properties.ContainsKey(MetadataSymbolKey))
        {
            Contract.ThrowIfFalse(Properties.ContainsKey(MetadataSymbolOriginatingProjectIdGuid));
            Contract.ThrowIfFalse(Properties.ContainsKey(MetadataSymbolOriginatingProjectIdDebugName));
        }
    }
 
    [Obsolete("Use GetNavigableLocationAsync instead")]
    public Task<bool> TryNavigateToAsync(Workspace workspace, bool showInPreviewTab, bool activateTab, CancellationToken cancellationToken)
        => TryNavigateToAsync(workspace, new NavigationOptions(showInPreviewTab, activateTab), cancellationToken);
 
    [Obsolete("Use GetNavigableLocationAsync instead")]
    public async Task<bool> TryNavigateToAsync(Workspace workspace, NavigationOptions options, CancellationToken cancellationToken)
    {
        var location = await GetNavigableLocationAsync(workspace, cancellationToken).ConfigureAwait(false);
        return location != null &&
            await location.NavigateToAsync(options, cancellationToken).ConfigureAwait(false);
    }
 
    public abstract Task<INavigableLocation?> GetNavigableLocationAsync(Workspace workspace, CancellationToken cancellationToken);
 
    // Kept around for binary compat with TypeScript.
    [Obsolete("TypeScript: Use external access APIs")]
    public static DefinitionItem Create(
        ImmutableArray<string> tags,
        ImmutableArray<TaggedText> displayParts,
        DocumentSpan sourceSpan,
        ImmutableArray<TaggedText> nameDisplayParts = default,
        bool displayIfNoReferences = true)
    {
        return Create(
            tags, displayParts,
            sourceSpan,
            classifiedSpans: null,
            nameDisplayParts, displayIfNoReferences);
    }
 
    [Obsolete("TypeScript: Use external access APIs")]
    public static DefinitionItem Create(
        ImmutableArray<string> tags,
        ImmutableArray<TaggedText> displayParts,
        DocumentSpan sourceSpan,
        ClassifiedSpansAndHighlightSpan? classifiedSpans,
        ImmutableArray<TaggedText> nameDisplayParts = default,
        bool displayIfNoReferences = true)
    {
        return Create(
            tags, displayParts,
            [sourceSpan],
            [classifiedSpans],
            nameDisplayParts, displayIfNoReferences);
    }
 
    // Kept around for binary compat with F#/TypeScript.
    [Obsolete("TypeScript: Use external access APIs")]
    public static DefinitionItem Create(
        ImmutableArray<string> tags,
        ImmutableArray<TaggedText> displayParts,
        ImmutableArray<DocumentSpan> sourceSpans,
        ImmutableArray<ClassifiedSpansAndHighlightSpan?> classifiedSpans,
        ImmutableArray<TaggedText> nameDisplayParts,
        bool displayIfNoReferences)
    {
        return Create(
            tags, displayParts, sourceSpans, classifiedSpans, ImmutableArray<AssemblyLocation>.Empty, nameDisplayParts,
            properties: null, displayableProperties: [], displayIfNoReferences: displayIfNoReferences);
    }
 
    [Obsolete("TypeScript: Use external access APIs")]
    public static DefinitionItem Create(
        ImmutableArray<string> tags,
        ImmutableArray<TaggedText> displayParts,
        ImmutableArray<DocumentSpan> sourceSpans,
        ImmutableArray<ClassifiedSpansAndHighlightSpan?> classifiedSpans,
        ImmutableArray<TaggedText> nameDisplayParts = default,
        ImmutableDictionary<string, string>? properties = null,
        bool displayIfNoReferences = true)
    {
        return Create(
            tags, displayParts, sourceSpans, classifiedSpans,
            ImmutableArray<AssemblyLocation>.Empty, nameDisplayParts, displayIfNoReferences: displayIfNoReferences);
    }
 
    public static DefinitionItem Create(
        ImmutableArray<string> tags,
        ImmutableArray<TaggedText> displayParts,
        ImmutableArray<DocumentSpan> sourceSpans,
        ImmutableArray<ClassifiedSpansAndHighlightSpan?> classifiedSpans,
        ImmutableArray<AssemblyLocation> metadataLocations,
        ImmutableArray<TaggedText> nameDisplayParts = default,
        ImmutableDictionary<string, string>? properties = null,
        ImmutableArray<(string key, string value)> displayableProperties = default,
        bool displayIfNoReferences = true)
    {
        Contract.ThrowIfTrue(sourceSpans.IsDefault);
        Contract.ThrowIfTrue(metadataLocations.IsDefault);
 
        return new DefaultDefinitionItem(
            tags, displayParts, nameDisplayParts,
            sourceSpans, classifiedSpans, metadataLocations, properties, displayableProperties, displayIfNoReferences);
    }
 
    // Kept around for binary compat with F#/TypeScript.
    [Obsolete("TypeScript: Use external access APIs")]
    public static DefinitionItem CreateNonNavigableItem(
        ImmutableArray<string> tags,
        ImmutableArray<TaggedText> displayParts,
        ImmutableArray<TaggedText> originationParts,
        bool displayIfNoReferences)
    {
        return CreateNonNavigableItem(
            tags, displayParts,
            properties: null, displayIfNoReferences: displayIfNoReferences);
    }
 
    public static DefinitionItem CreateNonNavigableItem(
        ImmutableArray<string> tags,
        ImmutableArray<TaggedText> displayParts,
        ImmutableArray<TaggedText> nameDisplayParts = default,
        ImmutableArray<AssemblyLocation> metadataLocations = default,
        ImmutableDictionary<string, string>? properties = null,
        bool displayIfNoReferences = true)
    {
        properties ??= ImmutableDictionary<string, string>.Empty;
        properties = properties.Add(NonNavigable, "");
 
        return new DefaultDefinitionItem(
            tags: tags,
            displayParts: displayParts,
            nameDisplayParts: nameDisplayParts,
            sourceSpans: [],
            classifiedSpans: [],
            metadataLocations,
            properties: properties,
            displayableProperties: [],
            displayIfNoReferences: displayIfNoReferences);
    }
 
    public DetachedDefinitionItem Detach()
        => new(Tags, DisplayParts, NameDisplayParts, SourceSpans.SelectAsArray(ss => (DocumentIdSpan)ss), MetadataLocations, Properties, DisplayableProperties, DisplayIfNoReferences);
}