| File: Completion\KeywordRecommenders\InKeywordRecommender.cs | Web Access |
| Project: 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.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 sealed class InKeywordRecommender() : AbstractSyntacticSingleKeywordRecommender(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; } }