File: GoToBase\AbstractGoToBaseService.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.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Classification;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.FindUsages;
using Microsoft.CodeAnalysis.Shared.Extensions;
 
namespace Microsoft.CodeAnalysis.GoToBase;
 
internal abstract class AbstractGoToBaseService : IGoToBaseService
{
    protected abstract Task<IMethodSymbol?> FindNextConstructorInChainAsync(
        Solution solution, IMethodSymbol constructor, CancellationToken cancellationToken);
 
    protected static IMethodSymbol? FindBaseNoArgConstructor(IMethodSymbol constructor)
    {
        var baseType = constructor.ContainingType.BaseType;
        if (baseType is null)
            return null;
 
        return baseType.InstanceConstructors.FirstOrDefault(
            baseConstructor => baseConstructor.IsAccessibleWithin(constructor.ContainingType) &&
                baseConstructor.Parameters.All(p => p.IsOptional || p.IsParams));
    }
 
    public async Task FindBasesAsync(IFindUsagesContext context, Document document, int position, OptionsProvider<ClassificationOptions> classificationOptions, CancellationToken cancellationToken)
    {
        var symbolAndProjectOpt = await FindUsagesHelpers.GetRelevantSymbolAndProjectAtPositionAsync(
            document, position, cancellationToken).ConfigureAwait(false);
 
        if (symbolAndProjectOpt == null)
        {
            await context.ReportNoResultsAsync(
                FeaturesResources.Cannot_navigate_to_the_symbol_under_the_caret, cancellationToken).ConfigureAwait(false);
            return;
        }
 
        var (symbol, project) = symbolAndProjectOpt.Value;
 
        var solution = project.Solution;
        var bases = FindBaseHelpers.FindBases(symbol, solution, cancellationToken);
        if (bases.Length == 0 && symbol is IMethodSymbol { MethodKind: MethodKind.Constructor } constructor)
        {
            var nextConstructor = await FindNextConstructorInChainAsync(solution, constructor, cancellationToken).ConfigureAwait(false);
            if (nextConstructor != null)
                bases = [nextConstructor];
        }
 
        await context.SetSearchTitleAsync(
            string.Format(FeaturesResources._0_bases,
            FindUsagesHelpers.GetDisplayName(symbol)),
            cancellationToken).ConfigureAwait(false);
 
        var found = false;
 
        // For each potential base, try to find its definition in sources.
        // If found, add it's definitionItem to the context.
        // If not found but the symbol is from metadata, create it's definition item from metadata and add to the context.
        foreach (var baseSymbol in bases)
        {
            var sourceDefinition = await SymbolFinder.FindSourceDefinitionAsync(
               baseSymbol, solution, cancellationToken).ConfigureAwait(false);
            if (sourceDefinition != null)
            {
                var definitionItem = await sourceDefinition.ToClassifiedDefinitionItemAsync(
                    classificationOptions, solution, FindReferencesSearchOptions.Default, isPrimary: true, includeHiddenLocations: false, cancellationToken: cancellationToken).ConfigureAwait(false);
 
                await context.OnDefinitionFoundAsync(definitionItem, cancellationToken).ConfigureAwait(false);
                found = true;
            }
            else if (baseSymbol.Locations.Any(static l => l.IsInMetadata))
            {
                var definitionItem = baseSymbol.ToNonClassifiedDefinitionItem(
                    solution, FindReferencesSearchOptions.Default, includeHiddenLocations: true);
                await context.OnDefinitionFoundAsync(definitionItem, cancellationToken).ConfigureAwait(false);
                found = true;
            }
        }
 
        if (!found)
        {
            await context.ReportNoResultsAsync(FeaturesResources.The_symbol_has_no_base, cancellationToken).ConfigureAwait(false);
        }
    }
}