|
// 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.ComponentModel;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.FindSymbols.Finders;
using Microsoft.CodeAnalysis.MetadataAsSource;
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 implmentations 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>
private const string MetadataSymbolKey = nameof(MetadataSymbolKey);
private const string MetadataSymbolOriginatingProjectIdGuid = nameof(MetadataSymbolOriginatingProjectIdGuid);
private 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 diplayable properties that can be attached to the definition for clients that want to
/// display additional data.
/// </summary>
public ImmutableDictionary<string, string> 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>
/// Where the location originally came from (for example, the containing assembly or
/// project name). May be used in the presentation of a definition.
/// </summary>
public ImmutableArray<TaggedText> OriginationParts { 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>
/// 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; }
// F# uses this
protected DefinitionItem(
ImmutableArray<string> tags,
ImmutableArray<TaggedText> displayParts,
ImmutableArray<TaggedText> nameDisplayParts,
ImmutableArray<TaggedText> originationParts,
ImmutableArray<DocumentSpan> sourceSpans,
ImmutableDictionary<string, string>? properties,
bool displayIfNoReferences)
: this(
tags,
displayParts,
nameDisplayParts,
originationParts,
sourceSpans,
properties,
ImmutableDictionary<string, string>.Empty,
displayIfNoReferences)
{
}
protected DefinitionItem(
ImmutableArray<string> tags,
ImmutableArray<TaggedText> displayParts,
ImmutableArray<TaggedText> nameDisplayParts,
ImmutableArray<TaggedText> originationParts,
ImmutableArray<DocumentSpan> sourceSpans,
ImmutableDictionary<string, string>? properties,
ImmutableDictionary<string, string>? displayableProperties,
bool displayIfNoReferences)
{
Tags = tags;
DisplayParts = displayParts;
NameDisplayParts = nameDisplayParts.IsDefaultOrEmpty ? displayParts : nameDisplayParts;
OriginationParts = originationParts.NullToEmpty();
SourceSpans = sourceSpans.NullToEmpty();
Properties = properties ?? ImmutableDictionary<string, string>.Empty;
DisplayableProperties = displayableProperties ?? ImmutableDictionary<string, string>.Empty;
DisplayIfNoReferences = displayIfNoReferences;
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);
public static DefinitionItem Create(
ImmutableArray<string> tags,
ImmutableArray<TaggedText> displayParts,
DocumentSpan sourceSpan,
ImmutableArray<TaggedText> nameDisplayParts = default,
bool displayIfNoReferences = true)
{
return Create(
tags, displayParts, ImmutableArray.Create(sourceSpan),
nameDisplayParts, displayIfNoReferences);
}
// Kept around for binary compat with F#/TypeScript.
public static DefinitionItem Create(
ImmutableArray<string> tags,
ImmutableArray<TaggedText> displayParts,
ImmutableArray<DocumentSpan> sourceSpans,
ImmutableArray<TaggedText> nameDisplayParts,
bool displayIfNoReferences)
{
return Create(
tags, displayParts, sourceSpans, nameDisplayParts,
properties: null, displayableProperties: ImmutableDictionary<string, string>.Empty, displayIfNoReferences: displayIfNoReferences);
}
public static DefinitionItem Create(
ImmutableArray<string> tags,
ImmutableArray<TaggedText> displayParts,
ImmutableArray<DocumentSpan> sourceSpans,
ImmutableArray<TaggedText> nameDisplayParts = default,
ImmutableDictionary<string, string>? properties = null,
bool displayIfNoReferences = true)
{
return Create(tags, displayParts, sourceSpans, nameDisplayParts, properties, ImmutableDictionary<string, string>.Empty, displayIfNoReferences);
}
public static DefinitionItem Create(
ImmutableArray<string> tags,
ImmutableArray<TaggedText> displayParts,
ImmutableArray<DocumentSpan> sourceSpans,
ImmutableArray<TaggedText> nameDisplayParts = default,
ImmutableDictionary<string, string>? properties = null,
ImmutableDictionary<string, string>? displayableProperties = null,
bool displayIfNoReferences = true)
{
if (sourceSpans.Length == 0)
{
throw new ArgumentException($"{nameof(sourceSpans)} cannot be empty.");
}
var firstDocument = sourceSpans[0].Document;
var originationParts = ImmutableArray.Create(
new TaggedText(TextTags.Text, firstDocument.Project.Name));
return new DefaultDefinitionItem(
tags, displayParts, nameDisplayParts, originationParts,
sourceSpans, properties, displayableProperties, displayIfNoReferences);
}
internal static DefinitionItem CreateMetadataDefinition(
ImmutableArray<string> tags,
ImmutableArray<TaggedText> displayParts,
ImmutableArray<TaggedText> nameDisplayParts,
Solution solution,
ISymbol symbol,
ImmutableDictionary<string, string>? properties = null,
bool displayIfNoReferences = true)
{
properties ??= ImmutableDictionary<string, string>.Empty;
var symbolKey = symbol.GetSymbolKey().ToString();
var projectId = solution.GetOriginatingProjectId(symbol);
Contract.ThrowIfNull(projectId);
properties = properties
.Add(MetadataSymbolKey, symbolKey)
.Add(MetadataSymbolOriginatingProjectIdGuid, projectId.Id.ToString())
.Add(MetadataSymbolOriginatingProjectIdDebugName, projectId.DebugName ?? "");
// Find the highest level containing type to show as the "file name". For metadata locations
// that come from embedded source or SourceLink this could be wrong, as there is no reason
// to assume a type is defined in a filename that matches, but its _way_ too expensive
// to try to find the right answer. For metadata-as-source locations though, it will be the same
// as the synthesized filename, so will make sense in the majority of cases.
var containingTypeName = MetadataAsSourceHelpers.GetTopLevelContainingNamedType(symbol).Name;
properties = properties.Add(AbstractReferenceFinder.ContainingTypeInfoPropertyName, containingTypeName);
var originationParts = GetOriginationParts(symbol);
return new DefaultDefinitionItem(
tags, displayParts, nameDisplayParts, originationParts,
sourceSpans: ImmutableArray<DocumentSpan>.Empty,
properties: properties,
displayableProperties: ImmutableDictionary<string, string>.Empty,
displayIfNoReferences: displayIfNoReferences);
}
// Kept around for binary compat with F#/TypeScript.
public static DefinitionItem CreateNonNavigableItem(
ImmutableArray<string> tags,
ImmutableArray<TaggedText> displayParts,
ImmutableArray<TaggedText> originationParts,
bool displayIfNoReferences)
{
return CreateNonNavigableItem(
tags, displayParts, originationParts,
properties: null, displayIfNoReferences: displayIfNoReferences);
}
public static DefinitionItem CreateNonNavigableItem(
ImmutableArray<string> tags,
ImmutableArray<TaggedText> displayParts,
ImmutableArray<TaggedText> originationParts = 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: ImmutableArray<TaggedText>.Empty,
originationParts: originationParts,
sourceSpans: ImmutableArray<DocumentSpan>.Empty,
properties: properties,
displayableProperties: ImmutableDictionary<string, string>.Empty,
displayIfNoReferences: displayIfNoReferences);
}
internal static ImmutableArray<TaggedText> GetOriginationParts(ISymbol symbol)
{
// We don't show an origination location for a namespace because it can span over
// both metadata assemblies and source projects.
//
// Otherwise show the assembly this symbol came from as the Origination of
// the DefinitionItem.
if (symbol.Kind != SymbolKind.Namespace)
{
var assemblyName = symbol.ContainingAssembly?.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat);
if (!string.IsNullOrWhiteSpace(assemblyName))
{
return ImmutableArray.Create(new TaggedText(TextTags.Assembly, assemblyName));
}
}
return ImmutableArray<TaggedText>.Empty;
}
public DetachedDefinitionItem Detach()
=> new(Tags, DisplayParts, NameDisplayParts, OriginationParts, SourceSpans.SelectAsArray(ss => (DocumentIdSpan)ss), Properties, DisplayableProperties, DisplayIfNoReferences);
}
}
|