File: FindSymbols\Declarations\DeclarationFinder_AllDeclarations.cs
Web Access
Project: src\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces)
// 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.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Remote;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.FindSymbols;
 
// All the logic for finding all declarations in a given solution/project with some name 
// is in this file.  
 
internal static partial class DeclarationFinder
{
    public static async Task<ImmutableArray<ISymbol>> FindAllDeclarationsWithNormalQueryAsync(
        Project project, SearchQuery query, SymbolFilter criteria, CancellationToken cancellationToken)
    {
        // All entrypoints to this function are Find functions that are only searching
        // for specific strings (i.e. they never do a custom search).
        Contract.ThrowIfTrue(query.Kind == SearchKind.Custom, "Custom queries are not supported in this API");
 
        if (project == null)
            throw new ArgumentNullException(nameof(project));
 
        Contract.ThrowIfNull(query.Name);
        if (string.IsNullOrWhiteSpace(query.Name))
            return [];
 
        var client = await RemoteHostClient.TryGetClientAsync(project, cancellationToken).ConfigureAwait(false);
        if (client != null)
        {
            var solution = project.Solution;
 
            var result = await client.TryInvokeAsync<IRemoteSymbolFinderService, ImmutableArray<SerializableSymbolAndProjectId>>(
                solution,
                (service, solutionInfo, cancellationToken) => service.FindAllDeclarationsWithNormalQueryAsync(solutionInfo, project.Id, query.Name, query.Kind, criteria, cancellationToken),
                cancellationToken).ConfigureAwait(false);
 
            if (!result.HasValue)
            {
                return [];
            }
 
            return await RehydrateAsync(solution, result.Value, cancellationToken).ConfigureAwait(false);
        }
 
        return await FindAllDeclarationsWithNormalQueryInCurrentProcessAsync(
            project, query, criteria, cancellationToken).ConfigureAwait(false);
    }
 
    internal static async Task<ImmutableArray<ISymbol>> FindAllDeclarationsWithNormalQueryInCurrentProcessAsync(
        Project project, SearchQuery query, SymbolFilter criteria, CancellationToken cancellationToken)
    {
        using var _ = ArrayBuilder<ISymbol>.GetInstance(out var result);
 
        // Lazily produce the compilation.  We don't want to incur any costs (especially source generators) if there
        // are no results for this query in this project.
        var lazyCompilation = AsyncLazy.Create(static (project, cancellationToken) =>
            project.GetRequiredCompilationAsync(cancellationToken),
            arg: project);
 
        if (project.SupportsCompilation)
        {
            await SearchCurrentProjectAsync().ConfigureAwait(false);
            await SearchProjectReferencesAsync().ConfigureAwait(false);
            await SearchMetadataReferencesAsync().ConfigureAwait(false);
        }
 
        return result.ToImmutableAndClear();
 
        async Task SearchCurrentProjectAsync()
        {
            // Search in the source symbols for this project first.
            using var _ = ArrayBuilder<ISymbol>.GetInstance(out var buffer);
 
            // get declarations from the compilation's assembly
            await AddCompilationSourceDeclarationsWithNormalQueryAsync(
                project, query, criteria, buffer, cancellationToken).ConfigureAwait(false);
 
            // No need to map symbols since we're looking in the starting project.
            await AddAllAsync(buffer, mapSymbol: false).ConfigureAwait(false);
        }
 
        async Task SearchProjectReferencesAsync()
        {
            // get declarations from directly referenced projects
            foreach (var projectReference in project.ProjectReferences)
            {
                using var _ = ArrayBuilder<ISymbol>.GetInstance(out var buffer);
 
                var referencedProject = project.Solution.GetProject(projectReference.ProjectId);
                if (referencedProject is null)
                    continue;
 
                await AddCompilationSourceDeclarationsWithNormalQueryAsync(
                    referencedProject, query, criteria, buffer, cancellationToken).ConfigureAwait(false);
 
                // Add all the results.  If they're from a different language, attempt to map them back into the
                // starting project's language.
                await AddAllAsync(buffer, mapSymbol: referencedProject.Language != project.Language).ConfigureAwait(false);
            }
        }
 
        async Task SearchMetadataReferencesAsync()
        {
            // get declarations from directly referenced metadata
            foreach (var peReference in project.MetadataReferences.OfType<PortableExecutableReference>())
            {
                using var _ = ArrayBuilder<ISymbol>.GetInstance(out var buffer);
 
                var lazyAssembly = AsyncLazy.Create(static async (arg, cancellationToken) =>
                    {
                        var compilation = await arg.lazyCompilation.GetValueAsync(cancellationToken).ConfigureAwait(false);
                        var assemblySymbol = compilation.GetAssemblyOrModuleSymbol(arg.peReference) as IAssemblySymbol;
                        return assemblySymbol;
                    },
                    arg: (lazyCompilation, peReference));
 
                await AddMetadataDeclarationsWithNormalQueryAsync(
                    project, lazyAssembly, peReference,
                    query, criteria, buffer, cancellationToken).ConfigureAwait(false);
 
                // No need to map symbols since we're looking in metadata.  They will always be in the language of our starting project.
                await AddAllAsync(buffer, mapSymbol: false).ConfigureAwait(false);
            }
        }
 
        async Task AddAllAsync(ArrayBuilder<ISymbol> buffer, bool mapSymbol)
        {
            if (buffer.Count == 0)
                return;
 
            var compilation = await lazyCompilation.GetValueAsync(cancellationToken).ConfigureAwait(false);
 
            // Make certain all namespace symbols returned by API are from the compilation
            // for the passed in project.
            foreach (var symbol in buffer)
            {
                var mappedSymbol = mapSymbol
                    ? symbol.GetSymbolKey(cancellationToken).Resolve(compilation, cancellationToken: cancellationToken).Symbol
                    : symbol;
 
                result.AddIfNotNull(mappedSymbol is INamespaceSymbol ns
                    ? compilation.GetCompilationNamespace(ns)
                    : mappedSymbol);
            }
        }
    }
 
    private static async Task<ImmutableArray<ISymbol>> RehydrateAsync(
        Solution solution, IList<SerializableSymbolAndProjectId> array, CancellationToken cancellationToken)
    {
        var result = ArrayBuilder<ISymbol>.GetInstance(array.Count);
 
        foreach (var dehydrated in array)
        {
            cancellationToken.ThrowIfCancellationRequested();
            var rehydrated = await dehydrated.TryRehydrateAsync(solution, cancellationToken).ConfigureAwait(false);
            if (rehydrated != null)
            {
                result.Add(rehydrated);
            }
        }
 
        return result.ToImmutableAndFree();
    }
}