File: src\Workspaces\SharedUtilitiesAndExtensions\Workspace\CSharp\Extensions\ContextQuery\SyntaxTreeExtensions.cs
Web Access
Project: src\src\CodeStyle\CSharp\CodeFixes\Microsoft.CodeAnalysis.CSharp.CodeStyle.Fixes.csproj (Microsoft.CodeAnalysis.CSharp.CodeStyle.Fixes)
// 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.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.CSharp.Utilities;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Roslyn.Utilities;
 
#pragma warning disable IDE0060 // Remove unused parameter - Majority of extension methods in this file have an unused 'SyntaxTree' this parameter for consistency with other Context related extension methods.
 
namespace Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery;
 
internal static partial class SyntaxTreeExtensions
{
    private static readonly ISet<SyntaxKind> s_validLocalFunctionModifiers = new HashSet<SyntaxKind>(SyntaxFacts.EqualityComparer)
        {
            SyntaxKind.ExternKeyword,
            SyntaxKind.StaticKeyword,
            SyntaxKind.AsyncKeyword,
            SyntaxKind.UnsafeKeyword,
        };
 
    public static bool IsAttributeNameContext(this SyntaxTree syntaxTree, int position, CancellationToken cancellationToken)
    {
        var token = syntaxTree.FindTokenOnLeftOfPosition(position, cancellationToken);
        token = token.GetPreviousTokenIfTouchingWord(position);
 
        // cases:
        //   [ |
        if (token.IsKind(SyntaxKind.OpenBracketToken) &&
            token.Parent.IsKind(SyntaxKind.AttributeList))
        {
            return true;
        }
 
        // cases:
        //   [Goo(1), |
        if (token.IsKind(SyntaxKind.CommaToken) &&
            token.Parent.IsKind(SyntaxKind.AttributeList))
        {
            return true;
        }
 
        // cases:
        //   [specifier: |
        if (token.IsKind(SyntaxKind.ColonToken) &&
            token.Parent.IsKind(SyntaxKind.AttributeTargetSpecifier))
        {
            return true;
        }
 
        // cases:
        //   [Namespace.|
        if (token.Parent.IsKind(SyntaxKind.QualifiedName) &&
            token.Parent.IsParentKind(SyntaxKind.Attribute))
        {
            return true;
        }
 
        // cases:
        //   [global::|
        if (token.Parent.IsKind(SyntaxKind.AliasQualifiedName) &&
            token.Parent.IsParentKind(SyntaxKind.Attribute))
        {
            return true;
        }
 
        return false;
    }
 
    public static bool IsGlobalMemberDeclarationContext(
        this SyntaxTree syntaxTree,
        int position,
        ISet<SyntaxKind> validModifiers,
        CancellationToken cancellationToken)
    {
        if (!syntaxTree.IsScript())
        {
            return false;
        }
 
        var tokenOnLeftOfPosition = syntaxTree.FindTokenOnLeftOfPosition(position, cancellationToken);
        var token = tokenOnLeftOfPosition.GetPreviousTokenIfTouchingWord(position);
        var parent = token.Parent;
 
        var modifierTokens = syntaxTree.GetPrecedingModifiers(position, cancellationToken);
        if (modifierTokens.IsEmpty())
        {
            if (token.IsKind(SyntaxKind.CloseBracketToken)
                && parent is AttributeListSyntax attributeList
                && !IsGlobalAttributeList(attributeList))
            {
                // Allow empty modifier tokens if we have an attribute list
                parent = attributeList.Parent;
            }
            else
            {
                return false;
            }
        }
 
        if (modifierTokens.IsSubsetOf(validModifiers))
        {
            // the parent is the member
            // the grandparent is the container of the member
            // in interactive, it's possible that there might be an intervening "incomplete" member for partially
            // typed declarations that parse ambiguously. For example, "internal e". It's also possible for a
            // complete member to be parsed based on data after the caret, e.g. "unsafe $$ void L() { }".
            if (parent.IsKind(SyntaxKind.CompilationUnit) ||
               (parent is MemberDeclarationSyntax && parent.IsParentKind(SyntaxKind.CompilationUnit)))
            {
                return true;
            }
        }
 
        return false;
 
        // Local functions
        static bool IsGlobalAttributeList(AttributeListSyntax attributeList)
        {
            if (attributeList.Target is { Identifier.RawKind: var kind })
            {
                return kind is ((int)SyntaxKind.AssemblyKeyword)
                    or ((int)SyntaxKind.ModuleKeyword);
            }
 
            return false;
        }
    }
 
    public static bool IsMemberDeclarationContext(
        this SyntaxTree syntaxTree, int position, SyntaxToken tokenOnLeftOfPosition)
    {
        // cases:
        // class C {
        //   |
 
        // class C {
        //   void Goo() {
        //   }
        //   |
 
        // class C {
        //   int i;
        //   |
 
        // class C {
        //   [Goo]
        //   |
 
        var originalToken = tokenOnLeftOfPosition;
        var token = originalToken;
 
        // If we're touching the right of an identifier, move back to
        // previous token.
        token = token.GetPreviousTokenIfTouchingWord(position);
 
        // class C {
        //   |
        if (token.IsKind(SyntaxKind.OpenBraceToken))
        {
            if (token.Parent is BaseTypeDeclarationSyntax)
            {
                return true;
            }
        }
 
        // class C {
        //   int i;
        //   |
        if (token.IsKind(SyntaxKind.SemicolonToken))
        {
            if (token.Parent is MemberDeclarationSyntax &&
                token.Parent.Parent is BaseTypeDeclarationSyntax)
            {
                return true;
            }
        }
 
        // class A {
        //   class C {}
        //   |
 
        // class C {
        //    void Goo() {
        //    }
        //    |
        if (token.IsKind(SyntaxKind.CloseBraceToken))
        {
            if (token.Parent is BaseTypeDeclarationSyntax &&
                token.Parent.Parent is BaseTypeDeclarationSyntax)
            {
                // after a nested type
                return true;
            }
            else if (token.Parent is AccessorListSyntax)
            {
                // after a property
                return true;
            }
            else if (
                token.Parent.IsKind(SyntaxKind.Block) &&
                token.Parent.Parent is MemberDeclarationSyntax)
            {
                // after a method/operator/etc.
                return true;
            }
        }
 
        // namespace Goo {
        //   [Bar]
        //   |
 
        if (token.IsKind(SyntaxKind.CloseBracketToken) &&
            token.Parent.IsKind(SyntaxKind.AttributeList))
        {
            // attributes belong to a member which itself is in a
            // container.
 
            // the parent is the attribute
            // the grandparent is the owner of the attribute
            // the great-grandparent is the container that the owner is in
            var container = token.Parent.Parent?.Parent;
            if (container is BaseTypeDeclarationSyntax)
            {
                return true;
            }
        }
 
        return false;
    }
 
    public static bool IsMemberDeclarationContext(
        this SyntaxTree syntaxTree,
        int position,
        CSharpSyntaxContext? context,
        ISet<SyntaxKind>? validModifiers,
        ISet<SyntaxKind>? validTypeDeclarations,
        bool canBePartial,
        CancellationToken cancellationToken)
    {
        var typeDecl = context != null
            ? context.ContainingTypeOrEnumDeclaration
            : syntaxTree.GetContainingTypeOrEnumDeclaration(position, cancellationToken);
 
        if (typeDecl == null)
        {
            return false;
        }
 
        validTypeDeclarations ??= SpecializedCollections.EmptySet<SyntaxKind>();
 
        if (!validTypeDeclarations.Contains(typeDecl.Kind()))
        {
            return false;
        }
 
        // Check many of the simple cases first.
        var leftToken = context != null
            ? context.LeftToken
            : syntaxTree.FindTokenOnLeftOfPosition(position, cancellationToken);
 
        var token = context != null
            ? context.TargetToken
            : leftToken.GetPreviousTokenIfTouchingWord(position);
 
        if (token.IsAnyAccessorDeclarationContext(position))
        {
            return false;
        }
 
        if (syntaxTree.IsMemberDeclarationContext(position, leftToken))
        {
            return true;
        }
 
        // A member can also show up after certain types of modifiers
        if (canBePartial &&
            token.IsKindOrHasMatchingText(SyntaxKind.PartialKeyword))
        {
            return true;
        }
 
        var modifierTokens = context?.PrecedingModifiers ?? syntaxTree.GetPrecedingModifiers(position, cancellationToken);
        if (modifierTokens.IsEmpty())
            return false;
 
        validModifiers ??= SpecializedCollections.EmptySet<SyntaxKind>();
 
        if (modifierTokens.IsSubsetOf(validModifiers))
        {
            var member = token.Parent;
            if (token.HasMatchingText(SyntaxKind.AsyncKeyword))
            {
                // rule out async lambdas inside a method
                if (token.GetAncestor<StatementSyntax>() == null)
                {
                    member = token.GetAncestor<MemberDeclarationSyntax>();
                }
            }
 
            // cases:
            // public |
            // async |
            // public async |
            return member != null &&
                member.Parent is BaseTypeDeclarationSyntax;
        }
 
        return false;
    }
 
    public static bool IsLambdaDeclarationContext(
        this SyntaxTree syntaxTree,
        int position,
        SyntaxKind otherModifier,
        CancellationToken cancellationToken)
    {
        var modifierTokens = syntaxTree.GetPrecedingModifiers(position, cancellationToken, out position);
        if (modifierTokens.Count >= 2)
            return false;
 
        if (modifierTokens.Count == 1)
            return modifierTokens.Contains(otherModifier) && IsLambdaDeclarationContext(syntaxTree, position, SyntaxKind.None, cancellationToken);
 
        var leftToken = syntaxTree.FindTokenOnLeftOfPosition(position, cancellationToken);
        return syntaxTree.IsExpressionContext(position, leftToken, attributes: false, cancellationToken);
    }
 
    public static bool IsLocalFunctionDeclarationContext(this SyntaxTree syntaxTree, int position, CancellationToken cancellationToken)
        => IsLocalFunctionDeclarationContext(syntaxTree, position, s_validLocalFunctionModifiers, cancellationToken);
 
    public static bool IsLocalFunctionDeclarationContext(
        this SyntaxTree syntaxTree,
        int position,
        ISet<SyntaxKind> validModifiers,
        CancellationToken cancellationToken)
    {
        var modifierTokens = syntaxTree.GetPrecedingModifiers(position, cancellationToken, out position);
 
        // if we had modifiers, they have to be legal in this context.
        if (!modifierTokens.IsSubsetOf(validModifiers))
            return false;
 
        // if we had modifiers, restart the search at the point prior to them.
        if (modifierTokens.Count > 0)
            return IsLocalFunctionDeclarationContext(syntaxTree, position, validModifiers, cancellationToken);
 
        var leftToken = syntaxTree.FindTokenOnLeftOfPosition(position, cancellationToken);
        var token = leftToken.GetPreviousTokenIfTouchingWord(position);
 
        // if we're after an attribute, restart the check at teh start of the attribute.
        if (token.Kind() == SyntaxKind.CloseBracketToken && token.Parent is AttributeListSyntax)
            return syntaxTree.IsLocalFunctionDeclarationContext(token.Parent.SpanStart, validModifiers, cancellationToken);
 
        if (syntaxTree.IsStatementContext(position, leftToken, cancellationToken))
            return true;
 
        return !syntaxTree.IsScript() && syntaxTree.IsGlobalStatementContext(position, cancellationToken);
    }
 
    public static bool IsTypeDeclarationContext(
        this SyntaxTree syntaxTree, int position, SyntaxToken tokenOnLeftOfPosition, CancellationToken cancellationToken)
    {
        // cases:
        // root: |
 
        // extern alias a;
        // |
 
        // using Goo;
        // |
 
        // using Goo = Bar;
        // |
 
        // namespace N {}
        // |
 
        // namespace N {
        // |
 
        // class C {}
        // |
 
        // class C {
        // |
 
        // class C {
        //   void Goo() {
        //   }
        //   |
 
        // class C {
        //   int i;
        //   |
 
        // class C {
        //   [Goo]
        //   |
 
        // (all the class cases apply to structs, interfaces and records).
 
        var originalToken = tokenOnLeftOfPosition;
        var token = originalToken;
 
        // If we're touching the right of an identifier, move back to
        // previous token.
        token = token.GetPreviousTokenIfTouchingWord(position);
 
        // a type decl can't come before usings/externs
        var nextToken = originalToken.GetNextToken(includeSkipped: true);
        if (nextToken.IsUsingOrExternKeyword() ||
            (nextToken.Kind() == SyntaxKind.GlobalKeyword && nextToken.GetAncestor<UsingDirectiveSyntax>()?.GlobalKeyword == nextToken))
        {
            return false;
        }
 
        // root: |
        if (token.IsKind(SyntaxKind.None))
        {
            // root namespace
 
            // a type decl can't come before usings/externs
            if (syntaxTree.GetRoot(cancellationToken) is CompilationUnitSyntax compilationUnit &&
                (compilationUnit.Externs.Count > 0 || compilationUnit.Usings.Count > 0))
            {
                return false;
            }
 
            return true;
        }
 
        if (token.IsKind(SyntaxKind.OpenBraceToken) && token.Parent is NamespaceDeclarationSyntax or TypeDeclarationSyntax)
            return true;
 
        // extern alias a;
        // |
 
        // using Goo;
        // |
 
        // class C {
        //   int i;
        //   |
 
        // namespace NS;
        // |
        if (token.IsKind(SyntaxKind.SemicolonToken))
        {
            if (token.Parent is (kind: SyntaxKind.ExternAliasDirective or SyntaxKind.UsingDirective))
            {
                return true;
            }
            else if (token.Parent is MemberDeclarationSyntax)
            {
                return true;
            }
        }
 
        // class C {}
        // |
 
        // namespace N {}
        // |
 
        // class C {
        //    void Goo() {
        //    }
        //    |
        if (token.IsKind(SyntaxKind.CloseBraceToken))
        {
            if (token.Parent is BaseTypeDeclarationSyntax)
            {
                return true;
            }
            else if (token.Parent.IsKind(SyntaxKind.NamespaceDeclaration))
            {
                return true;
            }
            else if (token.Parent is AccessorListSyntax)
            {
                return true;
            }
            else if (
                token.Parent.IsKind(SyntaxKind.Block) &&
                token.Parent.Parent is MemberDeclarationSyntax)
            {
                return true;
            }
        }
 
        // namespace Goo {
        //   [Bar]
        //   |
 
        // namespace NS;
        // [Attr]
        // |
 
        if (token.IsKind(SyntaxKind.CloseBracketToken) &&
            token.Parent.IsKind(SyntaxKind.AttributeList))
        {
            // assembly attributes belong to the containing compilation unit
            if (token.Parent.IsParentKind(SyntaxKind.CompilationUnit))
                return true;
 
            // other attributes belong to a member which itself is in a
            // container.
 
            // the parent is the attribute
            // the grandparent is the owner of the attribute
            // the great-grandparent is the container that the owner is in
            var container = token.Parent?.Parent?.Parent;
            if (container is CompilationUnitSyntax or BaseNamespaceDeclarationSyntax or TypeDeclarationSyntax)
                return true;
        }
 
        return false;
    }
 
    public static bool IsTypeDeclarationContext(
        this SyntaxTree syntaxTree,
        int position,
        CSharpSyntaxContext? context,
        ISet<SyntaxKind>? validModifiers,
        ISet<SyntaxKind>? validTypeDeclarations,
        bool canBePartial,
        CancellationToken cancellationToken)
    {
        // We only allow nested types inside a class, struct, or interface, not inside a
        // an enum.
        var typeDecl = context != null
            ? context.ContainingTypeDeclaration
            : syntaxTree.GetContainingTypeDeclaration(position, cancellationToken);
 
        validTypeDeclarations ??= SpecializedCollections.EmptySet<SyntaxKind>();
 
        if (typeDecl != null)
        {
            if (!validTypeDeclarations.Contains(typeDecl.Kind()))
            {
                return false;
            }
        }
 
        // Check many of the simple cases first.
        var leftToken = context != null
            ? context.LeftToken
            : syntaxTree.FindTokenOnLeftOfPosition(position, cancellationToken);
 
        // If we're touching the right of an identifier, move back to
        // previous token.
        var token = context != null
            ? context.TargetToken
            : leftToken.GetPreviousTokenIfTouchingWord(position);
 
        if (token.IsAnyAccessorDeclarationContext(position))
        {
            return false;
        }
 
        if (syntaxTree.IsTypeDeclarationContext(position, leftToken, cancellationToken))
        {
            return true;
        }
 
        // A type can also show up after certain types of modifiers
        if (canBePartial &&
            token.IsKindOrHasMatchingText(SyntaxKind.PartialKeyword))
        {
            return true;
        }
 
        // using directive is never a type declaration context
        if (token.GetAncestor<UsingDirectiveSyntax>() is not null)
        {
            return false;
        }
 
        var modifierTokens = context?.PrecedingModifiers ?? syntaxTree.GetPrecedingModifiers(position, cancellationToken);
        if (modifierTokens.IsEmpty())
            return false;
 
        validModifiers ??= SpecializedCollections.EmptySet<SyntaxKind>();
 
        if (modifierTokens.IsProperSubsetOf(validModifiers))
        {
            // the parent is the member
            // the grandparent is the container of the member
            var container = token.Parent?.Parent;
 
            // ref $$
            // readonly ref $$
            if (container is IncompleteMemberSyntax incompleteMember)
                return incompleteMember.Type.IsKind(SyntaxKind.RefType);
 
            if (container is CompilationUnitSyntax or BaseNamespaceDeclarationSyntax or TypeDeclarationSyntax)
                return true;
 
            if (container is VariableDeclarationSyntax && modifierTokens.Contains(SyntaxKind.FileKeyword))
                return true;
        }
 
        return false;
    }
 
    public static bool IsNamespaceContext(
        this SyntaxTree syntaxTree,
        int position,
        CancellationToken cancellationToken,
        SemanticModel? semanticModel = null)
    {
        // first do quick exit check
        if (syntaxTree.IsInNonUserCode(position, cancellationToken) ||
            syntaxTree.IsRightOfDotOrArrow(position, cancellationToken))
        {
            return false;
        }
 
        var token = syntaxTree.FindTokenOnLeftOfPosition(position, cancellationToken)
                              .GetPreviousTokenIfTouchingWord(position);
 
        // global::
        if (token.IsKind(SyntaxKind.ColonColonToken) &&
            token.GetPreviousToken(includeSkipped: true).IsKind(SyntaxKind.GlobalKeyword))
        {
            return true;
        }
 
        // using |
        // but not:
        // using | = Bar
 
        // Note: we take care of the using alias case in the IsTypeContext
        // call below.
 
        if (token.IsKind(SyntaxKind.UsingKeyword))
        {
            var usingDirective = token.GetAncestor<UsingDirectiveSyntax>();
            if (usingDirective != null)
            {
                if (token.GetNextToken(includeSkipped: true).Kind() != SyntaxKind.EqualsToken &&
                    usingDirective.Alias == null)
                {
                    return true;
                }
            }
        }
 
        // using static |
        // using static unsafe |
        if (token.IsStaticKeywordContextInUsingDirective())
        {
            return true;
        }
 
        // if it is not using directive location, most of places where 
        // type can appear, namespace can appear as well
        return syntaxTree.IsTypeContext(position, cancellationToken, semanticModel);
    }
 
    public static bool IsNamespaceDeclarationNameContext(this SyntaxTree syntaxTree, int position, CancellationToken cancellationToken)
    {
        if (syntaxTree.IsScript() || syntaxTree.IsInNonUserCode(position, cancellationToken))
            return false;
 
        var token = syntaxTree.FindTokenOnLeftOfPosition(position, cancellationToken)
                              .GetPreviousTokenIfTouchingWord(position);
        if (token == default)
            return false;
 
        var declaration = token.GetAncestor<BaseNamespaceDeclarationSyntax>();
        if (declaration?.NamespaceKeyword == token)
            return true;
 
        return declaration?.Name.Span.IntersectsWith(position) == true;
    }
 
    public static bool IsPartialTypeDeclarationNameContext(this SyntaxTree syntaxTree, int position, CancellationToken cancellationToken, [NotNullWhen(true)] out TypeDeclarationSyntax? declarationSyntax)
    {
        if (!syntaxTree.IsInNonUserCode(position, cancellationToken))
        {
            var token = syntaxTree.FindTokenOnLeftOfPosition(position, cancellationToken)
                                  .GetPreviousTokenIfTouchingWord(position);
 
            if (token.Kind()
                    is SyntaxKind.ClassKeyword
                    or SyntaxKind.StructKeyword
                    or SyntaxKind.InterfaceKeyword &&
                token.GetPreviousToken().IsKind(SyntaxKind.PartialKeyword))
            {
                declarationSyntax = token.GetAncestor<TypeDeclarationSyntax>();
                return declarationSyntax != null && declarationSyntax.Keyword == token;
            }
        }
 
        declarationSyntax = null;
        return false;
    }
 
    public static bool IsDefinitelyNotTypeContext(this SyntaxTree syntaxTree, int position, CancellationToken cancellationToken)
    {
        if (syntaxTree.IsPreProcessorDirectiveContext(position, cancellationToken))
            return true;
 
        return
            syntaxTree.IsInNonUserCode(position, cancellationToken) ||
            syntaxTree.IsRightOfDotOrArrow(position, cancellationToken);
    }
 
    public static bool IsTypeContext(
        this SyntaxTree syntaxTree, int position, CancellationToken cancellationToken, SemanticModel? semanticModel = null)
    {
        // first do quick exit check
        if (syntaxTree.IsDefinitelyNotTypeContext(position, cancellationToken))
            return false;
 
        // okay, now it is a case where we can't use parse tree (valid or error recovery) to
        // determine whether it is a right place to put type. use lex based one Cyrus created.
 
        var tokenOnLeftOfPosition = syntaxTree.FindTokenOnLeftOfPosition(position, cancellationToken);
        return
            syntaxTree.IsAfterKeyword(position, SyntaxKind.ConstKeyword, cancellationToken) ||
            syntaxTree.IsAfterKeyword(position, SyntaxKind.RefKeyword, cancellationToken) ||
            syntaxTree.IsAfterKeyword(position, SyntaxKind.ReadOnlyKeyword, cancellationToken) ||
            syntaxTree.IsAfterKeyword(position, SyntaxKind.CaseKeyword, cancellationToken) ||
            syntaxTree.IsAfterKeyword(position, SyntaxKind.EventKeyword, cancellationToken) ||
            syntaxTree.IsAfterKeyword(position, SyntaxKind.StackAllocKeyword, cancellationToken) ||
            syntaxTree.IsAttributeNameContext(position, cancellationToken) ||
            syntaxTree.IsBaseClassOrInterfaceContext(position, cancellationToken) ||
            syntaxTree.IsCatchVariableDeclarationContext(position, cancellationToken) ||
            syntaxTree.IsDefiniteCastTypeContext(position, tokenOnLeftOfPosition) ||
            syntaxTree.IsDelegateReturnTypeContext(position, tokenOnLeftOfPosition) ||
            syntaxTree.IsExpressionContext(position, tokenOnLeftOfPosition, attributes: true, cancellationToken: cancellationToken, semanticModel: semanticModel) ||
            syntaxTree.IsPrimaryFunctionExpressionContext(position, tokenOnLeftOfPosition) ||
            syntaxTree.IsGenericTypeArgumentContext(position, tokenOnLeftOfPosition, cancellationToken, semanticModel) ||
            syntaxTree.IsFunctionPointerTypeArgumentContext(position, tokenOnLeftOfPosition, cancellationToken) ||
            syntaxTree.IsFixedVariableDeclarationContext(position, tokenOnLeftOfPosition) ||
            syntaxTree.IsImplicitOrExplicitOperatorTypeContext(position, tokenOnLeftOfPosition) ||
            syntaxTree.IsIsOrAsTypeContext(position, tokenOnLeftOfPosition) ||
            syntaxTree.IsLocalVariableDeclarationContext(position, tokenOnLeftOfPosition, cancellationToken) ||
            syntaxTree.IsObjectCreationTypeContext(position, tokenOnLeftOfPosition, cancellationToken) ||
            syntaxTree.IsParameterTypeContext(position, tokenOnLeftOfPosition) ||
            syntaxTree.IsPossibleLambdaOrAnonymousMethodParameterTypeContext(position, tokenOnLeftOfPosition, cancellationToken) ||
            syntaxTree.IsStatementContext(position, tokenOnLeftOfPosition, cancellationToken) ||
            syntaxTree.IsGlobalStatementContext(position, cancellationToken) ||
            syntaxTree.IsTypeParameterConstraintContext(position, tokenOnLeftOfPosition) ||
            syntaxTree.IsUsingAliasTypeContext(position, cancellationToken) ||
            syntaxTree.IsUsingStaticContext(position, cancellationToken) ||
            syntaxTree.IsGlobalMemberDeclarationContext(position, SyntaxKindSet.AllGlobalMemberModifiers, cancellationToken) ||
            syntaxTree.IsPossibleTupleContext(tokenOnLeftOfPosition, position) ||
            syntaxTree.IsMemberDeclarationContext(
                position,
                context: null,
                validModifiers: SyntaxKindSet.AllMemberModifiers,
                validTypeDeclarations: SyntaxKindSet.ClassInterfaceStructRecordTypeDeclarations,
                canBePartial: false,
                cancellationToken: cancellationToken) ||
            syntaxTree.IsLocalFunctionDeclarationContext(position, cancellationToken);
    }
 
    public static bool IsBaseClassOrInterfaceContext(this SyntaxTree syntaxTree, int position, CancellationToken cancellationToken)
    {
        // class C : |
        // class C : Bar, |
        // NOT enum E : |
 
        var token = syntaxTree.FindTokenOnLeftOfPosition(position, cancellationToken);
        token = token.GetPreviousTokenIfTouchingWord(position);
 
        if (token.Kind() is SyntaxKind.ColonToken or SyntaxKind.CommaToken &&
            token.Parent is BaseListSyntax { Parent: not EnumDeclarationSyntax })
        {
            return true;
        }
 
        return false;
    }
 
    public static bool IsUsingAliasTypeContext(this SyntaxTree syntaxTree, int position, CancellationToken cancellationToken)
    {
        // using Goo = |
 
        var token = syntaxTree.FindTokenOnLeftOfPosition(position, cancellationToken);
        token = token.GetPreviousTokenIfTouchingWord(position);
 
        if (token.IsKind(SyntaxKind.EqualsToken) &&
            token.GetAncestor<UsingDirectiveSyntax>() != null)
        {
            return true;
        }
 
        return false;
    }
 
    public static bool IsUsingStaticContext(this SyntaxTree syntaxTree, int position, CancellationToken cancellationToken)
    {
        // using static |
        // using static unsafe |
 
        var token = syntaxTree.FindTokenOnLeftOfPosition(position, cancellationToken);
        token = token.GetPreviousTokenIfTouchingWord(position);
 
        return token.IsStaticKeywordContextInUsingDirective();
    }
 
    public static bool IsTypeArgumentOfConstraintClause(
        this SyntaxTree syntaxTree, int position, CancellationToken cancellationToken)
    {
        // cases:
        //   where |
        //   class Goo<T> : Object where |
 
        var token = syntaxTree.FindTokenOnLeftOfPosition(position, cancellationToken);
        token = token.GetPreviousTokenIfTouchingWord(position);
 
        if (token.IsKind(SyntaxKind.WhereKeyword) &&
            token.Parent.IsKind(SyntaxKind.TypeParameterConstraintClause))
        {
            return true;
        }
 
        if (token.IsKind(SyntaxKind.IdentifierToken) &&
            token.HasMatchingText(SyntaxKind.WhereKeyword) &&
            token.Parent.IsKind(SyntaxKind.IdentifierName) &&
            token.Parent.IsParentKind(SyntaxKind.SimpleBaseType) &&
            token.Parent.Parent.IsParentKind(SyntaxKind.BaseList))
        {
            return true;
        }
 
        return false;
    }
 
    public static bool IsTypeParameterConstraintStartContext(
        this SyntaxTree syntaxTree, int position, SyntaxToken tokenOnLeftOfPosition)
    {
        // cases:
        //   where T : |
 
        var token = tokenOnLeftOfPosition;
        token = token.GetPreviousTokenIfTouchingWord(position);
 
        if (token.IsKind(SyntaxKind.ColonToken) &&
            token.GetPreviousToken(includeSkipped: true).IsKind(SyntaxKind.IdentifierToken) &&
            token.GetPreviousToken(includeSkipped: true).GetPreviousToken().IsKind(SyntaxKind.WhereKeyword))
        {
            return true;
        }
 
        return false;
    }
 
    public static bool IsTypeParameterConstraintContext(this SyntaxTree syntaxTree, int position, SyntaxToken tokenOnLeftOfPosition)
    {
        if (syntaxTree.IsTypeParameterConstraintStartContext(position, tokenOnLeftOfPosition))
        {
            return true;
        }
 
        var token = tokenOnLeftOfPosition;
        token = token.GetPreviousTokenIfTouchingWord(position);
 
        // Can't come after new()
        //
        //    where T : |
        //    where T : class, |
        //    where T : struct, |
        //    where T : Goo, |
        if (token.IsKind(SyntaxKind.CommaToken) &&
            token.Parent is TypeParameterConstraintClauseSyntax constraintClause)
        {
            // Check if there's a 'new()' constraint.  If there isn't, or we're before it, then
            // this is a type parameter constraint context. 
            var firstConstructorConstraint = constraintClause.Constraints.FirstOrDefault(t => t is ConstructorConstraintSyntax);
            if (firstConstructorConstraint == null || firstConstructorConstraint.SpanStart > token.Span.End)
            {
                return true;
            }
        }
 
        return false;
    }
 
    public static bool IsTypeOfExpressionContext(this SyntaxTree syntaxTree, int position, SyntaxToken tokenOnLeftOfPosition)
    {
        var token = tokenOnLeftOfPosition.GetPreviousTokenIfTouchingWord(position);
 
        if (token.IsKind(SyntaxKind.OpenParenToken) && token.Parent.IsKind(SyntaxKind.TypeOfExpression))
        {
            return true;
        }
 
        return false;
    }
 
    public static bool IsDefaultExpressionContext(this SyntaxTree syntaxTree, int position, SyntaxToken tokenOnLeftOfPosition)
    {
        var token = tokenOnLeftOfPosition.GetPreviousTokenIfTouchingWord(position);
 
        if (token.IsKind(SyntaxKind.OpenParenToken) && token.Parent.IsKind(SyntaxKind.DefaultExpression))
        {
            return true;
        }
 
        return false;
    }
 
    public static bool IsSizeOfExpressionContext(
        this SyntaxTree syntaxTree, int position, SyntaxToken tokenOnLeftOfPosition)
    {
        var token = tokenOnLeftOfPosition.GetPreviousTokenIfTouchingWord(position);
 
        if (token.IsKind(SyntaxKind.OpenParenToken) && token.Parent.IsKind(SyntaxKind.SizeOfExpression))
        {
            return true;
        }
 
        return false;
    }
 
    public static bool IsFunctionPointerTypeArgumentContext(
        this SyntaxTree syntaxTree,
        int position,
        SyntaxToken tokenOnLeftOfPosition,
        CancellationToken cancellationToken)
    {
        var token = tokenOnLeftOfPosition;
        token = token.GetPreviousTokenIfTouchingWord(position);
 
        switch (token.Kind())
        {
            case SyntaxKind.LessThanToken:
            case SyntaxKind.CommaToken:
                return token.Parent.IsKind(SyntaxKind.FunctionPointerParameterList);
        }
 
        return token switch
        {
            // ref modifiers
            { Parent.RawKind: (int)SyntaxKind.FunctionPointerParameter } => true,
            // Regular type specifiers
            { Parent: TypeSyntax { Parent.RawKind: (int)SyntaxKind.FunctionPointerParameter } } => true,
            _ => false
        };
    }
 
    public static bool IsGenericConstraintContext(this SyntaxTree syntaxTree, SyntaxToken targetToken)
        => targetToken.Parent.IsKind(SyntaxKind.TypeParameterConstraintClause) &&
           targetToken.Kind() is SyntaxKind.ColonToken or SyntaxKind.CommaToken;
 
    public static bool IsGenericTypeArgumentContext(
        this SyntaxTree syntaxTree,
        int position,
        SyntaxToken tokenOnLeftOfPosition,
        CancellationToken cancellationToken,
        SemanticModel? semanticModelOpt = null)
    {
        // cases: 
        //    Goo<|
        //    Goo<Bar,|
        //    Goo<Bar<Baz<int[],|
        var token = tokenOnLeftOfPosition;
        token = token.GetPreviousTokenIfTouchingWord(position);
 
        if (token.Kind() is not SyntaxKind.LessThanToken and not SyntaxKind.CommaToken)
        {
            return false;
        }
 
        if (token.Parent is TypeArgumentListSyntax)
        {
            // Easy case, it was known to be a generic name, so this is a type argument context.
            return true;
        }
 
        if (!syntaxTree.IsInPartiallyWrittenGeneric(position, cancellationToken, out var nameToken))
        {
            return false;
        }
 
        if (nameToken.Parent is not NameSyntax name)
        {
            return false;
        }
 
        // Looks viable!  If they provided a binding, then check if it binds properly to
        // an actual generic entity.
        if (semanticModelOpt == null)
        {
            // No binding.  Just make the decision based on the syntax tree.
            return true;
        }
 
        // '?' is syntactically ambiguous in incomplete top-level statements:
        //
        // T ? goo<| 
        //
        // Might be an incomplete conditional expression or an incomplete declaration of a method returning a nullable type.
        // Bind T to see if it is a type. If it is we don't show signature help.
        if (name.IsParentKind(SyntaxKind.LessThanExpression) &&
            name.Parent?.Parent is ConditionalExpressionSyntax conditional &&
            conditional.IsParentKind(SyntaxKind.ExpressionStatement) &&
            conditional.Parent.IsParentKind(SyntaxKind.GlobalStatement))
        {
            var conditionOrType = semanticModelOpt.GetSymbolInfo(conditional.Condition, cancellationToken);
            if (conditionOrType.GetBestOrAllSymbols().FirstOrDefault() is { Kind: SymbolKind.NamedType })
            {
                return false;
            }
        }
 
        // We have reached the expression:
        //
        // goo.Baz<|
        //
        // This could either be an incomplete generic type or method, or a binary less than operator
        // To ensure that we are in the generic case, we need to match at least one generic method or type,
        // and all other candidates to be types or methods.
        var symbols = semanticModelOpt.LookupName(nameToken, cancellationToken);
        if (symbols.Length == 0)
            return false;
 
        var anyGeneric = symbols.Any(static s =>
        {
            return s switch
            {
                INamedTypeSymbol nt => nt.Arity > 0,
                IMethodSymbol m => m.Arity > 0,
                _ => false,
            };
        });
 
        if (!anyGeneric)
            return false;
 
        return symbols.All(static s => s is INamedTypeSymbol or IMethodSymbol);
    }
 
    public static bool IsParameterModifierContext(
        this SyntaxTree syntaxTree,
        int position,
        SyntaxToken tokenOnLeftOfPosition,
        bool includeOperators,
        out int parameterIndex,
        out SyntaxKind previousModifier)
    {
        // cases:
        //   Goo(|
        //   Goo(int i, |
        //   Goo([Bar]|
        var token = tokenOnLeftOfPosition.GetPreviousTokenIfTouchingWord(position);
 
        parameterIndex = -1;
        previousModifier = SyntaxKind.None;
 
        if (token.IsKind(SyntaxKind.OpenParenToken) &&
            token.Parent is ParameterListSyntax parameterList1 &&
            IsSuitableParameterList(parameterList1, includeOperators))
        {
            parameterIndex = 0;
            return true;
        }
 
        if (token.IsKind(SyntaxKind.LessThanToken) && token.Parent.IsKind(SyntaxKind.FunctionPointerParameterList))
        {
            parameterIndex = 0;
            return true;
        }
 
        if (token.IsKind(SyntaxKind.CommaToken) &&
            token.Parent is ParameterListSyntax parameterList2 &&
            IsSuitableParameterList(parameterList2, includeOperators))
        {
            var commaIndex = parameterList2.Parameters.GetWithSeparators().IndexOf(token);
 
            parameterIndex = commaIndex / 2 + 1;
            return true;
        }
 
        if (token.IsKind(SyntaxKind.CommaToken) &&
            token.Parent is FunctionPointerParameterListSyntax funcPtrParamList)
        {
            var commaIndex = funcPtrParamList.Parameters.GetWithSeparators().IndexOf(token);
 
            parameterIndex = commaIndex / 2 + 1;
            return true;
        }
 
        if (token.IsKind(SyntaxKind.CloseBracketToken) &&
            token.Parent.IsKind(SyntaxKind.AttributeList) &&
            token.Parent.Parent is ParameterSyntax parameter3 &&
            parameter3.Parent is ParameterListSyntax parameterList3 &&
            IsSuitableParameterList(parameterList3, includeOperators))
        {
            parameterIndex = parameterList3.Parameters.IndexOf(parameter3);
            return true;
        }
 
        ParameterSyntax? parameter4 = null;
        if (token.Kind() is SyntaxKind.RefKeyword or SyntaxKind.InKeyword or SyntaxKind.ReadOnlyKeyword or SyntaxKind.OutKeyword or SyntaxKind.ThisKeyword or SyntaxKind.ParamsKeyword or SyntaxKind.ScopedKeyword)
        {
            parameter4 = token.Parent as ParameterSyntax;
            previousModifier = token.Kind();
        }
        else if (token.IsKind(SyntaxKind.IdentifierToken) && token.Text == "scoped" && token.Parent is IdentifierNameSyntax scopedIdentifierName)
        {
            parameter4 = scopedIdentifierName.Parent as ParameterSyntax;
            previousModifier = SyntaxKind.ScopedKeyword;
        }
 
        if (parameter4 is { Parent: ParameterListSyntax parameterList4 } &&
            IsSuitableParameterList(parameterList4, includeOperators))
        {
            parameterIndex = parameterList4.Parameters.IndexOf(parameter4);
            return true;
        }
 
        return false;
 
        static bool IsSuitableParameterList(ParameterListSyntax parameterList, bool includeOperators)
            => parameterList.Parent switch
            {
                MethodDeclarationSyntax or LocalFunctionStatementSyntax or ConstructorDeclarationSyntax or DelegateDeclarationSyntax or TypeDeclarationSyntax => true,
                OperatorDeclarationSyntax or ConversionOperatorDeclarationSyntax when includeOperators => true,
                _ => false,
            };
    }
 
    public static bool IsParamsModifierContext(
        this SyntaxTree syntaxTree,
        int position,
        SyntaxToken tokenOnLeftOfPosition,
        CancellationToken cancellationToken)
    {
        if (syntaxTree.IsParameterModifierContext(position, tokenOnLeftOfPosition, includeOperators: false, out _, out var previousModifier) &&
            previousModifier == SyntaxKind.None)
        {
            return true;
        }
 
        if (syntaxTree.IsPossibleLambdaParameterModifierContext(position, tokenOnLeftOfPosition, cancellationToken))
        {
            return true;
        }
 
        var token = tokenOnLeftOfPosition;
        token = token.GetPreviousTokenIfTouchingWord(position);
 
        if (token.Kind() is SyntaxKind.OpenBracketToken or SyntaxKind.CommaToken)
        {
            return token.Parent.IsKind(SyntaxKind.BracketedParameterList);
        }
 
        return false;
    }
 
    public static bool IsDelegateReturnTypeContext(
        this SyntaxTree syntaxTree, int position, SyntaxToken tokenOnLeftOfPosition)
    {
        var token = tokenOnLeftOfPosition;
        token = token.GetPreviousTokenIfTouchingWord(position);
 
        if (token.IsKind(SyntaxKind.DelegateKeyword) &&
            token.Parent.IsKind(SyntaxKind.DelegateDeclaration))
        {
            return true;
        }
 
        return false;
    }
 
    public static bool IsImplicitOrExplicitOperatorTypeContext(
        this SyntaxTree syntaxTree, int position, SyntaxToken tokenOnLeftOfPosition)
    {
        var token = tokenOnLeftOfPosition;
        token = token.GetPreviousTokenIfTouchingWord(position);
 
        if (token.IsKind(SyntaxKind.OperatorKeyword) &&
            token.GetPreviousToken(includeSkipped: true).Kind() is SyntaxKind.ImplicitKeyword or SyntaxKind.ExplicitKeyword)
        {
            return true;
        }
 
        return false;
    }
 
    public static bool IsParameterTypeContext(this SyntaxTree syntaxTree, int position, SyntaxToken tokenOnLeftOfPosition)
    {
        var token = tokenOnLeftOfPosition.GetPreviousTokenIfTouchingWord(position);
        if (syntaxTree.IsParameterModifierContext(position, tokenOnLeftOfPosition, includeOperators: true, out _, out _))
            return true;
 
        // int this[ |
        // int this[int i, |
        if (token.Kind() is SyntaxKind.OpenParenToken or SyntaxKind.OpenBracketToken or SyntaxKind.CommaToken &&
            token.Parent is (kind: SyntaxKind.ParameterList or SyntaxKind.BracketedParameterList))
        {
            return true;
        }
 
        return false;
    }
 
    public static bool IsPossibleExtensionMethodContext(this SyntaxTree syntaxTree, SyntaxToken tokenOnLeftOfPosition)
    {
        var method = tokenOnLeftOfPosition.Parent.GetAncestorOrThis<MethodDeclarationSyntax>();
        var typeDecl = method.GetAncestorOrThis<TypeDeclarationSyntax>();
 
        return method != null && typeDecl != null &&
               typeDecl.IsKind(SyntaxKind.ClassDeclaration) &&
               method.Modifiers.Any(SyntaxKind.StaticKeyword) &&
               typeDecl.Modifiers.Any(SyntaxKind.StaticKeyword);
    }
 
    public static bool IsPossibleLambdaParameterModifierContext(
        this SyntaxTree syntaxTree, int position, SyntaxToken tokenOnLeftOfPosition, CancellationToken cancellationToken)
    {
        var token = tokenOnLeftOfPosition;
        token = token.GetPreviousTokenIfTouchingWord(position);
 
        if (token.Kind() is SyntaxKind.OpenParenToken or SyntaxKind.CommaToken)
        {
            if (token.Parent.IsKind(SyntaxKind.ParameterList) &&
                token.Parent.IsParentKind(SyntaxKind.ParenthesizedLambdaExpression))
            {
                return true;
            }
 
            // TODO(cyrusn): Tie into semantic analysis system to only consider this a lambda if this is a location
            // where the lambda's type would be inferred because of a delegate or Expression<T> type.
            //
            // ERROR tolerance.  Cast expressions can show up with partially written lambdas like so:
            //
            //      var lambda = (x$$)
            //      NextStatement();
            //
            // Check if the expression of the cast is on the same line as us or not to see if we want to
            // consider this a lambda, or just a cast.
 
            if (token.Parent is (kind: SyntaxKind.ParenthesizedExpression or SyntaxKind.TupleExpression or SyntaxKind.CastExpression))
                return true;
        }
 
        return false;
    }
 
    public static bool IsAnonymousMethodParameterModifierContext(
        this SyntaxTree syntaxTree, int position, SyntaxToken tokenOnLeftOfPosition)
    {
        var token = tokenOnLeftOfPosition;
        token = token.GetPreviousTokenIfTouchingWord(position);
 
        SyntaxNode? parent;
        if (token.Kind() is SyntaxKind.OpenParenToken or SyntaxKind.CommaToken)
        {
            parent = token.Parent;
        }
        else if (token.IsKind(SyntaxKind.ScopedKeyword) && token.Parent.IsKind(SyntaxKind.Parameter))
        {
            parent = token.Parent.Parent;
        }
        else if (token.IsKind(SyntaxKind.IdentifierToken) && token.Text == "scoped" && token.Parent is IdentifierNameSyntax scopedIdentifierName && scopedIdentifierName.Parent.IsKind(SyntaxKind.Parameter))
        {
            parent = scopedIdentifierName.Parent.Parent;
        }
        else
        {
            return false;
        }
 
        return parent.IsKind(SyntaxKind.ParameterList) && parent.IsParentKind(SyntaxKind.AnonymousMethodExpression);
    }
 
    public static bool IsPossibleLambdaOrAnonymousMethodParameterTypeContext(
        this SyntaxTree syntaxTree, int position, SyntaxToken tokenOnLeftOfPosition, CancellationToken cancellationToken)
    {
        var token = tokenOnLeftOfPosition;
        token = token.GetPreviousTokenIfTouchingWord(position);
 
        if (token.Kind() is SyntaxKind.RefKeyword or SyntaxKind.InKeyword or SyntaxKind.OutKeyword)
        {
            position = token.SpanStart;
            tokenOnLeftOfPosition = syntaxTree.FindTokenOnLeftOfPosition(position, cancellationToken);
        }
 
        if (IsAnonymousMethodParameterModifierContext(syntaxTree, position, tokenOnLeftOfPosition) ||
            IsPossibleLambdaParameterModifierContext(syntaxTree, position, tokenOnLeftOfPosition, cancellationToken))
        {
            return true;
        }
 
        return false;
    }
 
    /// <summary>
    /// Are you possibly typing a tuple type or expression?
    /// This is used to suppress colon as a completion trigger (so that you can type element names).
    /// This is also used to recommend some keywords (like var).
    /// </summary>
    public static bool IsPossibleTupleContext(this SyntaxTree syntaxTree, SyntaxToken leftToken, int position)
    {
        leftToken = leftToken.GetPreviousTokenIfTouchingWord(position);
 
        // ($$
        // (a, $$
        if (IsPossibleTupleOpenParenOrComma(leftToken))
        {
            return true;
        }
 
        // ((a, b) $$
        // (..., (a, b) $$
        if (leftToken.IsKind(SyntaxKind.CloseParenToken))
        {
            if (leftToken.Parent is (kind:
                    SyntaxKind.ParenthesizedExpression or
                    SyntaxKind.TupleExpression or
                    SyntaxKind.TupleType))
            {
                var possibleCommaOrParen = FindTokenOnLeftOfNode(leftToken.Parent);
                if (IsPossibleTupleOpenParenOrComma(possibleCommaOrParen))
                {
                    return true;
                }
            }
        }
 
        // (a $$
        // (..., b $$
        if (leftToken.IsKind(SyntaxKind.IdentifierToken))
        {
            var possibleCommaOrParen = FindTokenOnLeftOfNode(leftToken.Parent!);
            if (IsPossibleTupleOpenParenOrComma(possibleCommaOrParen))
            {
                return true;
            }
        }
 
        // (a.b $$
        // (..., a.b $$
        if (leftToken.IsKind(SyntaxKind.IdentifierToken) &&
            leftToken.Parent.IsKind(SyntaxKind.IdentifierName) &&
            leftToken.Parent.Parent is (kind: SyntaxKind.QualifiedName or SyntaxKind.SimpleMemberAccessExpression))
        {
            var possibleCommaOrParen = FindTokenOnLeftOfNode(leftToken.Parent.Parent);
            if (IsPossibleTupleOpenParenOrComma(possibleCommaOrParen))
            {
                return true;
            }
        }
 
        return false;
    }
 
    public static bool IsAtStartOfPattern(this SyntaxTree syntaxTree, SyntaxToken leftToken, int position)
    {
        leftToken = leftToken.GetPreviousTokenIfTouchingWord(position);
 
        if (leftToken.IsKind(SyntaxKind.OpenParenToken))
        {
            if (leftToken.Parent is ParenthesizedExpressionSyntax parenthesizedExpression)
            {
                // If we're dealing with an expression surrounded by one or more sets of open parentheses, we need to
                // walk up the parens in order to see if we're actually at the start of a valid pattern or not.
                return IsAtStartOfPattern(syntaxTree, parenthesizedExpression.GetFirstToken().GetPreviousToken(), parenthesizedExpression.SpanStart);
            }
 
            // e is ((($$ 1 or 2)))
            if (leftToken.Parent.IsKind(SyntaxKind.ParenthesizedPattern))
            {
                return true;
            }
        }
 
        // case $$
        // is $$
        if (leftToken.Kind() is SyntaxKind.CaseKeyword or SyntaxKind.IsKeyword)
        {
            return true;
        }
 
        // e switch { $$
        // e switch { ..., $$
        if (leftToken.Kind() is SyntaxKind.OpenBraceToken or SyntaxKind.CommaToken && leftToken.Parent.IsKind(SyntaxKind.SwitchExpression))
        {
            return true;
        }
 
        // e is ($$
        // e is (..., $$
        if (leftToken.Kind() is SyntaxKind.OpenParenToken or SyntaxKind.CommaToken && leftToken.Parent.IsKind(SyntaxKind.PositionalPatternClause))
        {
            return true;
        }
 
        // e is [$$
        // e is [..., $$
        if (leftToken.Kind() is SyntaxKind.OpenBracketToken or SyntaxKind.CommaToken && leftToken.Parent.IsKind(SyntaxKind.ListPattern))
        {
            return true;
        }
 
        // e is [..$$
        // e is [..., ..$$
        if (leftToken.IsKind(SyntaxKind.DotDotToken) && leftToken.Parent.IsKind(SyntaxKind.SlicePattern))
        {
            return true;
        }
 
        // e is { P: $$
        // e is { ..., P: $$
        // e is { ..., P.P2: $$
        if (leftToken.IsKind(SyntaxKind.ColonToken) && leftToken.Parent is (kind: SyntaxKind.NameColon or SyntaxKind.ExpressionColon) &&
            leftToken.Parent.IsParentKind(SyntaxKind.Subpattern))
        {
            return true;
        }
 
        // e is 1 and $$
        // e is 1 or $$
        // e is SomeEnum.SomeEnumValue and $$
        // e is SomeEnum.SomeEnumValue or $$
        // 'and' & 'or' are identifier in the last 2 examples because of lack of context
        if (leftToken.IsKindOrHasMatchingText(SyntaxKind.AndKeyword) || leftToken.IsKindOrHasMatchingText(SyntaxKind.OrKeyword))
        {
            return leftToken.Parent is BinaryPatternSyntax ||
                   leftToken.Parent is SingleVariableDesignationSyntax { Parent: DeclarationPatternSyntax };
        }
 
        // e is not $$
        if (leftToken.IsKind(SyntaxKind.NotKeyword) && leftToken.Parent.IsKind(SyntaxKind.NotPattern))
        {
            return true;
        }
 
        // e is > $$
        // e is >= $$
        // e is < $$
        // e is <= $$
        if (leftToken.Kind() is SyntaxKind.GreaterThanToken or SyntaxKind.GreaterThanEqualsToken or SyntaxKind.LessThanToken or SyntaxKind.LessThanEqualsToken &&
            leftToken.Parent.IsKind(SyntaxKind.RelationalPattern))
        {
            return true;
        }
 
        return false;
    }
 
    public static bool IsAtEndOfPattern(this SyntaxTree syntaxTree, SyntaxToken leftToken, int position)
    {
        var originalLeftToken = leftToken;
        leftToken = leftToken.GetPreviousTokenIfTouchingWord(position);
 
        // For instance:
        // e is { A.$$ }
        // e is { A->$$ }
        if (leftToken.IsKind(SyntaxKind.DotToken) ||
            leftToken.IsKind(SyntaxKind.MinusGreaterThanToken))
        {
            return false;
        }
 
        var patternSyntax = leftToken.GetAncestor<PatternSyntax>();
        if (patternSyntax != null)
        {
            var lastTokenInPattern = patternSyntax.GetLastToken();
 
            // This check should cover the majority of cases, e.g.:
            // e is 1 $$
            // e is >= 0 $$
            // e is { P: (1 $$
            // e is { P: (1) $$
            if (leftToken == lastTokenInPattern)
            {
                // Patterns such as 'e is not $$', 'e is 1 or $$', 'e is ($$', and 'e is null or global::$$' should be invalid here
                // as they are incomplete patterns.
                return leftToken.Kind() is not (SyntaxKind.OrKeyword
                    or SyntaxKind.AndKeyword
                    or SyntaxKind.NotKeyword
                    or SyntaxKind.OpenParenToken
                    or SyntaxKind.ColonColonToken
                    or SyntaxKind.DotDotToken
                    or SyntaxKind.OpenBraceToken);
            }
 
            // We want to make sure that IsAtEndOfPattern returns true even when the user is in the middle of typing a keyword
            // after a pattern.
            // For example, with the keyword 'and', we want to make sure that 'e is int an$$' is still recognized as valid.
            if (lastTokenInPattern.Parent is SingleVariableDesignationSyntax variableDesignationSyntax &&
                originalLeftToken.Parent == variableDesignationSyntax)
            {
                return patternSyntax is DeclarationPatternSyntax or RecursivePatternSyntax;
            }
 
            // e is (expr) a$$
            //
            // this will be parsed as a constant-pattern where the constant expression is a cast expression (if 'expr'
            // is a legal type).
            if (patternSyntax is ConstantPatternSyntax { Expression: CastExpressionSyntax { Expression: IdentifierNameSyntax } castExpression } &&
                leftToken == castExpression.CloseParenToken)
            {
                return true;
            }
        }
 
        // e is C.P $$
        // e is int $$
        if (leftToken.IsLastTokenOfNode<TypeSyntax>(out var typeSyntax))
        {
            // If typeSyntax is part of a qualified name, we want to get the fully-qualified name so that we can
            // later accurately perform the check comparing the right side of the BinaryExpressionSyntax to
            // the typeSyntax.
            while (typeSyntax.Parent is TypeSyntax parentTypeSyntax)
            {
                typeSyntax = parentTypeSyntax;
            }
 
            if (typeSyntax.Parent is BinaryExpressionSyntax binaryExpressionSyntax &&
                binaryExpressionSyntax.OperatorToken.IsKind(SyntaxKind.IsKeyword) &&
                binaryExpressionSyntax.Right == typeSyntax && !typeSyntax.IsVar)
            {
                return true;
            }
        }
 
        // We need to include a special case for switch statement cases, as some are not currently parsed as patterns, e.g. case (1 $$
        if (IsAtEndOfSwitchStatementPattern(leftToken))
        {
            return true;
        }
 
        return false;
 
        static bool IsAtEndOfSwitchStatementPattern(SyntaxToken leftToken)
        {
            SyntaxNode? node = leftToken.Parent as ExpressionSyntax;
            if (node == null)
                return false;
 
            // Walk up the right edge of all complete expressions.
            while (node is ExpressionSyntax && node.GetLastToken(includeZeroWidth: true) == leftToken)
                node = node.GetRequiredParent();
 
            // Getting rid of the extra parentheses to deal with cases such as 'case (((1 $$'
            while (node is ParenthesizedExpressionSyntax)
                node = node.GetRequiredParent();
 
            // case (1 $$
            if (node is CaseSwitchLabelSyntax { Parent: SwitchSectionSyntax })
                return true;
 
            return false;
        }
    }
 
    private static SyntaxToken FindTokenOnLeftOfNode(SyntaxNode node)
        => node.FindTokenOnLeftOfPosition(node.SpanStart);
 
    public static bool IsPossibleTupleOpenParenOrComma(this SyntaxToken possibleCommaOrParen)
    {
        if (possibleCommaOrParen.Kind() is not (SyntaxKind.OpenParenToken or SyntaxKind.CommaToken))
        {
            return false;
        }
 
        if (possibleCommaOrParen.Parent is (kind:
                SyntaxKind.ParenthesizedExpression or
                SyntaxKind.TupleExpression or
                SyntaxKind.TupleType or
                SyntaxKind.CastExpression))
        {
            return true;
        }
 
        // in script
        if (possibleCommaOrParen.Parent.IsKind(SyntaxKind.ParameterList) &&
            possibleCommaOrParen.Parent?.Parent is ParenthesizedLambdaExpressionSyntax parenthesizedLambda)
        {
            if (parenthesizedLambda.ArrowToken.IsMissing)
            {
                return true;
            }
        }
 
        return false;
    }
 
    /// <summary>
    /// Are you possibly in the designation part of a deconstruction?
    /// This is used to enter suggestion mode (suggestions become soft-selected).
    /// </summary>
    public static bool IsPossibleDeconstructionDesignation(this SyntaxTree syntaxTree,
        int position, CancellationToken cancellationToken)
    {
        var leftToken = syntaxTree.FindTokenOnLeftOfPosition(position, cancellationToken);
        leftToken = leftToken.GetPreviousTokenIfTouchingWord(position);
 
        // The well-formed cases:
        // var ($$, y) = e;
        // (var $$, var y) = e;
        if (leftToken.Parent.IsKind(SyntaxKind.ParenthesizedVariableDesignation) ||
            leftToken.Parent.IsParentKind(SyntaxKind.ParenthesizedVariableDesignation))
        {
            return true;
        }
 
        // (var $$, var y)
        // (var x, var y)
        if (syntaxTree.IsPossibleTupleContext(leftToken, position) && !IsPossibleTupleOpenParenOrComma(leftToken))
        {
            return true;
        }
 
        // var ($$)
        // var (x, $$)
        if (IsPossibleVarDeconstructionOpenParenOrComma(leftToken))
        {
            return true;
        }
 
        // var (($$), y)
        if (leftToken.IsKind(SyntaxKind.OpenParenToken) && leftToken.Parent.IsKind(SyntaxKind.ParenthesizedExpression))
        {
            if (IsPossibleVarDeconstructionOpenParenOrComma(FindTokenOnLeftOfNode(leftToken.Parent)))
            {
                return true;
            }
        }
 
        // var ((x, $$), y)
        // var (($$, x), y)
        if (leftToken.Kind() is SyntaxKind.OpenParenToken or SyntaxKind.CommaToken && leftToken.Parent.IsKind(SyntaxKind.TupleExpression))
        {
            if (IsPossibleVarDeconstructionOpenParenOrComma(FindTokenOnLeftOfNode(leftToken.Parent)))
            {
                return true;
            }
        }
 
        // foreach (var ($$
        // foreach (var ((x, $$), y)
        if (leftToken.Kind() is SyntaxKind.OpenParenToken or SyntaxKind.CommaToken)
        {
            var outer = UnwrapPossibleTuple(leftToken.Parent!);
            if (outer.Parent is ForEachStatementSyntax @foreach)
            {
                if (@foreach.Expression == outer &&
                    @foreach.Type is IdentifierNameSyntax identifierName &&
                    identifierName.Identifier.ValueText == "var")
                {
                    return true;
                }
            }
        }
 
        return false;
    }
 
    /// <summary>
    /// If inside a parenthesized or tuple expression, unwrap the nestings and return the container.
    /// </summary>
    private static SyntaxNode UnwrapPossibleTuple(SyntaxNode node)
    {
        while (true)
        {
            if (node.Parent.IsKind(SyntaxKind.ParenthesizedExpression))
            {
                node = node.Parent;
                continue;
            }
 
            if (node.Parent.IsKind(SyntaxKind.Argument) && node.Parent.Parent.IsKind(SyntaxKind.TupleExpression))
            {
                node = node.Parent.Parent;
                continue;
            }
 
            return node;
        }
    }
 
    private static bool IsPossibleVarDeconstructionOpenParenOrComma(SyntaxToken leftToken)
    {
        if (leftToken.Kind() is SyntaxKind.OpenParenToken or SyntaxKind.CommaToken &&
            leftToken.Parent.IsKind(SyntaxKind.ArgumentList) &&
            leftToken.Parent?.Parent is InvocationExpressionSyntax invocation)
        {
            if (invocation.Expression is IdentifierNameSyntax identifierName &&
                identifierName.Identifier.ValueText == "var")
            {
                return true;
            }
        }
 
        return false;
    }
 
    public static bool HasNames(this TupleExpressionSyntax tuple)
        => tuple.Arguments.Any(a => a.NameColon != null);
 
    public static bool IsValidContextForFromClause(
        this SyntaxTree syntaxTree,
        int position,
        SyntaxToken tokenOnLeftOfPosition,
        CancellationToken cancellationToken,
        SemanticModel? semanticModelOpt = null)
    {
        if (syntaxTree.IsExpressionContext(position, tokenOnLeftOfPosition, attributes: false, cancellationToken: cancellationToken, semanticModel: semanticModelOpt) &&
            !syntaxTree.IsConstantExpressionContext(position, tokenOnLeftOfPosition))
        {
            return true;
        }
 
        // cases:
        //   var q = |
        //   var q = f|
        //
        //   var q = from x in y
        //           |
        //
        //   var q = from x in y
        //           f|
        //
        // this list is *not* exhaustive.
        // the first two are handled by 'IsExpressionContext'
 
        var token = tokenOnLeftOfPosition;
        token = token.GetPreviousTokenIfTouchingWord(position);
 
        // var q = from x in y
        //         |
        if (!token.IntersectsWith(position) &&
            token.IsLastTokenOfQueryClause())
        {
            return true;
        }
 
        return false;
    }
 
    public static bool IsValidContextForJoinClause(
        this SyntaxTree syntaxTree, int position, SyntaxToken tokenOnLeftOfPosition)
    {
        var token = tokenOnLeftOfPosition;
        token = token.GetPreviousTokenIfTouchingWord(position);
 
        // var q = from x in y
        //         |
        if (!token.IntersectsWith(position) &&
            token.IsLastTokenOfQueryClause())
        {
            return true;
        }
 
        return false;
    }
 
    public static bool IsLocalVariableDeclarationContext(
        this SyntaxTree syntaxTree, int position, SyntaxToken tokenOnLeftOfPosition, CancellationToken cancellationToken)
    {
        // cases:
        //  const var
        //  out var
        //  for (var
        //  foreach (var
        //  await foreach (var
        //  using (var
        //  await using (var
        //  from var
        //  join var
        //  using var
        //  await using var
        //  scoped var
 
        var token = tokenOnLeftOfPosition.GetPreviousTokenIfTouchingWord(position);
 
        // const |
        if (token.IsKind(SyntaxKind.ConstKeyword) &&
            token.Parent.IsKind(SyntaxKind.LocalDeclarationStatement))
        {
            return true;
        }
 
        // ref |
        // ref readonly |
        // for ( ref |
        // foreach ( ref | x
        if (token.Kind() is SyntaxKind.RefKeyword or SyntaxKind.ReadOnlyKeyword)
        {
            var parent = token.Parent;
            if (parent is (kind: SyntaxKind.RefType or SyntaxKind.RefExpression or SyntaxKind.LocalDeclarationStatement))
            {
                if (parent.IsParentKind(SyntaxKind.VariableDeclaration) &&
                    parent.Parent?.Parent is (kind:
                        SyntaxKind.LocalDeclarationStatement or
                        SyntaxKind.ForStatement or
                        SyntaxKind.ForEachVariableStatement))
                {
                    return true;
                }
 
                if (parent.Parent is (kind: SyntaxKind.ForEachStatement or SyntaxKind.ForEachVariableStatement))
                {
                    return true;
                }
            }
        }
 
        // out |
        if (token.IsKind(SyntaxKind.OutKeyword) &&
            token.Parent is ArgumentSyntax argument &&
            argument.RefKindKeyword == token)
        {
            return true;
        }
 
        // for ( |
        // foreach ( |
        // await foreach ( |
        // using ( |
        // await using ( |
        if (token.IsKind(SyntaxKind.OpenParenToken))
        {
            var previous = token.GetPreviousToken(includeSkipped: true);
            if (previous.Kind() is SyntaxKind.ForKeyword or SyntaxKind.ForEachKeyword or SyntaxKind.UsingKeyword)
                return true;
        }
 
        // using |
        // await using |
        if (token.IsKind(SyntaxKind.UsingKeyword) &&
            token.Parent is LocalDeclarationStatementSyntax)
        {
            return true;
        }
 
        // from |
        var tokenOnLeftOfStart = syntaxTree.FindTokenOnLeftOfPosition(token.SpanStart, cancellationToken);
        if (token.IsKindOrHasMatchingText(SyntaxKind.FromKeyword) &&
            syntaxTree.IsValidContextForFromClause(token.SpanStart, tokenOnLeftOfStart, cancellationToken))
        {
            return true;
        }
 
        // join |
        if (CodeAnalysis.CSharpExtensions.IsKind(token, SyntaxKind.JoinKeyword) &&
            syntaxTree.IsValidContextForJoinClause(token.SpanStart, tokenOnLeftOfStart))
        {
            return true;
        }
 
        // scoped |
        // The compiler parses this as an identifier whose parent is:
        // - ExpressionStatementSyntax when in method declaration.
        // - IncompleteMemberSyntax when in top-level code and there are no class declarations after it.
        // - BaseTypeDeclarationSyntax if it comes after scoped
        // - VariableDeclarationSyntax for `scoped X` inside method declaration
        if (token.IsKind(SyntaxKind.IdentifierToken) && token.Text == "scoped" && token.Parent.IsKind(SyntaxKind.IdentifierName) && token.Parent.Parent is VariableDeclarationSyntax or ExpressionStatementSyntax or IncompleteMemberSyntax)
        {
            return true;
        }
 
        // scoped v|
        if (token.IsKind(SyntaxKind.ScopedKeyword) && token.Parent is IncompleteMemberSyntax)
        {
            return true;
        }
 
        return false;
    }
 
    public static bool IsFixedVariableDeclarationContext(
        this SyntaxTree syntaxTree, int position, SyntaxToken tokenOnLeftOfPosition)
    {
        // cases:
        //  fixed (var
 
        var token = tokenOnLeftOfPosition;
        token = token.GetPreviousTokenIfTouchingWord(position);
 
        if (token.IsKind(SyntaxKind.OpenParenToken) &&
            token.GetPreviousToken(includeSkipped: true).IsKind(SyntaxKind.FixedKeyword))
        {
            return true;
        }
 
        return false;
    }
 
    public static bool IsCatchVariableDeclarationContext(this SyntaxTree syntaxTree, int position, CancellationToken cancellationToken)
    {
        // cases:
        //  catch (var
 
        var token = syntaxTree.FindTokenOnLeftOfPosition(position, cancellationToken);
        token = token.GetPreviousTokenIfTouchingWord(position);
 
        if (token.IsKind(SyntaxKind.OpenParenToken) &&
            token.GetPreviousToken(includeSkipped: true).IsKind(SyntaxKind.CatchKeyword))
        {
            return true;
        }
 
        return false;
    }
 
    public static bool IsIsOrAsTypeContext(this SyntaxTree syntaxTree, int position, SyntaxToken tokenOnLeftOfPosition)
    {
        var token = tokenOnLeftOfPosition;
        token = token.GetPreviousTokenIfTouchingWord(position);
 
        if (token.Kind() is SyntaxKind.IsKeyword or SyntaxKind.AsKeyword)
            return true;
 
        return false;
    }
 
    public static bool IsObjectCreationTypeContext(
        this SyntaxTree syntaxTree, int position, SyntaxToken tokenOnLeftOfPosition, CancellationToken cancellationToken)
    {
        var token = tokenOnLeftOfPosition.GetPreviousTokenIfTouchingWord(position);
 
        if (token.IsKind(SyntaxKind.NewKeyword))
        {
            // we can follow a 'new' if it's the 'new' for an expression.
            var start = token.SpanStart;
            var tokenOnLeftOfStart = syntaxTree.FindTokenOnLeftOfPosition(start, cancellationToken);
            return
                IsNonConstantExpressionContext(syntaxTree, token.SpanStart, tokenOnLeftOfStart, cancellationToken) ||
                syntaxTree.IsStatementContext(token.SpanStart, tokenOnLeftOfStart, cancellationToken) ||
                syntaxTree.IsGlobalStatementContext(token.SpanStart, cancellationToken);
        }
 
        return false;
    }
 
    private static bool IsNonConstantExpressionContext(SyntaxTree syntaxTree, int position, SyntaxToken tokenOnLeftOfPosition, CancellationToken cancellationToken)
    {
        return
            syntaxTree.IsExpressionContext(position, tokenOnLeftOfPosition, attributes: true, cancellationToken: cancellationToken) &&
            !syntaxTree.IsConstantExpressionContext(position, tokenOnLeftOfPosition);
    }
 
    public static bool IsPreProcessorDirectiveContext(this SyntaxTree syntaxTree, int position, CancellationToken cancellationToken)
    {
        var leftToken = syntaxTree.FindTokenOnLeftOfPosition(position, cancellationToken, includeDirectives: true);
 
        return syntaxTree.IsPreProcessorDirectiveContext(position, leftToken, cancellationToken);
    }
 
    public static bool IsStatementContext(this SyntaxTree syntaxTree, int position, SyntaxToken tokenOnLeftOfPosition, CancellationToken cancellationToken)
    {
#if false
        // we're in a statement if the thing that comes before allows for
        // statements to follow.  Or if we're on a just started identifier
        // in the first position where a statement can go.
        if (syntaxTree.IsInPreprocessorDirectiveContext(position, cancellationToken))
        {
            return false;
        }
#endif
 
        var token = tokenOnLeftOfPosition;
        token = token.GetPreviousTokenIfTouchingWord(position);
 
        return token.IsBeginningOfStatementContext();
    }
 
    public static bool IsGlobalStatementContext(this SyntaxTree syntaxTree, int position, CancellationToken cancellationToken)
    {
        if (syntaxTree.IsPreProcessorDirectiveContext(position, cancellationToken))
            return false;
 
        var token = syntaxTree.FindTokenOnLeftOfPosition(position, cancellationToken)
                              .GetPreviousTokenIfTouchingWord(position);
 
        if (token.IsKind(SyntaxKind.None))
        {
            // global statements can't come before usings/externs
            if (syntaxTree.GetRoot(cancellationToken) is CompilationUnitSyntax compilationUnit &&
                (compilationUnit.Externs.Count > 0 || compilationUnit.Usings.Count > 0))
            {
                return false;
            }
 
            return true;
        }
 
        return token.IsBeginningOfGlobalStatementContext();
    }
 
    public static bool IsInstanceContext(this SyntaxTree syntaxTree, SyntaxToken targetToken, SemanticModel semanticModel, CancellationToken cancellationToken)
    {
#if false
        if (syntaxTree.IsInPreprocessorDirectiveContext(position, cancellationToken))
        {
            return false;
        }
#endif
 
        // It's possible the caller is asking about a speculative semantic model, and may have moved before the
        // bounds of that model (for example, while looking at the nearby tokens around an edit).  If so, ensure we
        // walk outwards to the correct model to actually ask this question of.
        var position = targetToken.SpanStart;
        if (semanticModel.IsSpeculativeSemanticModel && position < semanticModel.OriginalPositionForSpeculation)
            semanticModel = semanticModel.GetOriginalSemanticModel();
 
        var enclosingSymbol = semanticModel.GetEnclosingSymbol(position, cancellationToken);
 
        while (enclosingSymbol is IMethodSymbol { MethodKind: MethodKind.LocalFunction or MethodKind.AnonymousFunction } method)
        {
            if (method.IsStatic)
                return false;
 
            // It is allowed to reference the instance (`this`) within a local function or anonymous function, as long as the containing method allows it
            enclosingSymbol = enclosingSymbol.ContainingSymbol;
        }
 
        return enclosingSymbol is { IsStatic: false };
    }
 
    public static bool IsPossibleCastTypeContext(
        this SyntaxTree syntaxTree, int position, SyntaxToken tokenOnLeftOfPosition, CancellationToken cancellationToken)
    {
        var token = tokenOnLeftOfPosition.GetPreviousTokenIfTouchingWord(position);
 
        if (CodeAnalysis.CSharpExtensions.IsKind(token, SyntaxKind.OpenParenToken) &&
            syntaxTree.IsExpressionContext(token.SpanStart, syntaxTree.FindTokenOnLeftOfPosition(token.SpanStart, cancellationToken), false, cancellationToken))
        {
            return true;
        }
 
        return false;
    }
 
    public static bool IsDefiniteCastTypeContext(this SyntaxTree syntaxTree, int position, SyntaxToken tokenOnLeftOfPosition)
    {
        var token = tokenOnLeftOfPosition;
        token = token.GetPreviousTokenIfTouchingWord(position);
 
        if (token.IsKind(SyntaxKind.OpenParenToken) &&
            token.Parent.IsKind(SyntaxKind.CastExpression))
        {
            return true;
        }
 
        return false;
    }
 
    public static bool IsConstantExpressionContext(this SyntaxTree syntaxTree, int position,
        SyntaxToken tokenOnLeftOfPosition)
    {
        if (IsAtStartOfPattern(syntaxTree, tokenOnLeftOfPosition, position))
        {
            return true;
        }
 
        var token = tokenOnLeftOfPosition.GetPreviousTokenIfTouchingWord(position);
 
        // goto case |
        if (token.IsKind(SyntaxKind.CaseKeyword) &&
            token.Parent.IsKind(SyntaxKind.GotoCaseStatement))
        {
            return true;
        }
 
        if (token.IsKind(SyntaxKind.EqualsToken) &&
            token.Parent is EqualsValueClauseSyntax equalsValue)
        {
            if (equalsValue.IsParentKind(SyntaxKind.VariableDeclarator) &&
                equalsValue.Parent.IsParentKind(SyntaxKind.VariableDeclaration))
            {
                // class C { const int i = |
                var fieldDeclaration = equalsValue.GetAncestor<FieldDeclarationSyntax>();
                if (fieldDeclaration != null)
                {
                    return fieldDeclaration.Modifiers.Any(SyntaxKind.ConstKeyword);
                }
 
                // void M() { const int i = |
                var localDeclaration = equalsValue.GetAncestor<LocalDeclarationStatementSyntax>();
                if (localDeclaration != null)
                {
                    return localDeclaration.Modifiers.Any(SyntaxKind.ConstKeyword);
                }
            }
 
            // enum E { A = |
            if (equalsValue.IsParentKind(SyntaxKind.EnumMemberDeclaration))
            {
                return true;
            }
 
            // void M(int i = |
            if (equalsValue.IsParentKind(SyntaxKind.Parameter))
            {
                return true;
            }
        }
 
        // [Goo( |
        // [Goo(x, |
        if (token.Parent.IsKind(SyntaxKind.AttributeArgumentList) &&
            token.Kind() is SyntaxKind.CommaToken or SyntaxKind.OpenParenToken)
        {
            return true;
        }
 
        // [Goo(x: |
        if (token.IsKind(SyntaxKind.ColonToken) &&
            token.Parent.IsKind(SyntaxKind.NameColon) &&
            token.Parent.IsParentKind(SyntaxKind.AttributeArgument))
        {
            return true;
        }
 
        // [Goo(X = |
        if (token.IsKind(SyntaxKind.EqualsToken) &&
            token.Parent.IsKind(SyntaxKind.NameEquals) &&
            token.Parent.IsParentKind(SyntaxKind.AttributeArgument))
        {
            return true;
        }
 
        // TODO: Fixed-size buffer declarations
 
        return false;
    }
 
    public static bool IsLabelContext(this SyntaxTree syntaxTree, int position, CancellationToken cancellationToken)
    {
        var token = syntaxTree.FindTokenOnLeftOfPosition(position, cancellationToken);
 
        var gotoStatement = token.GetAncestor<GotoStatementSyntax>();
        if (gotoStatement != null)
        {
            if (gotoStatement.GotoKeyword == token)
            {
                return true;
            }
 
            if (gotoStatement.Expression != null &&
                !gotoStatement.Expression.IsMissing &&
                gotoStatement.Expression is IdentifierNameSyntax &&
                ((IdentifierNameSyntax)gotoStatement.Expression).Identifier == token &&
                token.IntersectsWith(position))
            {
                return true;
            }
        }
 
        return false;
    }
 
    public static bool IsExpressionContext(
        this SyntaxTree syntaxTree,
        int position,
        SyntaxToken tokenOnLeftOfPosition,
        bool attributes,
        CancellationToken cancellationToken,
        SemanticModel? semanticModel = null)
    {
        // cases:
        //   var q = |
        //   var q = a|
        // this list is *not* exhaustive.
 
        var token = tokenOnLeftOfPosition.GetPreviousTokenIfTouchingWord(position);
 
        if (token.GetAncestor<ConditionalDirectiveTriviaSyntax>() != null)
        {
            return false;
        }
 
        if (!attributes)
        {
            if (token.GetAncestor<AttributeListSyntax>() != null)
            {
                return false;
            }
        }
 
        if (syntaxTree.IsConstantExpressionContext(position, tokenOnLeftOfPosition))
        {
            return true;
        }
 
        // no expressions after .   ::   ->
        if (token.Kind()
                is SyntaxKind.DotToken
                or SyntaxKind.ColonColonToken
                or SyntaxKind.MinusGreaterThanToken)
        {
            return false;
        }
 
        // Normally you can have any sort of expression after an equals. However, this does not
        // apply to a "using Goo = ..." situation.
        if (token.IsKind(SyntaxKind.EqualsToken))
        {
            if (token.Parent.IsKind(SyntaxKind.NameEquals) &&
                token.Parent.IsParentKind(SyntaxKind.UsingDirective))
            {
                return false;
            }
        }
 
        // q = |
        // q -= |
        // q *= |
        // q += |
        // q /= |
        // q ^= |
        // q %= |
        // q &= |
        // q |= |
        // q <<= |
        // q >>= |
        // q >>>= |
        // q ??= |
        if (token.Kind()
                is SyntaxKind.EqualsToken
                or SyntaxKind.MinusEqualsToken
                or SyntaxKind.AsteriskEqualsToken
                or SyntaxKind.PlusEqualsToken
                or SyntaxKind.SlashEqualsToken
                or SyntaxKind.ExclamationEqualsToken
                or SyntaxKind.CaretEqualsToken
                or SyntaxKind.AmpersandEqualsToken
                or SyntaxKind.BarEqualsToken
                or SyntaxKind.PercentEqualsToken
                or SyntaxKind.LessThanLessThanEqualsToken
                or SyntaxKind.GreaterThanGreaterThanEqualsToken
                or SyntaxKind.GreaterThanGreaterThanGreaterThanEqualsToken
                or SyntaxKind.QuestionQuestionEqualsToken)
        {
            return true;
        }
 
        // ( |
        if (token.IsKind(SyntaxKind.OpenParenToken))
        {
            if (token.Parent.IsKind(SyntaxKind.ParenthesizedExpression))
                return true;
 
            // If there's a string in the parenthesis in the code below, the parser would return
            // a CastExpression instead of ParenthesizedExpression. However, some features like keyword completion
            // might be able tolerate this and still want to treat it as a ParenthesizedExpression.
            //
            //         var data = (n$$)
            //         M();
            if (token.Parent is CastExpressionSyntax castExpression &&
                (castExpression.Expression.IsMissing || castExpression.CloseParenToken.TrailingTrivia.GetFirstNewLine().HasValue))
            {
                return true;
            }
        }
 
        // - |
        // + |
        // ~ |
        // ! |
        if (token.Parent is PrefixUnaryExpressionSyntax prefix)
        {
            return prefix.OperatorToken == token;
        }
 
        // not sure about these:
        //   ++ |
        //   -- |
#if false
            token.Kind == SyntaxKind.PlusPlusToken ||
            token.Kind == SyntaxKind.DashDashToken)
#endif
        // await |
        if (token.Parent is AwaitExpressionSyntax awaitExpression)
        {
            return awaitExpression.AwaitKeyword == token;
        }
 
        // Check for binary operators.
        // Note:
        //   - We handle < specially as it can be ambiguous with generics.
        //   - We handle * specially because it can be ambiguous with pointer types.
 
        // a *
        // a /
        // a %
        // a +
        // a -
        // a <<
        // a >>
        // a <
        // a >
        // a &&
        // a ||
        // a &
        // a |
        // a ^
        if (token.Parent is BinaryExpressionSyntax binary)
        {
            // If the client provided a binding, then check if this is actually generic.  If so,
            // then this is not an expression context. i.e. if we have "Goo < |" then it could
            // be an expression context, or it could be a type context if Goo binds to a type or
            // method.
            if (semanticModel != null && syntaxTree.IsGenericTypeArgumentContext(position, tokenOnLeftOfPosition, cancellationToken, semanticModel))
            {
                return false;
            }
 
            if (binary.OperatorToken == token)
            {
                // If this is a multiplication expression and a semantic model was passed in,
                // check to see if the expression to the left is a type name. If it is, treat
                // this as a pointer type.
                if (token.IsKind(SyntaxKind.AsteriskToken) && semanticModel != null)
                {
                    if (binary.Left is TypeSyntax type && type.IsPotentialTypeName(semanticModel, cancellationToken))
                    {
                        return false;
                    }
                }
 
                return true;
            }
        }
 
        // Special case:
        //    Goo * bar
        //    Goo ? bar
        // This parses as a local decl called bar of type Goo* or Goo?
        if (tokenOnLeftOfPosition.IntersectsWith(position) &&
            tokenOnLeftOfPosition.IsKind(SyntaxKind.IdentifierToken))
        {
            var previousToken = tokenOnLeftOfPosition.GetPreviousToken(includeSkipped: true);
            if (previousToken.Kind() is SyntaxKind.AsteriskToken or SyntaxKind.QuestionToken &&
                previousToken.Parent?.Kind() is SyntaxKind.PointerType or SyntaxKind.NullableType)
            {
                var type = previousToken.Parent as TypeSyntax;
                if (type.IsParentKind(SyntaxKind.VariableDeclaration) &&
                    type.Parent?.Parent is LocalDeclarationStatementSyntax declStatement)
                {
                    // note, this doesn't apply for cases where we know it 
                    // absolutely is not multiplication or a conditional expression.
                    var underlyingType = type is PointerTypeSyntax pointerType
                        ? pointerType.ElementType
                        : ((NullableTypeSyntax)type).ElementType;
 
                    if (!underlyingType.IsPotentialTypeName(semanticModel, cancellationToken))
                    {
                        return true;
                    }
                }
            }
        }
 
        // new int[|
        // new int[expr, |
        if (token.Kind() is SyntaxKind.OpenBracketToken or SyntaxKind.CommaToken &&
            token.Parent.IsKind(SyntaxKind.ArrayRankSpecifier))
        {
            return true;
        }
 
        // 1..|
        // not 1.|.
        if (token.IsKind(SyntaxKind.DotDotToken) &&
            token.Parent.IsKind(SyntaxKind.RangeExpression) &&
            position >= token.Span.End)
        {
            return true;
        }
 
        // goo ? |
        if (token.IsKind(SyntaxKind.QuestionToken) &&
            token.Parent is ConditionalExpressionSyntax conditionalExpression)
        {
            // If the condition is simply a TypeSyntax that binds to a type, treat this as a nullable type.
            return conditionalExpression.Condition is not TypeSyntax type
                || !type.IsPotentialTypeName(semanticModel, cancellationToken);
        }
 
        // goo ? bar : |
        if (token.IsKind(SyntaxKind.ColonToken) &&
            token.Parent.IsKind(SyntaxKind.ConditionalExpression))
        {
            return true;
        }
 
        // typeof(|
        // default(|
        // sizeof(|
        if (token.IsKind(SyntaxKind.OpenParenToken))
        {
            if (token.Parent is (kind: SyntaxKind.TypeOfExpression or SyntaxKind.DefaultExpression or SyntaxKind.SizeOfExpression))
            {
                return false;
            }
        }
 
        // var(|
        // var(id, |
        // Those are more likely to be deconstruction-declarations being typed than invocations a method "var"
        if (token.Kind() is SyntaxKind.OpenParenToken or SyntaxKind.CommaToken &&
            token.IsInvocationOfVarExpression())
        {
            return false;
        }
 
        // Goo(|
        // Goo(expr, |
        // this[|
        // var t = (1, |
        // var t = (| , 2)
        if (token.Kind() is SyntaxKind.OpenParenToken or SyntaxKind.OpenBracketToken or SyntaxKind.CommaToken &&
            token.Parent is (kind: SyntaxKind.ArgumentList or SyntaxKind.BracketedArgumentList or SyntaxKind.TupleExpression))
        {
            return true;
        }
 
        // [Goo(|
        // [Goo(expr, |
        if (attributes)
        {
            if (token.Kind() is SyntaxKind.OpenParenToken or SyntaxKind.CommaToken)
            {
                if (token.Parent.IsKind(SyntaxKind.AttributeArgumentList))
                {
                    return true;
                }
            }
        }
 
        // Goo(ref |
        // Goo(in |
        // Goo(out |
        // ref var x = ref |
        if (token.Kind() is SyntaxKind.RefKeyword or SyntaxKind.InKeyword or SyntaxKind.OutKeyword)
        {
            if (token.Parent.IsKind(SyntaxKind.Argument))
            {
                return true;
            }
            else if (token.Parent.IsKind(SyntaxKind.RefExpression))
            {
                // ( ref |
                // parenthesized expressions can't directly contain RefExpression, unless the user is typing an incomplete lambda expression.
                if (token.Parent.IsParentKind(SyntaxKind.ParenthesizedExpression))
                {
                    return false;
                }
 
                return true;
            }
        }
 
        // Goo(bar: |
        if (token.IsKind(SyntaxKind.ColonToken) &&
            token.Parent.IsKind(SyntaxKind.NameColon) &&
            token.Parent.IsParentKind(SyntaxKind.Argument))
        {
            return true;
        }
 
        // a => |
        if (token.IsKind(SyntaxKind.EqualsGreaterThanToken))
        {
            return true;
        }
 
        // new List<int> { |
        // new List<int> { expr, |
        if (token.Kind() is SyntaxKind.OpenBraceToken or SyntaxKind.CommaToken)
        {
            if (token.Parent is InitializerExpressionSyntax)
            {
                // The compiler treats the ambiguous case as an object initializer, so we'll say
                // expressions are legal here
                if (token.Parent.IsKind(SyntaxKind.ObjectInitializerExpression) && token.IsKind(SyntaxKind.OpenBraceToken))
                {
                    // In this position { a$$ =, the user is trying to type an object initializer.
                    if (!token.IntersectsWith(position) && token.GetNextToken().GetNextToken().IsKind(SyntaxKind.EqualsToken))
                    {
                        return false;
                    }
 
                    return true;
                }
 
                // Perform a semantic check to determine whether or not the type being created
                // can support a collection initializer. If not, this must be an object initializer
                // and can't be an expression context.
                if (semanticModel != null &&
                    token.Parent?.Parent is ObjectCreationExpressionSyntax objectCreation)
                {
                    var containingSymbol = semanticModel.GetEnclosingNamedTypeOrAssembly(position, cancellationToken);
                    if (semanticModel.GetSymbolInfo(objectCreation.Type, cancellationToken).Symbol is ITypeSymbol type && !type.CanSupportCollectionInitializer(containingSymbol))
                    {
                        return false;
                    }
                }
 
                return true;
            }
        }
 
        // for (; |
        // for (; ; |
        if (token.IsKind(SyntaxKind.SemicolonToken) &&
            token.Parent is ForStatementSyntax forStatement)
        {
            if (token == forStatement.FirstSemicolonToken ||
                token == forStatement.SecondSemicolonToken)
            {
                return true;
            }
        }
 
        // for ( |
        if (token.IsKind(SyntaxKind.OpenParenToken) &&
            token.Parent is ForStatementSyntax forStatement2 &&
            token == forStatement2.OpenParenToken)
        {
            return true;
        }
 
        // for (; ; Goo(), | 
        // for ( Goo(), |
        if (token.IsKind(SyntaxKind.CommaToken) &&
            token.Parent.IsKind(SyntaxKind.ForStatement))
        {
            return true;
        }
 
        // foreach (var v in |
        // await foreach (var v in |
        // from a in |
        // join b in |
        if (token.IsKind(SyntaxKind.InKeyword))
        {
            if (token.Parent is (kind:
                    SyntaxKind.ForEachStatement or
                    SyntaxKind.ForEachVariableStatement or
                    SyntaxKind.FromClause or
                    SyntaxKind.JoinClause))
            {
                return true;
            }
        }
 
        // join x in y on |
        // join x in y on a equals |
        if (token.Kind() is SyntaxKind.OnKeyword or SyntaxKind.EqualsKeyword &&
            token.Parent.IsKind(SyntaxKind.JoinClause))
        {
            return true;
        }
 
        // where |
        if (token.IsKind(SyntaxKind.WhereKeyword) &&
            token.Parent.IsKind(SyntaxKind.WhereClause))
        {
            return true;
        }
 
        // orderby |
        // orderby a, |
        if (token.Kind() is SyntaxKind.OrderByKeyword or SyntaxKind.CommaToken &&
            token.Parent.IsKind(SyntaxKind.OrderByClause))
        {
            return true;
        }
 
        // select |
        if (token.IsKind(SyntaxKind.SelectKeyword) &&
            token.Parent.IsKind(SyntaxKind.SelectClause))
        {
            return true;
        }
 
        // group |
        // group expr by |
        if (token.Kind() is SyntaxKind.GroupKeyword or SyntaxKind.ByKeyword &&
            token.Parent.IsKind(SyntaxKind.GroupClause))
        {
            return true;
        }
 
        // return |
        // yield return |
        // but not: [return |
        if (token.IsKind(SyntaxKind.ReturnKeyword))
        {
            if (token.GetPreviousToken(includeSkipped: true).Kind() != SyntaxKind.OpenBracketToken)
            {
                return true;
            }
        }
 
        // throw |
        if (token.IsKind(SyntaxKind.ThrowKeyword))
        {
            return true;
        }
 
        // while ( |
        if (token.IsKind(SyntaxKind.OpenParenToken) &&
            token.GetPreviousToken(includeSkipped: true).IsKind(SyntaxKind.WhileKeyword))
        {
            return true;
        }
 
        // todo: handle 'for' cases.
 
        // using ( |
        // await using ( |
        if (token.IsKind(SyntaxKind.OpenParenToken) && token.Parent.IsKind(SyntaxKind.UsingStatement))
        {
            return true;
        }
 
        // lock ( |
        if (token.IsKind(SyntaxKind.OpenParenToken) &&
            token.GetPreviousToken(includeSkipped: true).IsKind(SyntaxKind.LockKeyword))
        {
            return true;
        }
 
        // lock ( |
        if (token.IsKind(SyntaxKind.OpenParenToken) &&
            token.GetPreviousToken(includeSkipped: true).IsKind(SyntaxKind.IfKeyword))
        {
            return true;
        }
 
        // switch ( |
        if (token.IsKind(SyntaxKind.OpenParenToken) &&
            token.GetPreviousToken(includeSkipped: true).IsKind(SyntaxKind.SwitchKeyword))
        {
            return true;
        }
 
        // checked ( |
        if (token.IsKind(SyntaxKind.OpenParenToken) &&
            token.GetPreviousToken(includeSkipped: true).IsKind(SyntaxKind.CheckedKeyword))
        {
            return true;
        }
 
        // unchecked ( |
        if (token.IsKind(SyntaxKind.OpenParenToken) &&
            token.GetPreviousToken(includeSkipped: true).IsKind(SyntaxKind.UncheckedKeyword))
        {
            return true;
        }
 
        // when ( |
        if (token.IsKind(SyntaxKind.OpenParenToken) &&
            token.GetPreviousToken(includeSkipped: true).IsKind(SyntaxKind.WhenKeyword))
        {
            return true;
        }
 
        // case ... when |
        if (token.IsKind(SyntaxKind.WhenKeyword) && token.Parent.IsKind(SyntaxKind.WhenClause))
        {
            return true;
        }
 
        // (SomeType) |
        if (token.IsAfterPossibleCast())
        {
            return true;
        }
 
        // In anonymous type initializer.
        //
        // new { | We allow new inside of anonymous object member declarators, so that the user
        // can dot into a member afterward. For example:
        //
        // var a = new { new C().Goo };
        if (token.Kind() is SyntaxKind.OpenBraceToken or SyntaxKind.CommaToken &&
            token.Parent.IsKind(SyntaxKind.AnonymousObjectCreationExpression))
        {
            return true;
        }
 
        // List patterns
        // is [ |
        // is [ 0, |
        if (token.Kind() is SyntaxKind.OpenBracketToken or SyntaxKind.CommaToken &&
            token.Parent.IsKind(SyntaxKind.ListPattern))
        {
            return true;
        }
 
        // Collection expressions
        // [|
        // [0, |
        if (token.Kind() is SyntaxKind.OpenBracketToken or SyntaxKind.DotDotToken or SyntaxKind.CommaToken &&
            token.Parent.IsKind(SyntaxKind.CollectionExpression))
        {
            return true;
        }
 
        // Spread elements in collection expressions
        // [.. |
        // [0, .. |
        if (token.Kind() is SyntaxKind.DotDotToken &&
            token.Parent.IsKind(SyntaxKind.SpreadElement))
        {
            return true;
        }
 
        // $"{ |
        // $@"{ |
        // $"""{ |
        // $"{x} { |
        // $@"{x} { |
        // $"""{x} { |
        if (token.IsKind(SyntaxKind.OpenBraceToken))
        {
            return token.Parent is InterpolationSyntax interpolation
                && interpolation.OpenBraceToken == token;
        }
 
        return false;
    }
 
    public static bool IsInvocationOfVarExpression(this SyntaxToken token)
        => token.Parent?.Parent is InvocationExpressionSyntax invocation &&
           invocation.Expression.ToString() == "var";
 
    public static bool IsNameOfContext(this SyntaxTree syntaxTree, int position, SemanticModel? semanticModelOpt = null, CancellationToken cancellationToken = default)
    {
        var token = syntaxTree.FindTokenOnLeftOfPosition(position, cancellationToken);
        token = token.GetPreviousTokenIfTouchingWord(position);
 
        // nameof(Goo.|
        // nameof(Goo.Bar.|
        // Locate the open paren.
        if (token.IsKind(SyntaxKind.DotToken))
        {
            // Could have been parsed as member access
            if (token.Parent.IsKind(SyntaxKind.SimpleMemberAccessExpression))
            {
                var parentMemberAccess = token.Parent;
                while (parentMemberAccess.Parent.IsKind(SyntaxKind.SimpleMemberAccessExpression))
                {
                    parentMemberAccess = parentMemberAccess.Parent;
                }
 
                if (parentMemberAccess.Parent.IsKind(SyntaxKind.Argument) &&
                    parentMemberAccess.Parent.IsChildNode<ArgumentListSyntax>(a => a.Arguments.FirstOrDefault()))
                {
                    token = ((ArgumentListSyntax)parentMemberAccess.Parent.Parent!).OpenParenToken;
                }
            }
 
            // Could have been parsed as a qualified name.
            if (token.Parent.IsKind(SyntaxKind.QualifiedName))
            {
                var parentQualifiedName = token.Parent;
                while (parentQualifiedName.Parent.IsKind(SyntaxKind.QualifiedName))
                {
                    parentQualifiedName = parentQualifiedName.Parent;
                }
 
                if (parentQualifiedName.Parent.IsKind(SyntaxKind.Argument) &&
                    parentQualifiedName.Parent.IsChildNode<ArgumentListSyntax>(a => a.Arguments.FirstOrDefault()))
                {
                    token = ((ArgumentListSyntax)parentQualifiedName.Parent.Parent!).OpenParenToken;
                }
            }
        }
 
        ExpressionSyntax? parentExpression = null;
 
        // if the nameof expression has a missing close paren, it is parsed as an invocation expression.
        if (token.Parent.IsKind(SyntaxKind.ArgumentList) &&
            token.Parent.Parent is InvocationExpressionSyntax invocationExpression &&
            invocationExpression.IsNameOfInvocation())
        {
            parentExpression = invocationExpression;
        }
 
        if (parentExpression != null)
        {
            if (semanticModelOpt == null)
            {
                return true;
            }
 
            return semanticModelOpt.GetSymbolInfo(parentExpression, cancellationToken).Symbol == null;
        }
 
        return false;
    }
 
    public static bool IsIsOrAsOrSwitchOrWithExpressionContext(
        this SyntaxTree syntaxTree,
        SemanticModel semanticModel,
        int position,
        SyntaxToken tokenOnLeftOfPosition,
        CancellationToken cancellationToken)
    {
        // cases:
        //    expr |
 
        var token = tokenOnLeftOfPosition;
        token = token.GetPreviousTokenIfTouchingWord(position);
 
        // Not if the position is a numeric literal
        if (token.IsKind(SyntaxKind.NumericLiteralToken))
            return false;
 
        if (token.GetAncestor<BlockSyntax>() == null &&
            token.GetAncestor<ArrowExpressionClauseSyntax>() == null)
        {
            return false;
        }
 
        // is/as/with are valid after expressions.
        if (token.IsLastTokenOfNode<ExpressionSyntax>(out var expression))
        {
            // 'is/as/with' not allowed after a anonymous-method/lambda/method-group.
            if (expression is AnonymousFunctionExpressionSyntax)
                return false;
 
            var symbol = semanticModel.GetSymbolInfo(expression, cancellationToken).GetAnySymbol();
            if (symbol is IMethodSymbol)
                return false;
 
            // However, many names look like expressions.  For example:
            //    foreach (var |
            // ('var' is a TypeSyntax which is an expression syntax.
 
            var type = token.GetAncestors<TypeSyntax>().LastOrDefault();
            if (type == null)
                return true;
 
            if (type.Kind() is SyntaxKind.GenericName or SyntaxKind.AliasQualifiedName or SyntaxKind.PredefinedType)
                return false;
 
            ExpressionSyntax nameExpr = type;
            if (IsRightSideName(nameExpr))
            {
                nameExpr = (ExpressionSyntax)nameExpr.Parent!;
            }
 
            // If this name is the start of a local variable declaration context, we
            // shouldn't show is or as. For example: for(var |
            if (syntaxTree.IsLocalVariableDeclarationContext(token.SpanStart, syntaxTree.FindTokenOnLeftOfPosition(token.SpanStart, cancellationToken), cancellationToken))
                return false;
 
            // Not on the left hand side of an object initializer
            if (token.IsKind(SyntaxKind.IdentifierToken) &&
                token.Parent.IsKind(SyntaxKind.IdentifierName) &&
                token.Parent.Parent is (kind: SyntaxKind.ObjectInitializerExpression or SyntaxKind.CollectionInitializerExpression))
            {
                return false;
            }
 
            // Not after an 'out' declaration expression. For example: M(out var |
            if (token.Kind() is SyntaxKind.IdentifierToken &&
                token.Parent.IsKind(SyntaxKind.IdentifierName))
            {
                if (token.Parent.Parent is ArgumentSyntax { RefOrOutKeyword.RawKind: (int)SyntaxKind.OutKeyword })
                    return false;
            }
 
            if (token.Text == SyntaxFacts.GetText(SyntaxKind.AsyncKeyword))
            {
                // async $$
                //
                // 'async' will look like a normal identifier.  But we don't want to follow it
                // with 'is' or 'as' or 'with' if it's actually the start of a lambda.
                var delegateType = CSharpTypeInferenceService.Instance.InferDelegateType(
                    semanticModel, token.SpanStart, cancellationToken);
                if (delegateType != null)
                {
                    return false;
                }
            }
 
            // case X $$
            //
            // while `X` is in an expr context, it's a limited one that doesn't support the full breadth of operators like these.
            var tokenBeforeName = syntaxTree.FindTokenOnLeftOfPosition(nameExpr.SpanStart, cancellationToken);
            if (tokenBeforeName.Kind() == SyntaxKind.CaseKeyword)
                return false;
 
            // Now, make sure the name was actually in a location valid for
            // an expression.  If so, then we know we can follow it.
            if (syntaxTree.IsExpressionContext(nameExpr.SpanStart, tokenBeforeName, attributes: false, cancellationToken))
                return true;
        }
 
        return false;
    }
 
    private static bool IsRightSideName(ExpressionSyntax name)
    {
        if (name.Parent != null)
        {
            switch (name.Parent.Kind())
            {
                case SyntaxKind.QualifiedName:
                    return ((QualifiedNameSyntax)name.Parent).Right == name;
                case SyntaxKind.AliasQualifiedName:
                    return ((AliasQualifiedNameSyntax)name.Parent).Name == name;
                case SyntaxKind.SimpleMemberAccessExpression:
                    return ((MemberAccessExpressionSyntax)name.Parent).Name == name;
            }
        }
 
        return false;
    }
 
    public static bool IsCatchOrFinallyContext(
        this SyntaxTree syntaxTree, int position, SyntaxToken tokenOnLeftOfPosition)
    {
        // cases:
        // try { 
        // } |
 
        // try {
        // } c|
 
        // try {
        // } catch {
        // } |
 
        // try {
        // } catch {
        // } c|
 
        var token = tokenOnLeftOfPosition;
        token = token.GetPreviousTokenIfTouchingWord(position);
 
        if (token.IsKind(SyntaxKind.CloseBraceToken))
        {
            var block = token.GetAncestor<BlockSyntax>();
 
            if (block != null &&
                token == block.GetLastToken(includeSkipped: true) &&
                block.Parent?.Kind() is SyntaxKind.TryStatement or SyntaxKind.CatchClause)
            {
                return true;
            }
        }
 
        return false;
    }
 
    public static bool IsCatchFilterContext(this SyntaxTree syntaxTree, int position, SyntaxToken tokenOnLeftOfPosition)
    {
        // cases:
        //  catch |
        //  catch i|
        //  catch (declaration) |
        //  catch (declaration) i|
 
        var token = tokenOnLeftOfPosition;
        token = token.GetPreviousTokenIfTouchingWord(position);
 
        if (CodeAnalysis.CSharpExtensions.IsKind(token, SyntaxKind.CatchKeyword))
        {
            return true;
        }
 
        if (CodeAnalysis.CSharpExtensions.IsKind(token, SyntaxKind.CloseParenToken) &&
            token.Parent.IsKind(SyntaxKind.CatchDeclaration))
        {
            return true;
        }
 
        return false;
    }
 
    public static bool IsEnumBaseListContext(this SyntaxTree syntaxTree, SyntaxToken targetToken)
    {
        // Options:
        //  enum E : |
        //  enum E : i|
 
        return
            targetToken.IsKind(SyntaxKind.ColonToken) &&
            targetToken.Parent.IsKind(SyntaxKind.BaseList) &&
            targetToken.Parent.IsParentKind(SyntaxKind.EnumDeclaration);
    }
 
    public static bool IsEnumTypeMemberAccessContext(this SyntaxTree syntaxTree, SemanticModel semanticModel, int position, CancellationToken cancellationToken)
    {
        var token = syntaxTree
            .FindTokenOnLeftOfPosition(position, cancellationToken)
            .GetPreviousTokenIfTouchingWord(position);
 
        if (!token.IsKind(SyntaxKind.DotToken))
        {
            return false;
        }
 
        SymbolInfo leftHandBinding;
        if (token.Parent.IsKind(SyntaxKind.SimpleMemberAccessExpression))
        {
            var memberAccess = (MemberAccessExpressionSyntax)token.Parent;
            leftHandBinding = semanticModel.GetSymbolInfo(memberAccess.Expression, cancellationToken);
        }
        else if (token.Parent is QualifiedNameSyntax qualifiedName &&
            token.Parent?.Parent is BinaryExpressionSyntax(SyntaxKind.IsExpression) binaryExpression &&
            binaryExpression.Right == qualifiedName)
        {
            // The right-hand side of an is expression could be an enum
            leftHandBinding = semanticModel.GetSymbolInfo(qualifiedName.Left, cancellationToken);
        }
        else if (token.Parent is QualifiedNameSyntax qualifiedName1 &&
            token.Parent?.Parent is DeclarationPatternSyntax declarationExpression &&
            declarationExpression.Type == qualifiedName1)
        {
            // The right-hand side of an is declaration expression could be an enum
            leftHandBinding = semanticModel.GetSymbolInfo(qualifiedName1.Left, cancellationToken);
        }
        else
        {
            return false;
        }
 
        var symbol = leftHandBinding.GetBestOrAllSymbols().FirstOrDefault();
 
        if (symbol == null)
        {
            return false;
        }
 
        switch (symbol.Kind)
        {
            case SymbolKind.NamedType:
                return ((INamedTypeSymbol)symbol).TypeKind == TypeKind.Enum;
            case SymbolKind.Alias:
                var target = ((IAliasSymbol)symbol).Target;
                return target.IsType && ((ITypeSymbol)target).TypeKind == TypeKind.Enum;
        }
 
        return false;
    }
 
    public static bool IsFunctionPointerCallingConventionContext(this SyntaxTree syntaxTree, SyntaxToken targetToken)
    {
        return targetToken.IsKind(SyntaxKind.AsteriskToken) &&
               targetToken.Parent is FunctionPointerTypeSyntax functionPointerType &&
               targetToken == functionPointerType.AsteriskToken;
    }
}