File: FindSymbols\Declarations\DeclarationFinder_SourceDeclarations.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.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.PatternMatching;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Remote;
 
namespace Microsoft.CodeAnalysis.FindSymbols;
 
// All the logic for finding source declarations in a given solution/project with some name 
// is in this file.  
 
internal static partial class DeclarationFinder
{
    #region Dispatch Members
 
    // These are the public entrypoints to finding source declarations.  They will attempt to
    // remote the query to the OOP process, and will fallback to local processing if they can't.
 
    public static async Task<ImmutableArray<ISymbol>> FindSourceDeclarationsWithNormalQueryAsync(
        Solution solution, string name, bool ignoreCase, SymbolFilter criteria, CancellationToken cancellationToken)
    {
        if (solution == null)
        {
            throw new ArgumentNullException(nameof(solution));
        }
 
        if (name == null)
        {
            throw new ArgumentNullException(nameof(name));
        }
 
        if (string.IsNullOrWhiteSpace(name))
        {
            return [];
        }
 
        var client = await RemoteHostClient.TryGetClientAsync(solution.Services, cancellationToken).ConfigureAwait(false);
        if (client != null)
        {
            var result = await client.TryInvokeAsync<IRemoteSymbolFinderService, ImmutableArray<SerializableSymbolAndProjectId>>(
                solution,
                (service, solutionInfo, cancellationToken) => service.FindSolutionSourceDeclarationsWithNormalQueryAsync(solutionInfo, name, ignoreCase, criteria, cancellationToken),
                cancellationToken).ConfigureAwait(false);
 
            if (!result.HasValue)
            {
                return [];
            }
 
            return await RehydrateAsync(solution, result.Value, cancellationToken).ConfigureAwait(false);
        }
 
        return await FindSourceDeclarationsWithNormalQueryInCurrentProcessAsync(
            solution, name, ignoreCase, criteria, cancellationToken).ConfigureAwait(false);
    }
 
    public static async Task<ImmutableArray<ISymbol>> FindSourceDeclarationsWithNormalQueryAsync(
        Project project, string name, bool ignoreCase, SymbolFilter criteria, CancellationToken cancellationToken)
    {
        if (project == null)
        {
            throw new ArgumentNullException(nameof(project));
        }
 
        if (name == null)
        {
            throw new ArgumentNullException(nameof(name));
        }
 
        if (string.IsNullOrWhiteSpace(name))
        {
            return [];
        }
 
        var client = await RemoteHostClient.TryGetClientAsync(project, cancellationToken).ConfigureAwait(false);
        if (client != null)
        {
            var result = await client.TryInvokeAsync<IRemoteSymbolFinderService, ImmutableArray<SerializableSymbolAndProjectId>>(
                project.Solution,
                (service, solutionInfo, cancellationToken) => service.FindProjectSourceDeclarationsWithNormalQueryAsync(solutionInfo, project.Id, name, ignoreCase, criteria, cancellationToken),
                cancellationToken).ConfigureAwait(false);
 
            if (!result.HasValue)
            {
                return [];
            }
 
            return await RehydrateAsync(project.Solution, result.Value, cancellationToken).ConfigureAwait(false);
        }
 
        return await FindSourceDeclarationsWithNormalQueryInCurrentProcessAsync(
            project, name, ignoreCase, criteria, cancellationToken).ConfigureAwait(false);
    }
 
    public static async Task<ImmutableArray<ISymbol>> FindSourceDeclarationsWithPatternAsync(
        Solution solution, string pattern, SymbolFilter criteria, CancellationToken cancellationToken)
    {
        if (solution == null)
        {
            throw new ArgumentNullException(nameof(solution));
        }
 
        if (pattern == null)
        {
            throw new ArgumentNullException(nameof(pattern));
        }
 
        var client = await RemoteHostClient.TryGetClientAsync(solution.Services, cancellationToken).ConfigureAwait(false);
        if (client != null)
        {
            var result = await client.TryInvokeAsync<IRemoteSymbolFinderService, ImmutableArray<SerializableSymbolAndProjectId>>(
                solution,
                (service, solutionInfo, cancellationToken) => service.FindSolutionSourceDeclarationsWithPatternAsync(solutionInfo, pattern, criteria, cancellationToken),
                cancellationToken).ConfigureAwait(false);
 
            if (!result.HasValue)
            {
                return [];
            }
 
            return await RehydrateAsync(solution, result.Value, cancellationToken).ConfigureAwait(false);
        }
 
        return await FindSourceDeclarationsWithPatternInCurrentProcessAsync(
            solution, pattern, criteria, cancellationToken).ConfigureAwait(false);
    }
 
    public static async Task<ImmutableArray<ISymbol>> FindSourceDeclarationsWithPatternAsync(
        Project project, string pattern, SymbolFilter criteria, CancellationToken cancellationToken)
    {
        if (project == null)
        {
            throw new ArgumentNullException(nameof(project));
        }
 
        if (pattern == null)
        {
            throw new ArgumentNullException(nameof(pattern));
        }
 
        var client = await RemoteHostClient.TryGetClientAsync(project, cancellationToken).ConfigureAwait(false);
        if (client != null)
        {
            var result = await client.TryInvokeAsync<IRemoteSymbolFinderService, ImmutableArray<SerializableSymbolAndProjectId>>(
                project.Solution,
                (service, solutionInfo, cancellationToken) => service.FindProjectSourceDeclarationsWithPatternAsync(solutionInfo, project.Id, pattern, criteria, cancellationToken),
                cancellationToken).ConfigureAwait(false);
 
            if (!result.HasValue)
            {
                return [];
            }
 
            return await RehydrateAsync(project.Solution, result.Value, cancellationToken).ConfigureAwait(false);
        }
 
        return await FindSourceDeclarationsWithPatternInCurrentProcessAsync(
            project, pattern, criteria, cancellationToken).ConfigureAwait(false);
    }
 
    #endregion
 
    #region Local processing
 
    // These are the members that have the core logic that does the actual finding.  They will
    // be called 'in proc' in the remote process if we are able to remote the request.  Or they
    // will be called 'in proc' from within VS if we are not able to remote the request.
 
    internal static async Task<ImmutableArray<ISymbol>> FindSourceDeclarationsWithNormalQueryInCurrentProcessAsync(
        Solution solution, string name, bool ignoreCase, SymbolFilter criteria, CancellationToken cancellationToken)
    {
        using var query = SearchQuery.Create(name, ignoreCase);
 
        using var _ = ArrayBuilder<ISymbol>.GetInstance(out var result);
        foreach (var project in solution.Projects)
        {
            await AddCompilationSourceDeclarationsWithNormalQueryAsync(
                project, query, criteria, result, cancellationToken).ConfigureAwait(false);
        }
 
        return result.ToImmutableAndClear();
    }
 
    internal static async Task<ImmutableArray<ISymbol>> FindSourceDeclarationsWithNormalQueryInCurrentProcessAsync(
        Project project, string name, bool ignoreCase, SymbolFilter filter, CancellationToken cancellationToken)
    {
        using var _ = ArrayBuilder<ISymbol>.GetInstance(out var result);
        using var query = SearchQuery.Create(name, ignoreCase);
 
        await AddCompilationSourceDeclarationsWithNormalQueryAsync(
            project, query, filter, result, cancellationToken).ConfigureAwait(false);
 
        return result.ToImmutableAndClear();
    }
 
    private static async Task<ImmutableArray<ISymbol>> FindSourceDeclarationsWithPatternInCurrentProcessAsync(
        string pattern, Func<SearchQuery, Task<ImmutableArray<ISymbol>>> searchAsync)
    {
        // The compiler API only supports a predicate which is given a symbol's name.  Because
        // we only have the name, and nothing else, we need to check it against the last segment
        // of the pattern.  i.e. if the pattern is 'Console.WL' and we are given 'WriteLine', then
        // we don't want to check the whole pattern against it (as it will clearly fail), instead
        // we only want to check the 'WL' portion.  Then, after we get all the candidate symbols
        // we'll check if the full name matches the full pattern.
        var (namePart, containerPart) = PatternMatcher.GetNameAndContainer(pattern);
 
        // If we don't have a dot in the pattern, just make a pattern matcher for the entire
        // pattern they passed in.  Otherwise, make a pattern matcher just for the part after
        // the dot.
        using var nameMatcher = PatternMatcher.CreatePatternMatcher(namePart, includeMatchedSpans: false);
        using var query = SearchQuery.CreateCustom(nameMatcher.Matches);
 
        var symbolAndProjectIds = await searchAsync(query).ConfigureAwait(false);
 
        if (symbolAndProjectIds.Length == 0 ||
            containerPart == null)
        {
            // If it wasn't a dotted pattern, or we didn't get anything back, then we're done.
            // We can just return whatever set of results we got so far.
            return symbolAndProjectIds;
        }
 
        // Ok, we had a dotted pattern.  Have to see if the symbol's container matches the 
        // pattern as well.
        using var containerPatternMatcher = PatternMatcher.CreateDotSeparatedContainerMatcher(containerPart);
 
        return symbolAndProjectIds.WhereAsArray(t =>
            containerPatternMatcher.Matches(GetContainer(t)));
    }
 
    internal static Task<ImmutableArray<ISymbol>> FindSourceDeclarationsWithPatternInCurrentProcessAsync(
        Solution solution, string pattern, SymbolFilter criteria, CancellationToken cancellationToken)
    {
        return FindSourceDeclarationsWithPatternInCurrentProcessAsync(
            pattern,
            query => SymbolFinder.FindSourceDeclarationsWithCustomQueryAsync(solution, query, criteria, cancellationToken));
    }
 
    internal static Task<ImmutableArray<ISymbol>> FindSourceDeclarationsWithPatternInCurrentProcessAsync(
        Project project, string pattern, SymbolFilter criteria, CancellationToken cancellationToken)
    {
        return FindSourceDeclarationsWithPatternInCurrentProcessAsync(
            pattern,
            query => SymbolFinder.FindSourceDeclarationsWithCustomQueryAsync(project, query, criteria, cancellationToken));
    }
 
    private static string? GetContainer(ISymbol symbol)
    {
        var container = symbol.ContainingSymbol;
        if (container == null)
            return null;
 
        return container.ToDisplayString(DottedNameFormat);
    }
 
    private static readonly SymbolDisplayFormat DottedNameFormat =
        new(
            globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.Omitted,
            typeQualificationStyle: SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces,
            delegateStyle: SymbolDisplayDelegateStyle.NameOnly,
            extensionMethodStyle: SymbolDisplayExtensionMethodStyle.StaticMethod,
            propertyStyle: SymbolDisplayPropertyStyle.NameOnly);
 
    #endregion
}