File: Completion\CompletionProviders\ExplicitInterfaceTypeCompletionProvider.cs
Web Access
Project: src\src\roslyn\src\Features\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.Features.csproj (Microsoft.CodeAnalysis.CSharp.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;
using System.Collections.Immutable;
using System.Composition;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.Completion;
using Microsoft.CodeAnalysis.Completion.Providers;
using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.Utilities;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers;

[ExportCompletionProvider(nameof(ExplicitInterfaceTypeCompletionProvider), LanguageNames.CSharp), Shared]
[ExtensionOrder(After = nameof(ExplicitInterfaceMemberCompletionProvider))]
[method: ImportingConstructor]
[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
internal sealed partial class ExplicitInterfaceTypeCompletionProvider() : AbstractSymbolCompletionProvider<CSharpSyntaxContext>
{
    internal override string Language => LanguageNames.CSharp;

    public override bool IsInsertionTrigger(SourceText text, int insertedCharacterPosition, CompletionOptions options)
        => CompletionUtilities.IsTriggerAfterSpaceOrStartOfWordCharacter(text, insertedCharacterPosition, options);

    public override ImmutableHashSet<char> TriggerCharacters { get; } = CompletionUtilities.SpaceTriggerCharacter;

    protected override (string displayText, string suffix, string insertionText) GetDisplayAndSuffixAndInsertionText(ISymbol symbol, CSharpSyntaxContext context)
        => CompletionUtilities.GetDisplayAndSuffixAndInsertionText(symbol, context);

    public override async Task ProvideCompletionsAsync(CompletionContext context)
    {
        try
        {
            var completionCount = context.Items.Count;
            await base.ProvideCompletionsAsync(context).ConfigureAwait(false);

            if (completionCount < context.Items.Count)
            {
                // If we added any items, then add a suggestion mode item as this is a location 
                // where a member name could be written, and we should not interfere with that.
                context.SuggestionModeItem = CreateSuggestionModeItem(
                    CSharpFeaturesResources.member_name,
                    CSharpFeaturesResources.Autoselect_disabled_due_to_member_declaration);
            }
        }
        catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, ErrorSeverity.General))
        {
            // nop
        }
    }

    protected override Task<ImmutableArray<SymbolAndSelectionInfo>> GetSymbolsAsync(
        CompletionContext? completionContext, CSharpSyntaxContext context, int position, CompletionOptions options, CancellationToken cancellationToken)
    {
        var targetToken = context.TargetToken;

        // Don't want to offer this after "async" (even though the compiler may parse that as a type).
        if (SyntaxFacts.GetContextualKeywordKind(targetToken.ValueText) == SyntaxKind.AsyncKeyword)
            return SpecializedTasks.EmptyImmutableArray<SymbolAndSelectionInfo>();

        var potentialTypeNode = targetToken.Parent;
        if (targetToken.IsKind(SyntaxKind.GreaterThanToken) && potentialTypeNode is TypeArgumentListSyntax typeArgumentList)
            potentialTypeNode = typeArgumentList.Parent;

        var typeNode = potentialTypeNode as TypeSyntax;

        while (typeNode != null)
        {
            if (typeNode.Parent is TypeSyntax parentType && parentType.Span.End < position)
            {
                typeNode = parentType;
            }
            else
            {
                break;
            }
        }

        if (typeNode == null)
            return SpecializedTasks.EmptyImmutableArray<SymbolAndSelectionInfo>();

        // We weren't after something that looked like a type.
        var tokenBeforeType = typeNode.GetFirstToken().GetPreviousToken();

        if (!IsPreviousTokenValid(tokenBeforeType))
            return SpecializedTasks.EmptyImmutableArray<SymbolAndSelectionInfo>();

        var typeDeclaration = typeNode.GetAncestor<TypeDeclarationSyntax>();
        if (typeDeclaration == null)
            return SpecializedTasks.EmptyImmutableArray<SymbolAndSelectionInfo>();

        // Looks syntactically good.  See what interfaces our containing class/struct/interface has
        Debug.Assert(IsClassOrStructOrInterfaceOrRecord(typeDeclaration));

        var semanticModel = context.SemanticModel;
        var namedType = semanticModel.GetDeclaredSymbol(typeDeclaration, cancellationToken);
        Contract.ThrowIfNull(namedType);

        using var _ = PooledHashSet<ISymbol>.GetInstance(out var interfaceSet);
        foreach (var directInterface in namedType.Interfaces)
        {
            interfaceSet.Add(directInterface);
            interfaceSet.AddRange(directInterface.AllInterfaces);
        }

        return Task.FromResult(interfaceSet.SelectAsArray(t => new SymbolAndSelectionInfo(Symbol: t, Preselect: false)));
    }

    private static bool IsPreviousTokenValid(SyntaxToken tokenBeforeType)
    {
        while (tokenBeforeType.Kind() is SyntaxKind.AsyncKeyword or SyntaxKind.StaticKeyword)
            tokenBeforeType = tokenBeforeType.GetPreviousToken();

        // Show us after the open brace for a class/struct/interface
        if (tokenBeforeType.Kind() == SyntaxKind.OpenBraceToken)
            return IsClassOrStructOrInterfaceOrRecord(tokenBeforeType.GetRequiredParent());

        if (tokenBeforeType.Kind() is SyntaxKind.CloseBraceToken or SyntaxKind.SemicolonToken)
        {
            // Check that we're after a class/struct/interface member.
            var memberDeclaration = tokenBeforeType.GetAncestor<MemberDeclarationSyntax>();
            return memberDeclaration?.GetLastToken() == tokenBeforeType &&
                   IsClassOrStructOrInterfaceOrRecord(memberDeclaration.GetRequiredParent());
        }

        return false;
    }

    private static bool IsClassOrStructOrInterfaceOrRecord(SyntaxNode node)
        => node.Kind() is SyntaxKind.ClassDeclaration or SyntaxKind.StructDeclaration or
            SyntaxKind.InterfaceDeclaration or SyntaxKind.RecordDeclaration or SyntaxKind.RecordStructDeclaration;

    protected override CompletionItem CreateItem(
        CompletionContext completionContext,
        string displayText,
        string displayTextSuffix,
        string insertionText,
        ImmutableArray<SymbolAndSelectionInfo> symbols,
        CSharpSyntaxContext context,
        SupportedPlatformData? supportedPlatformData)
    {
        return CreateItemDefault(displayText, displayTextSuffix, insertionText, symbols, context, supportedPlatformData);
    }
}