// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Immutable; using System.Composition; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Classification; using Microsoft.CodeAnalysis.Completion; using Microsoft.CodeAnalysis.Features.RQName; using Microsoft.CodeAnalysis.FindSymbols; using Microsoft.CodeAnalysis.FindUsages; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.PooledObjects; using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.Editor.FindUsages { internal interface IDefinitionsAndReferencesFactory : IWorkspaceService { DefinitionItem GetThirdPartyDefinitionItem( Solution solution, DefinitionItem definitionItem, CancellationToken cancellationToken); } [ExportWorkspaceService(typeof(IDefinitionsAndReferencesFactory)), Shared] internal class DefaultDefinitionsAndReferencesFactory : IDefinitionsAndReferencesFactory { [ImportingConstructor] public DefaultDefinitionsAndReferencesFactory() { } /// /// Provides an extension point that allows for other workspace layers to add additional /// results to the results found by the FindReferences engine. /// public virtual DefinitionItem GetThirdPartyDefinitionItem( Solution solution, DefinitionItem definitionItem, CancellationToken cancellationToken) { return null; } } internal static class DefinitionItemExtensions { public static DefinitionItem ToNonClassifiedDefinitionItem( this ISymbol definition, Project project, bool includeHiddenLocations) { // Because we're passing in 'false' for 'includeClassifiedSpans', this won't ever have // to actually do async work. This is because the only asynchrony is when we are trying // to compute the classified spans for the locations of the definition. So it's totally // fine to pass in CancellationToken.None and block on the result. return ToDefinitionItemAsync( definition, project, includeHiddenLocations, includeClassifiedSpans: false, options: FindReferencesSearchOptions.Default, cancellationToken: CancellationToken.None).WaitAndGetResult_CanCallOnBackground(CancellationToken.None); } public static Task ToClassifiedDefinitionItemAsync( this ISymbol definition, Project project, bool includeHiddenLocations, FindReferencesSearchOptions options, CancellationToken cancellationToken) { return ToDefinitionItemAsync(definition, project, includeHiddenLocations, includeClassifiedSpans: true, options, cancellationToken); } private static async Task ToDefinitionItemAsync( this ISymbol definition, Project project, bool includeHiddenLocations, bool includeClassifiedSpans, FindReferencesSearchOptions options, CancellationToken cancellationToken) { // Ensure we're working with the original definition for the symbol. I.e. When we're // creating definition items, we want to create them for types like Dictionary // not some random instantiation of that type. // // This ensures that the type will both display properly to the user, as well as ensuring // that we can accurately resolve the type later on when we try to navigate to it. definition = definition.OriginalDefinition; var displayParts = definition.ToDisplayParts(GetFormat(definition)).ToTaggedText(); var nameDisplayParts = definition.ToDisplayParts(s_namePartsFormat).ToTaggedText(); var tags = GlyphTags.GetTags(definition.GetGlyph()); var displayIfNoReferences = definition.ShouldShowWithNoReferenceLocations( options, showMetadataSymbolsWithoutReferences: false); using var sourceLocationsDisposer = ArrayBuilder.GetInstance(out var sourceLocations); var properties = GetProperties(definition); // If it's a namespace, don't create any normal location. Namespaces // come from many different sources, but we'll only show a single // root definition node for it. That node won't be navigable. if (definition.Kind != SymbolKind.Namespace) { foreach (var location in definition.Locations) { if (location.IsInMetadata) { return DefinitionItem.CreateMetadataDefinition( tags, displayParts, nameDisplayParts, project, definition, properties, displayIfNoReferences); } else if (location.IsInSource) { if (!location.IsVisibleSourceLocation() && !includeHiddenLocations) { continue; } var document = project.Solution.GetDocument(location.SourceTree); if (document != null) { var documentLocation = !includeClassifiedSpans ? new DocumentSpan(document, location.SourceSpan) : await ClassifiedSpansAndHighlightSpanFactory.GetClassifiedDocumentSpanAsync( document, location.SourceSpan, cancellationToken).ConfigureAwait(false); sourceLocations.Add(documentLocation); } } } } if (sourceLocations.Count == 0) { // If we got no definition locations, then create a sentinel one // that we can display but which will not allow navigation. return DefinitionItem.CreateNonNavigableItem( tags, displayParts, DefinitionItem.GetOriginationParts(definition), properties, displayIfNoReferences); } return DefinitionItem.Create( tags, displayParts, sourceLocations.ToImmutable(), nameDisplayParts, properties, displayIfNoReferences); } private static ImmutableDictionary GetProperties(ISymbol definition) { var properties = ImmutableDictionary.Empty; var rqName = RQNameInternal.From(definition); if (rqName != null) { properties = properties.Add(DefinitionItem.RQNameKey1, rqName); } if (definition?.IsConstructor() == true) { // If the symbol being considered is a constructor include the containing type in case // a third party wants to navigate to that. rqName = RQNameInternal.From(definition.ContainingType); if (rqName != null) { properties = properties.Add(DefinitionItem.RQNameKey2, rqName); } } return properties; } public static async Task TryCreateSourceReferenceItemAsync( this ReferenceLocation referenceLocation, DefinitionItem definitionItem, bool includeHiddenLocations, CancellationToken cancellationToken) { var location = referenceLocation.Location; Debug.Assert(location.IsInSource); if (!location.IsVisibleSourceLocation() && !includeHiddenLocations) { return null; } var document = referenceLocation.Document; var sourceSpan = location.SourceSpan; var documentSpan = await ClassifiedSpansAndHighlightSpanFactory.GetClassifiedDocumentSpanAsync( document, sourceSpan, cancellationToken).ConfigureAwait(false); return new SourceReferenceItem(definitionItem, documentSpan, referenceLocation.SymbolUsageInfo); } private static SymbolDisplayFormat GetFormat(ISymbol definition) { return definition.Kind == SymbolKind.Parameter ? s_parameterDefinitionFormat : s_definitionFormat; } private static readonly SymbolDisplayFormat s_namePartsFormat = new SymbolDisplayFormat( memberOptions: SymbolDisplayMemberOptions.IncludeContainingType); private static readonly SymbolDisplayFormat s_definitionFormat = new SymbolDisplayFormat( typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameOnly, genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeParameters, parameterOptions: SymbolDisplayParameterOptions.IncludeType, propertyStyle: SymbolDisplayPropertyStyle.ShowReadWriteDescriptor, delegateStyle: SymbolDisplayDelegateStyle.NameAndSignature, kindOptions: SymbolDisplayKindOptions.IncludeMemberKeyword | SymbolDisplayKindOptions.IncludeNamespaceKeyword | SymbolDisplayKindOptions.IncludeTypeKeyword, localOptions: SymbolDisplayLocalOptions.IncludeType, memberOptions: SymbolDisplayMemberOptions.IncludeContainingType | SymbolDisplayMemberOptions.IncludeExplicitInterface | SymbolDisplayMemberOptions.IncludeModifiers | SymbolDisplayMemberOptions.IncludeParameters | SymbolDisplayMemberOptions.IncludeType, miscellaneousOptions: SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers | SymbolDisplayMiscellaneousOptions.UseSpecialTypes); private static readonly SymbolDisplayFormat s_parameterDefinitionFormat = s_definitionFormat .AddParameterOptions(SymbolDisplayParameterOptions.IncludeName); } }