File: Completion\KeywordRecommenders\InKeywordRecommender.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.Threading;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Shared.Extensions;
 
namespace Microsoft.CodeAnalysis.CSharp.Completion.KeywordRecommenders;
 
internal class InKeywordRecommender : AbstractSyntacticSingleKeywordRecommender
{
    public InKeywordRecommender()
        : base(SyntaxKind.InKeyword)
    {
    }
 
    protected override bool IsValidContext(int position, CSharpSyntaxContext context, CancellationToken cancellationToken)
    {
        var syntaxTree = context.SyntaxTree;
        return
            IsValidContextInForEachClause(context) ||
            IsValidContextInFromClause(context, cancellationToken) ||
            IsValidContextInJoinClause(context, cancellationToken) ||
            IsInParameterModifierContext(position, context) ||
            syntaxTree.IsAnonymousMethodParameterModifierContext(position, context.LeftToken) ||
            syntaxTree.IsPossibleLambdaParameterModifierContext(position, context.LeftToken, cancellationToken) ||
            context.TargetToken.IsConstructorOrMethodParameterArgumentContext() ||
            context.TargetToken.IsTypeParameterVarianceContext();
    }
 
    private static bool IsInParameterModifierContext(int position, CSharpSyntaxContext context)
    {
        if (context.SyntaxTree.IsParameterModifierContext(
                position, context.LeftToken, includeOperators: true, out var parameterIndex, out var previousModifier))
        {
            if (previousModifier is SyntaxKind.None or SyntaxKind.ScopedKeyword)
            {
                return true;
            }
 
            if (previousModifier == SyntaxKind.ThisKeyword &&
                parameterIndex == 0 &&
                context.SyntaxTree.IsPossibleExtensionMethodContext(context.LeftToken))
            {
                return true;
            }
        }
 
        return false;
    }
 
    private static bool IsValidContextInForEachClause(CSharpSyntaxContext context)
    {
        // cases:
        //   foreach (var v |
        //   foreach (var v i|
        //   foreach (var (x, y) |
 
        var token = context.TargetToken;
 
        if (token.Kind() == SyntaxKind.IdentifierToken)
        {
            if (token.Parent is ForEachStatementSyntax statement && token == statement.Identifier)
            {
                return true;
            }
        }
        else if (token.Kind() == SyntaxKind.CloseParenToken)
        {
            var statement = token.GetAncestor<ForEachVariableStatementSyntax>();
            if (statement != null && token.Span.End == statement.Variable.Span.End)
            {
                return true;
            }
        }
 
        return false;
    }
 
    private static bool IsValidContextInFromClause(CSharpSyntaxContext context, CancellationToken cancellationToken)
    {
        var token = context.TargetToken;
 
        if (token.Kind() == SyntaxKind.IdentifierToken)
        {
            // case:
            //   from x |
            if (token.GetPreviousToken(includeSkipped: true).IsKindOrHasMatchingText(SyntaxKind.FromKeyword))
            {
                var typeSyntax = token.Parent as TypeSyntax;
                if (!typeSyntax.IsPotentialTypeName(context.SemanticModel, cancellationToken))
                {
                    return true;
                }
            }
 
            if (token.Parent is FromClauseSyntax fromClause)
            {
                // case:
                //   from int x |
                if (token == fromClause.Identifier && fromClause.Type != null)
                {
                    return true;
                }
            }
        }
 
        return false;
    }
 
    private static bool IsValidContextInJoinClause(CSharpSyntaxContext context, CancellationToken cancellationToken)
    {
        var token = context.TargetToken;
 
        if (token.Kind() == SyntaxKind.IdentifierToken)
        {
            var joinClause = token.Parent?.FirstAncestorOrSelf<JoinClauseSyntax>();
            if (joinClause != null)
            {
                // case:
                //   join int x |
                if (token == joinClause.Identifier && joinClause.Type != null)
                {
                    return true;
                }
 
                // case:
                //   join x |
                if (joinClause.Type != null &&
                    joinClause.Type is IdentifierNameSyntax joinIdentifier &&
                    token == joinIdentifier.Identifier &&
                    !joinClause.Type.IsPotentialTypeName(context.SemanticModel, cancellationToken))
                {
                    return true;
                }
            }
        }
 
        return false;
    }
}