File: SignatureHelp\CommonSignatureHelpUtilities.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;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Operations;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.SignatureHelp;
 
internal static class CommonSignatureHelpUtilities
{
    internal static SignatureHelpState? GetSignatureHelpState<TArgumentList>(
        TArgumentList argumentList,
        int position,
        Func<TArgumentList, SyntaxToken> getOpenToken,
        Func<TArgumentList, SyntaxToken> getCloseToken,
        Func<TArgumentList, SyntaxNodeOrTokenList> getArgumentsWithSeparators,
        Func<TArgumentList, IEnumerable<string?>> getArgumentNames)
        where TArgumentList : SyntaxNode
    {
        if (TryGetCurrentArgumentIndex(argumentList, position, getOpenToken, getCloseToken, getArgumentsWithSeparators, out var argumentIndex))
        {
            var argumentNames = getArgumentNames(argumentList).ToImmutableArray();
            var argumentCount = argumentNames.Length;
 
            return new SignatureHelpState(
                argumentIndex,
                argumentCount,
                argumentIndex < argumentCount ? argumentNames[argumentIndex] : null,
                argumentNames.WhereNotNull().ToImmutableArray());
        }
 
        return null;
    }
 
    private static bool TryGetCurrentArgumentIndex<TArgumentList>(
        TArgumentList argumentList,
        int position,
        Func<TArgumentList, SyntaxToken> getOpenToken,
        Func<TArgumentList, SyntaxToken> getCloseToken,
        Func<TArgumentList, SyntaxNodeOrTokenList> getArgumentsWithSeparators,
        out int index) where TArgumentList : SyntaxNode
    {
        index = 0;
        if (position < getOpenToken(argumentList).Span.End)
            return false;
 
        var closeToken = getCloseToken(argumentList);
        if (!closeToken.IsMissing && position > closeToken.SpanStart)
            return false;
 
        foreach (var element in getArgumentsWithSeparators(argumentList))
        {
            if (element.IsToken && position >= element.Span.End)
                index++;
        }
 
        return true;
    }
 
    internal static TextSpan GetSignatureHelpSpan<TArgumentList>(
        TArgumentList argumentList,
        Func<TArgumentList, SyntaxToken> getCloseToken)
        where TArgumentList : SyntaxNode
    {
        return GetSignatureHelpSpan(argumentList, argumentList.GetRequiredParent().SpanStart, getCloseToken);
    }
 
    internal static TextSpan GetSignatureHelpSpan<TArgumentList>(
        TArgumentList argumentList,
        int start,
        Func<TArgumentList, SyntaxToken> getCloseToken)
        where TArgumentList : SyntaxNode
    {
        var closeToken = getCloseToken(argumentList);
        if (closeToken.RawKind != 0 && !closeToken.IsMissing)
        {
            return TextSpan.FromBounds(start, closeToken.SpanStart);
        }
 
        // Missing close paren, the span is up to the start of the next token.
        var lastToken = argumentList.GetLastToken();
        var nextToken = lastToken.GetNextToken();
        if (nextToken.RawKind == 0)
        {
            nextToken = argumentList.AncestorsAndSelf().Last().GetLastToken(includeZeroWidth: true);
        }
 
        return TextSpan.FromBounds(start, nextToken.SpanStart);
    }
 
    internal static bool TryGetSyntax<TSyntax>(
        SyntaxNode root,
        int position,
        ISyntaxFactsService syntaxFacts,
        SignatureHelpTriggerReason triggerReason,
        Func<SyntaxToken, bool> isTriggerToken,
        Func<TSyntax, SyntaxToken, bool> isArgumentListToken,
        CancellationToken cancellationToken,
        [NotNullWhen(true)] out TSyntax? expression)
        where TSyntax : SyntaxNode
    {
        var token = root.FindTokenOnLeftOfPosition(position);
        if (triggerReason == SignatureHelpTriggerReason.TypeCharCommand)
        {
            if (isTriggerToken(token) &&
                !syntaxFacts.IsInNonUserCode(root.SyntaxTree, position, cancellationToken))
            {
                expression = token.GetAncestor<TSyntax>();
                return expression != null;
            }
        }
        else if (triggerReason == SignatureHelpTriggerReason.InvokeSignatureHelpCommand)
        {
            expression = token.Parent?.GetAncestorsOrThis<TSyntax>().SkipWhile(syntax => !isArgumentListToken(syntax, token)).FirstOrDefault();
            return expression != null;
        }
        else if (triggerReason == SignatureHelpTriggerReason.RetriggerCommand)
        {
            if (!syntaxFacts.IsInNonUserCode(root.SyntaxTree, position, cancellationToken) ||
                syntaxFacts.IsEntirelyWithinStringOrCharOrNumericLiteral(root.SyntaxTree, position, cancellationToken))
            {
                expression = token.Parent?.AncestorsAndSelf()
                    .TakeWhile(n => !syntaxFacts.IsAnonymousFunctionExpression(n))
                    .OfType<TSyntax>()
                    .SkipWhile(syntax => !isArgumentListToken(syntax, token))
                    .FirstOrDefault();
                return expression != null;
            }
        }
 
        expression = null;
        return false;
    }
 
    public static async Task<ImmutableArray<IMethodSymbol>> GetCollectionInitializerAddMethodsAsync(
        Document document, SyntaxNode initializer, MemberDisplayOptions options, CancellationToken cancellationToken)
    {
        if (initializer is not { Parent: not null })
            return default;
 
        var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
        var compilation = semanticModel.Compilation;
        var ienumerableType = compilation.GetTypeByMetadataName(typeof(IEnumerable).FullName!);
        if (ienumerableType == null)
            return default;
 
        // get the regular signature help items
        var parentOperation = semanticModel.GetOperation(initializer.Parent, cancellationToken) as IObjectOrCollectionInitializerOperation;
        var parentType = parentOperation?.Type;
        if (parentType == null)
            return default;
 
        if (!parentType.AllInterfaces.Contains(ienumerableType))
            return default;
 
        var position = initializer.SpanStart;
        var addSymbols = semanticModel.LookupSymbols(
            position, parentType, WellKnownMemberNames.CollectionInitializerAddMethodName, includeReducedExtensionMethods: true);
 
        var addMethods = addSymbols.OfType<IMethodSymbol>()
                                   .Where(m => m.Parameters.Length >= 1)
                                   .ToImmutableArray()
                                   .FilterToVisibleAndBrowsableSymbols(options.HideAdvancedMembers, semanticModel.Compilation)
                                   .Sort(semanticModel, position);
 
        return addMethods;
    }
}