File: Completion\CompletionProviders\ExplicitInterfaceMemberCompletionProvider.ItemGetter.cs
Web Access
Project: src\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.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Completion;
using Microsoft.CodeAnalysis.Completion.Providers;
using Microsoft.CodeAnalysis.CSharp.CodeGeneration;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Editing;
using Microsoft.CodeAnalysis.ErrorReporting;
using Microsoft.CodeAnalysis.ImplementInterface;
using Microsoft.CodeAnalysis.ImplementType;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.Completion.Providers;
 
internal sealed partial class ExplicitInterfaceMemberCompletionProvider
{
    private sealed class ItemGetter(
        ExplicitInterfaceMemberCompletionProvider overrideCompletionProvider,
        Document document,
        int position,
        SourceText text,
        SyntaxTree syntaxTree,
        int startLineNumber,
        CancellationToken cancellationToken)
        : AbstractItemGetter<ExplicitInterfaceMemberCompletionProvider>(
            overrideCompletionProvider,
            document,
            position,
            text,
            syntaxTree,
            startLineNumber,
            cancellationToken)
    {
        public static async Task<ItemGetter> CreateAsync(
            ExplicitInterfaceMemberCompletionProvider overrideCompletionProvider,
            Document document,
            int position,
            CancellationToken cancellationToken)
        {
            var text = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false);
 
            var syntaxTree = await document.GetRequiredSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
            var startLineNumber = text.Lines.IndexOf(position);
            return new ItemGetter(overrideCompletionProvider, document, position, text, syntaxTree, startLineNumber, cancellationToken);
        }
 
        public override async Task<ImmutableArray<CompletionItem>> GetItemsAsync()
        {
            // modifiers* type? Interface(<typeparams+>)?.|
            try
            {
                var syntaxFacts = Document.GetRequiredLanguageService<ISyntaxFactsService>();
                var semanticFacts = Document.GetRequiredLanguageService<ISemanticFactsService>();
                var implementInterfaceService = Document.GetRequiredLanguageService<IImplementInterfaceService>();
 
                if (!SyntaxTree.IsRightOfDot(Position, CancellationToken) ||
                    syntaxFacts.IsInNonUserCode(SyntaxTree, Position, CancellationToken) ||
                    syntaxFacts.IsPreProcessorDirectiveContext(SyntaxTree, Position, CancellationToken))
                {
                    return [];
                }
 
                var targetToken = SyntaxTree
                    .FindTokenOnLeftOfPosition(Position, CancellationToken)
                    .GetPreviousTokenIfTouchingWord(Position);
 
                var node = targetToken.Parent;
                // Bind the interface name which is to the left of the dot
                NameSyntax? name = null;
                switch (node)
                {
                    case ExplicitInterfaceSpecifierSyntax specifierNode:
                        name = specifierNode.Name;
                        break;
 
                    case QualifiedNameSyntax qualifiedName
                    when node.Parent.IsKind(SyntaxKind.IncompleteMember):
                        name = qualifiedName.Left;
                        break;
 
                    default:
                        return [];
                }
 
                var semanticModel = await Document.ReuseExistingSpeculativeModelAsync(Position, CancellationToken).ConfigureAwait(false);
                var symbol = semanticModel.GetSymbolInfo(name, CancellationToken).Symbol as ITypeSymbol;
                if (symbol is not { TypeKind: TypeKind.Interface })
                    return [];
 
                var typeDeclaration = node.GetAncestor<BaseTypeDeclarationSyntax>();
                if (typeDeclaration is null)
                    return [];
 
                var containingType = semanticModel.GetDeclaredSymbol(typeDeclaration, CancellationToken);
                if (containingType is not INamedTypeSymbol { TypeKind: TypeKind.Class or TypeKind.Struct or TypeKind.Interface })
                    return [];
 
                // We must be explicitly implementing the interface ourselves.
                if (!containingType.Interfaces.Contains(symbol))
                    return [];
 
                var options = await Document.GetImplementTypeOptionsAsync(CancellationToken).ConfigureAwait(false);
                var info = new ImplementInterfaceInfo
                {
                    ClassOrStructType = containingType,
                    ContextNode = typeDeclaration,
                };
 
                // We're going to create a entry for each one, including the signature
                var namePosition = name.SpanStart;
                var text = await Document.GetValueTextAsync(CancellationToken).ConfigureAwait(false);
                text.GetLineAndOffset(namePosition, out var line, out var lineOffset);
                var items = symbol.GetMembers()
                    .Where(FilterInterfaceMember)
                    .SelectAsArray(CreateCompletionItem);
 
                return items;
 
                bool FilterInterfaceMember(ISymbol member)
                {
                    // Explicitly implementable interface members are either abstract or virtual
                    // Interfaces may contain non-overridable members, which we cannot explicitly implement
                    if (!member.IsAbstract && !member.IsVirtual)
                        return false;
 
                    // We cannot explicitly implement inaccessible members
                    if (member.IsAccessor() ||
                        member.Kind == SymbolKind.NamedType ||
                        !semanticModel.IsAccessible(node.SpanStart, member))
                    {
                        return false;
                    }
 
                    // Ensure this is a member we will be able to implement later on.
                    var members = implementInterfaceService.ImplementInterfaceMember(
                        Document, info, options, new() { Explicitly = true }, semanticModel.Compilation, member);
 
                    // this can only work if the member we're trying to implement returns a single member of the same
                    // type we started with.  For example, we don't want to implement a property as two accessor methods
                    // instead.
                    if (members.Length != 1 || members[0].Kind != member.Kind)
                        return false;
 
                    return true;
                }
 
                CompletionItem CreateCompletionItem(ISymbol member)
                {
                    var memberString = ToDisplayString(member, semanticModel);
 
                    // Split the member string into two parts (generally the name, and the signature portion). We want
                    // the split so that other features (like spell-checking), only look at the name portion.
                    var (displayText, displayTextSuffix) = SplitMemberName(memberString);
 
                    return MemberInsertionCompletionItem.Create(
                        displayText, displayTextSuffix, DeclarationModifiers.None, line,
                        member, targetToken, Position,
                        rules: GetRules());
                }
            }
            catch (Exception e) when (FatalError.ReportAndCatchUnlessCanceled(e, ErrorSeverity.General))
            {
                // nop
                return [];
            }
        }
 
        private string ToDisplayString(ISymbol symbol, SemanticModel semanticModel)
            => symbol switch
            {
                IEventSymbol eventSymbol => eventSymbol.Name,
                IPropertySymbol propertySymbol => ToDisplayString(propertySymbol, semanticModel),
                IMethodSymbol methodSymbol => ToDisplayString(methodSymbol, semanticModel),
                _ => throw new ArgumentException("Unexpected interface member symbol kind")
            };
 
        private string ToDisplayString(IPropertySymbol symbol, SemanticModel semanticModel)
        {
            using var _ = PooledStringBuilder.GetInstance(out var builder);
 
            if (symbol.IsIndexer)
            {
                builder.Append("this");
            }
            else
            {
                builder.Append(symbol.Name);
            }
 
            if (symbol.Parameters.Length > 0)
            {
                builder.Append('[');
                AddParameters(symbol.Parameters, builder, semanticModel);
                builder.Append(']');
            }
 
            return builder.ToString();
        }
 
        private string ToDisplayString(IMethodSymbol symbol, SemanticModel semanticModel)
        {
            using var _ = PooledStringBuilder.GetInstance(out var builder);
            switch (symbol.MethodKind)
            {
                case MethodKind.Ordinary:
                    builder.Append(symbol.Name);
                    break;
                case MethodKind.UserDefinedOperator:
                case MethodKind.BuiltinOperator:
                    AppendOperatorKeywords(symbol, builder);
                    builder.Append(SyntaxFacts.GetText(SyntaxFacts.GetOperatorKind(symbol.MetadataName)));
                    break;
                case MethodKind.Conversion:
                    AppendOperatorKeywords(symbol, builder);
                    AddType(symbol.ReturnType, builder, semanticModel);
                    break;
            }
 
            AddTypeArguments(symbol, builder);
            builder.Append('(');
            AddParameters(symbol.Parameters, builder, semanticModel);
            builder.Append(')');
            return builder.ToString();
        }
 
        private static void AppendOperatorKeywords(IMethodSymbol symbol, StringBuilder builder)
        {
            builder.Append("operator ");
            if (SyntaxFacts.IsCheckedOperator(symbol.MetadataName))
            {
                builder.Append("checked ");
            }
        }
 
        private void AddParameters(ImmutableArray<IParameterSymbol> parameters, StringBuilder builder, SemanticModel semanticModel)
        {
            builder.AppendJoinedValues(", ", parameters, (parameter, builder) =>
            {
                var modifiers = CSharpSyntaxGeneratorInternal.GetParameterModifiers(parameter);
                foreach (var modifier in modifiers)
                {
                    builder.Append(SyntaxFacts.GetText(modifier.Kind()));
                    builder.Append(" ");
                }
 
                AddType(parameter.Type, builder, semanticModel);
                builder.Append($" {parameter.Name.EscapeIdentifier()}");
            });
        }
 
        private static void AddTypeArguments(IMethodSymbol symbol, StringBuilder builder)
        {
            if (symbol.TypeArguments.Length <= 0)
            {
                return;
            }
 
            builder.Append('<');
            builder.AppendJoinedValues(", ", symbol.TypeArguments, static (symbol, builder) => builder.Append(symbol.Name.EscapeIdentifier()));
            builder.Append('>');
        }
 
        private void AddType(ITypeSymbol symbol, StringBuilder builder, SemanticModel semanticModel)
        {
            builder.Append(symbol.ToMinimalDisplayString(semanticModel, Position));
        }
    }
}