// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.LanguageService
{
    internal static class ISyntaxFactsExtensions
    {
        private static readonly ObjectPool<Stack<(SyntaxNodeOrToken nodeOrToken, bool leading, bool trailing)>> s_stackPool
            = SharedPools.Default<Stack<(SyntaxNodeOrToken nodeOrToken, bool leading, bool trailing)>>();

        public static bool IsMemberInitializerNamedAssignmentIdentifier(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => syntaxFacts.IsMemberInitializerNamedAssignmentIdentifier(node, out _);

        public static bool IsOnSingleLine(this ISyntaxFacts syntaxFacts, SyntaxNode node, bool fullSpan)
        {
            // The stack logic assumes the initial node is not null
            Contract.ThrowIfNull(node);

            // Use an actual Stack so we can write out deeply recursive structures without overflowing.
            // Note: algorithm is taken from GreenNode.WriteTo.
            //
            // General approach is that we recurse down the nodes, using a real stack object to
            // keep track of what node we're on.  If full-span is true we'll examine all tokens
            // and all the trivia on each token.  If full-span is false we'll examine all tokens
            // but we'll ignore the leading trivia on the very first trivia and the trailing trivia
            // on the very last token.
            var stack = s_stackPool.Allocate();
            stack.Push((node, leading: fullSpan, trailing: fullSpan));

            var result = IsOnSingleLine(syntaxFacts, stack);

            s_stackPool.ClearAndFree(stack);

            return result;
        }

        private static bool IsOnSingleLine(
            ISyntaxFacts syntaxFacts, Stack<(SyntaxNodeOrToken nodeOrToken, bool leading, bool trailing)> stack)
        {
            while (stack.Count > 0)
            {
                var (currentNodeOrToken, currentLeading, currentTrailing) = stack.Pop();
                if (currentNodeOrToken.IsToken)
                {
                    // If this token isn't on a single line, then the original node definitely
                    // isn't on a single line.
                    if (!IsOnSingleLine(syntaxFacts, currentNodeOrToken.AsToken(), currentLeading, currentTrailing))
                        return false;
                }
                else
                {
                    var currentNode = currentNodeOrToken.AsNode()!;

                    var childNodesAndTokens = currentNode.ChildNodesAndTokens();
                    var childCount = childNodesAndTokens.Count;

                    // Walk the children of this node in reverse, putting on the stack to process.
                    // This way we process the children in the actual child-order they are in for
                    // this node.
                    var index = 0;
                    foreach (var child in childNodesAndTokens.Reverse())
                    {
                        // Since we're walking the children in reverse, if we're on hte 0th item,
                        // that's the last child.
                        var last = index == 0;

                        // Once we get all the way to the end of the reversed list, we're actually
                        // on the first.
                        var first = index == childCount - 1;

                        // We want the leading trivia if we've asked for it, or if we're not the first
                        // token being processed.  We want the trailing trivia if we've asked for it,
                        // or if we're not the last token being processed.
                        stack.Push((child, currentLeading | !first, currentTrailing | !last));
                        index++;
                    }
                }
            }

            // All tokens were on a single line.  This node is on a single line.
            return true;
        }

        private static bool IsOnSingleLine(ISyntaxFacts syntaxFacts, SyntaxToken token, bool leading, bool trailing)
        {
            // If any of our trivia is not on a single line, then we're not on a single line.
            if (!IsOnSingleLine(syntaxFacts, token.LeadingTrivia, leading) ||
                !IsOnSingleLine(syntaxFacts, token.TrailingTrivia, trailing))
            {
                return false;
            }

            // Only string literals can span multiple lines.  Only need to check those.
            if (syntaxFacts.SyntaxKinds.StringLiteralToken == token.RawKind ||
                syntaxFacts.SyntaxKinds.InterpolatedStringTextToken == token.RawKind)
            {
                // This allocated.  But we only do it in the string case. For all other tokens
                // we don't need any allocations.
                if (!IsOnSingleLine(token.ToString()))
                {
                    return false;
                }
            }

            // Any other type of token is on a single line.
            return true;
        }

        private static bool IsOnSingleLine(ISyntaxFacts syntaxFacts, SyntaxTriviaList triviaList, bool checkTrivia)
        {
            if (checkTrivia)
            {
                foreach (var trivia in triviaList)
                {
                    if (trivia.HasStructure)
                    {
                        // For structured trivia, we recurse into the trivia to see if it
                        // is on a single line or not.  If it isn't, then we're definitely
                        // not on a single line.
                        if (!IsOnSingleLine(syntaxFacts, trivia.GetStructure()!, fullSpan: true))
                        {
                            return false;
                        }
                    }
                    else if (syntaxFacts.IsEndOfLineTrivia(trivia))
                    {
                        // Contained an end-of-line trivia.  Definitely not on a single line.
                        return false;
                    }
                    else if (!syntaxFacts.IsWhitespaceTrivia(trivia))
                    {
                        // Was some other form of trivia (like a comment).  Easiest thing
                        // to do is just stringify this and count the number of newlines.
                        // these should be rare.  So the allocation here is ok.
                        if (!IsOnSingleLine(trivia.ToString()))
                        {
                            return false;
                        }
                    }
                }
            }

            return true;
        }

        private static bool IsOnSingleLine(string value)
            => value.GetNumberOfLineBreaks() == 0;

        public static bool ContainsInterleavedDirective(
            this ISyntaxFacts syntaxFacts, ImmutableArray<SyntaxNode> nodes, CancellationToken cancellationToken)
        {
            if (nodes.Length > 0)
            {
                var span = TextSpan.FromBounds(nodes.First().Span.Start, nodes.Last().Span.End);

                foreach (var node in nodes)
                {
                    cancellationToken.ThrowIfCancellationRequested();

                    if (ContainsInterleavedDirective(syntaxFacts, span, node, cancellationToken))
                        return true;
                }
            }

            return false;
        }

        public static bool ContainsInterleavedDirective(this ISyntaxFacts syntaxFacts, SyntaxNode node, CancellationToken cancellationToken)
            => ContainsInterleavedDirective(syntaxFacts, node.Span, node, cancellationToken);

        public static bool ContainsInterleavedDirective(
            this ISyntaxFacts syntaxFacts, TextSpan span, SyntaxNode node, CancellationToken cancellationToken)
        {
            foreach (var token in node.DescendantTokens())
            {
                if (syntaxFacts.ContainsInterleavedDirective(span, token, cancellationToken))
                    return true;
            }

            return false;
        }

        public static bool SpansPreprocessorDirective(this ISyntaxFacts syntaxFacts, IEnumerable<SyntaxNode> nodes)
        {
            if (nodes == null || nodes.IsEmpty())
            {
                return false;
            }

            return SpansPreprocessorDirective(syntaxFacts, nodes.SelectMany(n => n.DescendantTokens()));
        }

        /// <summary>
        /// Determines if there is preprocessor trivia *between* any of the <paramref name="tokens"/>
        /// provided.  The <paramref name="tokens"/> will be deduped and then ordered by position.
        /// Specifically, the first token will not have it's leading trivia checked, and the last
        /// token will not have it's trailing trivia checked.  All other trivia will be checked to
        /// see if it contains a preprocessor directive.
        /// </summary>
        public static bool SpansPreprocessorDirective(this ISyntaxFacts syntaxFacts, IEnumerable<SyntaxToken> tokens)
        {
            // we want to check all leading trivia of all tokens (except the 
            // first one), and all trailing trivia of all tokens (except the
            // last one).

            var first = true;
            var previousToken = default(SyntaxToken);

            // Allow duplicate nodes/tokens to be passed in.  Also, allow the nodes/tokens
            // to not be in any particular order when passed in.
            var orderedTokens = tokens.Distinct().OrderBy(t => t.SpanStart);

            foreach (var token in orderedTokens)
            {
                if (first)
                {
                    first = false;
                }
                else
                {
                    // check the leading trivia of this token, and the trailing trivia
                    // of the previous token.
                    if (SpansPreprocessorDirective(syntaxFacts, token.LeadingTrivia) ||
                        SpansPreprocessorDirective(syntaxFacts, previousToken.TrailingTrivia))
                    {
                        return true;
                    }
                }

                previousToken = token;
            }

            return false;
        }

        private static bool SpansPreprocessorDirective(this ISyntaxFacts syntaxFacts, SyntaxTriviaList list)
            => list.Any(syntaxFacts.IsPreprocessorDirective);

        public static bool IsLegalIdentifier(this ISyntaxFacts syntaxFacts, string name)
        {
            if (name.Length == 0)
            {
                return false;
            }

            if (!syntaxFacts.IsIdentifierStartCharacter(name[0]))
            {
                return false;
            }

            for (var i = 1; i < name.Length; i++)
            {
                if (!syntaxFacts.IsIdentifierPartCharacter(name[i]))
                {
                    return false;
                }
            }

            return true;
        }

        public static bool IsReservedOrContextualKeyword(this ISyntaxFacts syntaxFacts, SyntaxToken token)
            => syntaxFacts.IsReservedKeyword(token) || syntaxFacts.IsContextualKeyword(token);

        public static bool IsWord(this ISyntaxFacts syntaxFacts, SyntaxToken token)
        {
            return syntaxFacts.IsIdentifier(token)
                || syntaxFacts.IsReservedOrContextualKeyword(token)
                || syntaxFacts.IsPreprocessorKeyword(token);
        }

        public static bool IsRegularOrDocumentationComment(this ISyntaxFacts syntaxFacts, SyntaxTrivia trivia)
            => syntaxFacts.IsRegularComment(trivia) || syntaxFacts.IsDocumentationComment(trivia);

        [return: NotNullIfNotNull(nameof(node))]
        public static SyntaxNode? WalkDownParentheses(this ISyntaxFacts syntaxFacts, SyntaxNode? node)
        {
            while (syntaxFacts.IsParenthesizedExpression(node))
            {
                syntaxFacts.GetPartsOfParenthesizedExpression(node, out _, out var child, out _);
                node = child;
            }

            return node;
        }

        [return: NotNullIfNotNull(nameof(node))]
        public static SyntaxNode? WalkUpParentheses(this ISyntaxFacts syntaxFacts, SyntaxNode? node)
        {
            while (syntaxFacts.IsParenthesizedExpression(node?.Parent))
                node = node.Parent;

            return node;
        }

        public static void GetPartsOfAssignmentStatement(
            this ISyntaxFacts syntaxFacts, SyntaxNode statement,
            out SyntaxNode left, out SyntaxNode right)
        {
            syntaxFacts.GetPartsOfAssignmentStatement(statement, out left, out _, out right);
        }

        public static SyntaxNode GetExpressionOfInvocationExpression(
            this ISyntaxFacts syntaxFacts, SyntaxNode node)
        {
            syntaxFacts.GetPartsOfInvocationExpression(node, out var expression, out _);
            return expression;
        }

        public static SyntaxNode Unparenthesize(
            this ISyntaxFacts syntaxFacts, SyntaxNode node)
        {
            SyntaxToken openParenToken;
            SyntaxNode operand;
            SyntaxToken closeParenToken;

            if (syntaxFacts.IsParenthesizedPattern(node))
            {
                syntaxFacts.GetPartsOfParenthesizedPattern(node,
                    out openParenToken, out operand, out closeParenToken);
            }
            else
            {
                syntaxFacts.GetPartsOfParenthesizedExpression(node,
                    out openParenToken, out operand, out closeParenToken);
            }

            var leadingTrivia = openParenToken.LeadingTrivia
                .Concat(openParenToken.TrailingTrivia)
                .Where(t => !syntaxFacts.IsElastic(t))
                .Concat(operand.GetLeadingTrivia());

            var trailingTrivia = operand.GetTrailingTrivia()
                .Concat(closeParenToken.LeadingTrivia)
                .Where(t => !syntaxFacts.IsElastic(t))
                .Concat(closeParenToken.TrailingTrivia);

            var resultNode = operand
                .WithLeadingTrivia(leadingTrivia)
                .WithTrailingTrivia(trailingTrivia);

            // If there's no trivia between the original node and the tokens around it, then add
            // elastic markers so the formatting engine will spaces if necessary to keep things
            // parseable.
            if (resultNode.GetLeadingTrivia().Count == 0)
            {
                var previousToken = node.GetFirstToken().GetPreviousToken();
                if (previousToken.TrailingTrivia.Count == 0 &&
                    syntaxFacts.IsWordOrNumber(previousToken) &&
                    syntaxFacts.IsWordOrNumber(resultNode.GetFirstToken()))
                {
                    resultNode = resultNode.WithPrependedLeadingTrivia(syntaxFacts.ElasticMarker);
                }
            }

            if (resultNode.GetTrailingTrivia().Count == 0)
            {
                var nextToken = node.GetLastToken().GetNextToken();
                if (nextToken.LeadingTrivia.Count == 0 &&
                    syntaxFacts.IsWordOrNumber(nextToken) &&
                    syntaxFacts.IsWordOrNumber(resultNode.GetLastToken()))
                {
                    resultNode = resultNode.WithAppendedTrailingTrivia(syntaxFacts.ElasticMarker);
                }
            }

            return resultNode;
        }

        private static bool IsWordOrNumber(this ISyntaxFacts syntaxFacts, SyntaxToken token)
            => syntaxFacts.IsWord(token) || syntaxFacts.IsNumericLiteral(token);

        public static bool SpansPreprocessorDirective(this ISyntaxFacts service, SyntaxNode node)
            => service.SpansPreprocessorDirective(SpecializedCollections.SingletonEnumerable(node));

        public static bool SpansPreprocessorDirective(this ISyntaxFacts service, params SyntaxNode[] nodes)
            => service.SpansPreprocessorDirective((IEnumerable<SyntaxNode>)nodes);

        public static bool IsWhitespaceOrEndOfLineTrivia(this ISyntaxFacts syntaxFacts, SyntaxTrivia trivia)
            => syntaxFacts.IsWhitespaceTrivia(trivia) || syntaxFacts.IsEndOfLineTrivia(trivia);

        public static void GetPartsOfBinaryExpression(this ISyntaxFacts syntaxFacts, SyntaxNode node, out SyntaxNode left, out SyntaxNode right)
            => syntaxFacts.GetPartsOfBinaryExpression(node, out left, out _, out right);

        public static SyntaxNode GetPatternOfParenthesizedPattern(this ISyntaxFacts syntaxFacts, SyntaxNode node)
        {
            syntaxFacts.GetPartsOfParenthesizedPattern(node, out _, out var pattern, out _);
            return pattern;
        }

        public static SyntaxToken GetOperatorTokenOfBinaryExpression(this ISyntaxFacts syntaxFacts, SyntaxNode node)
        {
            syntaxFacts.GetPartsOfBinaryExpression(node, out _, out var token, out _);
            return token;
        }

        public static bool IsAnonymousOrLocalFunction(this ISyntaxFacts syntaxFacts, SyntaxNode node)
            => syntaxFacts.IsAnonymousFunctionExpression(node) ||
               syntaxFacts.IsLocalFunctionStatement(node);

        public static SyntaxNode? GetExpressionOfElementAccessExpression(this ISyntaxFacts syntaxFacts, SyntaxNode node)
        {
            syntaxFacts.GetPartsOfElementAccessExpression(node, out var expression, out _);
            return expression;
        }

        public static SyntaxNode? GetArgumentListOfElementAccessExpression(this ISyntaxFacts syntaxFacts, SyntaxNode node)
        {
            syntaxFacts.GetPartsOfElementAccessExpression(node, out _, out var argumentList);
            return argumentList;
        }

        public static SyntaxNode GetExpressionOfConditionalAccessExpression(this ISyntaxFacts syntaxFacts, SyntaxNode node)
        {
            syntaxFacts.GetPartsOfConditionalAccessExpression(node, out var expression, out _);
            return expression;
        }

        public static SyntaxToken GetOperatorTokenOfMemberAccessExpression(this ISyntaxFacts syntaxFacts, SyntaxNode node)
        {
            syntaxFacts.GetPartsOfMemberAccessExpression(node, out _, out var operatorToken, out _);
            return operatorToken;
        }

        public static void GetPartsOfMemberAccessExpression(this ISyntaxFacts syntaxFacts, SyntaxNode node, out SyntaxNode expression, out SyntaxNode name)
            => syntaxFacts.GetPartsOfMemberAccessExpression(node, out expression, out _, out name);

        public static void GetPartsOfConditionalAccessExpression(this ISyntaxFacts syntaxFacts, SyntaxNode node, out SyntaxNode expression, out SyntaxNode whenNotNull)
            => syntaxFacts.GetPartsOfConditionalAccessExpression(node, out expression, out _, out whenNotNull);

        public static TextSpan GetSpanWithoutAttributes(this ISyntaxFacts syntaxFacts, SyntaxNode root, SyntaxNode node)
        {
            // Span without AttributeLists
            // - No AttributeLists -> original .Span
            // - Some AttributeLists -> (first non-trivia/comment Token.Span.Begin, original.Span.End)
            //   - We need to be mindful about comments due to:
            //      // [Test1]
            //      //Comment1
            //      [||]object Property1 { get; set; }
            //     the comment node being part of the next token's (`object`) leading trivia and not the AttributeList's node.
            // - In case only attribute is written we need to be careful to not to use next (unrelated) token as beginning current the node.
            var attributeList = syntaxFacts.GetAttributeLists(node);
            if (attributeList.Any())
            {
                var endOfAttributeLists = attributeList.Last().Span.End;
                var afterAttributesToken = root.FindTokenOnRightOfPosition(endOfAttributeLists);

                var endOfNode = node.Span.End;
                var startOfNodeWithoutAttributes = Math.Min(afterAttributesToken.Span.Start, endOfNode);

                return TextSpan.FromBounds(startOfNodeWithoutAttributes, endOfNode);
            }

            return node.Span;
        }

        /// <summary>
        /// Similar to <see cref="ISyntaxFacts.GetStandaloneExpression(SyntaxNode)"/>, this gets the containing
        /// expression that is actually a language expression and not just typed as an ExpressionSyntax for convenience.
        /// However, this goes beyond that that method in that if this expression is the RHS of a conditional access
        /// (i.e. <c>a?.b()</c>) it will also return the root of the conditional access expression tree.
        /// <para/> The intuition here is that this will give the topmost expression node that could realistically be
        /// replaced with any other expression.  For example, with <c>a?.b()</c> technically <c>.b()</c> is an
        /// expression.  But that cannot be replaced with something like <c>(1 + 1)</c> (as <c>a?.(1 + 1)</c> is not
        /// legal).  However, in <c>a?.b()</c>, then <c>a</c> itself could be replaced with <c>(1 + 1)?.b()</c> to form
        /// a legal expression.
        /// </summary>
        public static SyntaxNode GetRootStandaloneExpression(this ISyntaxFacts syntaxFacts, SyntaxNode node)
        {
            // First, make sure we're on a construct the language things is a standalone expression.
            var standalone = syntaxFacts.GetStandaloneExpression(node);

            // Then, if this is the RHS of a `?`, walk up to the top of that tree to get the final standalone expression.
            return syntaxFacts.GetRootConditionalAccessExpression(standalone) ?? standalone;
        }

        #region GetXXXOfYYY Members

        public static SyntaxNode? GetArgumentListOfInvocationExpression(this ISyntaxFacts syntaxFacts, SyntaxNode node)
        {
            syntaxFacts.GetPartsOfInvocationExpression(node, out _, out var argumentList);
            return argumentList;
        }

        public static SeparatedSyntaxList<SyntaxNode> GetArgumentsOfInvocationExpression(this ISyntaxFacts syntaxFacts, SyntaxNode node)
        {
            var argumentList = syntaxFacts.GetArgumentListOfInvocationExpression(node);
            return argumentList is null ? default : syntaxFacts.GetArgumentsOfArgumentList(argumentList);
        }

        public static SyntaxNode? GetArgumentListOfBaseObjectCreationExpression(this ISyntaxFacts syntaxFacts, SyntaxNode node)
        {
            syntaxFacts.GetPartsOfBaseObjectCreationExpression(node, out var argumentList, out _);
            return argumentList;
        }

        public static SyntaxNode? GetDefaultOfParameter(this ISyntaxFacts syntaxFacts, SyntaxNode node)
        {
            syntaxFacts.GetPartsOfParameter(node, out _, out var @default);
            return @default;
        }

        public static SyntaxNode GetExpressionOfParenthesizedExpression(this ISyntaxFacts syntaxFacts, SyntaxNode node)
        {
            syntaxFacts.GetPartsOfParenthesizedExpression(node, out _, out var expression, out _);
            return expression;
        }

        public static SyntaxToken GetIdentifierOfGenericName(this ISyntaxFacts syntaxFacts, SyntaxNode node)
        {
            syntaxFacts.GetPartsOfGenericName(node, out var identifier, out _);
            return identifier;
        }

        public static SyntaxToken GetIdentifierOfIdentifierName(this ISyntaxFacts syntaxFacts, SyntaxNode node)
            => syntaxFacts.GetIdentifierOfSimpleName(node);

        public static SyntaxToken GetIdentifierOfParameter(this ISyntaxFacts syntaxFacts, SyntaxNode node)
        {
            syntaxFacts.GetPartsOfParameter(node, out var identifier, out _);
            return identifier;
        }

        public static SyntaxList<SyntaxNode> GetImportsOfBaseNamespaceDeclaration(this ISyntaxFacts syntaxFacts, SyntaxNode node)
        {
            syntaxFacts.GetPartsOfBaseNamespaceDeclaration(node, out _, out var imports, out _);
            return imports;
        }

        public static SyntaxList<SyntaxNode> GetImportsOfCompilationUnit(this ISyntaxFacts syntaxFacts, SyntaxNode node)
        {
            syntaxFacts.GetPartsOfCompilationUnit(node, out var imports, out _, out _);
            return imports;
        }

        public static SyntaxNode? GetInitializerOfBaseObjectCreationExpression(this ISyntaxFacts syntaxFacts, SyntaxNode node)
        {
            syntaxFacts.GetPartsOfBaseObjectCreationExpression(node, out _, out var initializer);
            return initializer;
        }

        public static SyntaxList<SyntaxNode> GetMembersOfBaseNamespaceDeclaration(this ISyntaxFacts syntaxFacts, SyntaxNode node)
        {
            syntaxFacts.GetPartsOfBaseNamespaceDeclaration(node, out _, out _, out var members);
            return members;
        }

        public static SyntaxList<SyntaxNode> GetMembersOfCompilationUnit(this ISyntaxFacts syntaxFacts, SyntaxNode node)
        {
            syntaxFacts.GetPartsOfCompilationUnit(node, out _, out _, out var members);
            return members;
        }

        public static SyntaxNode GetNameOfBaseNamespaceDeclaration(this ISyntaxFacts syntaxFacts, SyntaxNode node)
        {
            syntaxFacts.GetPartsOfBaseNamespaceDeclaration(node, out var name, out _, out _);
            return name;
        }

        public static SyntaxNode GetNameOfMemberAccessExpression(this ISyntaxFacts syntaxFacts, SyntaxNode node)
        {
            syntaxFacts.GetPartsOfMemberAccessExpression(node, out _, out var name);
            return name;
        }

        public static SyntaxNode GetOperandOfPrefixUnaryExpression(this ISyntaxFacts syntaxFacts, SyntaxNode node)
        {
            syntaxFacts.GetPartsOfPrefixUnaryExpression(node, out _, out var operand);
            return operand;
        }

        public static SyntaxToken GetOperatorTokenOfPrefixUnaryExpression(this ISyntaxFacts syntaxFacts, SyntaxNode node)
        {
            syntaxFacts.GetPartsOfPrefixUnaryExpression(node, out var operatorToken, out _);
            return operatorToken;
        }

        public static SeparatedSyntaxList<SyntaxNode> GetTypeArgumentsOfGenericName(this ISyntaxFacts syntaxFacts, SyntaxNode node)
        {
            syntaxFacts.GetPartsOfGenericName(node, out _, out var typeArguments);
            return typeArguments;
        }

        public static SyntaxNode GetTypeOfObjectCreationExpression(this ISyntaxFacts syntaxFacts, SyntaxNode node)
        {
            syntaxFacts.GetPartsOfObjectCreationExpression(node, out var type, out _, out _);
            return type;
        }

        #endregion

        #region IsXXXOfYYY members

        public static bool IsExpressionOfAwaitExpression(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
        {
            var parent = node?.Parent;
            if (!syntaxFacts.IsAwaitExpression(parent))
                return false;

            return node == syntaxFacts.GetExpressionOfAwaitExpression(parent);
        }

        public static bool IsExpressionOfInvocationExpression(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
        {
            var parent = node?.Parent;
            if (!syntaxFacts.IsInvocationExpression(parent))
                return false;

            syntaxFacts.GetPartsOfInvocationExpression(parent, out var expression, out _);
            return node == expression;
        }

        public static bool IsExpressionOfMemberAccessExpression(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
        {
            var parent = node?.Parent;
            if (!syntaxFacts.IsMemberAccessExpression(parent))
                return false;

            syntaxFacts.GetPartsOfMemberAccessExpression(parent, out var expression, out _);
            return node == expression;
        }

        public static bool IsRightOfQualifiedName(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
        {
            var parent = node?.Parent;
            if (!syntaxFacts.IsQualifiedName(parent))
                return false;

            syntaxFacts.GetPartsOfQualifiedName(parent, out _, out _, out var right);
            return node == right;
        }

        public static bool IsTypeOfObjectCreationExpression(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
        {
            var parent = node?.Parent;
            if (!syntaxFacts.IsObjectCreationExpression(parent))
                return false;

            syntaxFacts.GetPartsOfObjectCreationExpression(parent, out var type, out _, out _);
            return type == node;
        }

        #endregion

        #region ISyntaxKinds forwarding methods

        #region trivia

        public static bool IsEndOfLineTrivia(this ISyntaxFacts syntaxFacts, SyntaxTrivia trivia)
            => trivia.RawKind == syntaxFacts.SyntaxKinds.EndOfLineTrivia;

        public static bool IsMultiLineCommentTrivia(this ISyntaxFacts syntaxFacts, SyntaxTrivia trivia)
            => trivia.RawKind == syntaxFacts.SyntaxKinds.MultiLineCommentTrivia;

        public static bool IsMultiLineDocCommentTrivia(this ISyntaxFacts syntaxFacts, SyntaxTrivia trivia)
            => trivia.RawKind == syntaxFacts.SyntaxKinds.MultiLineDocCommentTrivia;

        public static bool IsShebangDirectiveTrivia(this ISyntaxFacts syntaxFacts, SyntaxTrivia trivia)
            => trivia.RawKind == syntaxFacts.SyntaxKinds.ShebangDirectiveTrivia;

        public static bool IsSingleLineCommentTrivia(this ISyntaxFacts syntaxFacts, SyntaxTrivia trivia)
            => trivia.RawKind == syntaxFacts.SyntaxKinds.SingleLineCommentTrivia;

        public static bool IsSingleLineDocCommentTrivia(this ISyntaxFacts syntaxFacts, SyntaxTrivia trivia)
            => trivia.RawKind == syntaxFacts.SyntaxKinds.SingleLineDocCommentTrivia;

        public static bool IsWhitespaceTrivia(this ISyntaxFacts syntaxFacts, SyntaxTrivia trivia)
            => trivia.RawKind == syntaxFacts.SyntaxKinds.WhitespaceTrivia;

        public static bool IsSkippedTokensTrivia(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.SkippedTokensTrivia;

        #endregion

        #region keywords

        public static bool IsAwaitKeyword(this ISyntaxFacts syntaxFacts, SyntaxToken token)
            => token.RawKind == syntaxFacts.SyntaxKinds.AwaitKeyword;

        public static bool IsGlobalNamespaceKeyword(this ISyntaxFacts syntaxFacts, SyntaxToken token)
            => token.RawKind == syntaxFacts.SyntaxKinds.GlobalKeyword;

        #endregion

        #region literal tokens

        public static bool IsCharacterLiteral(this ISyntaxFacts syntaxFacts, SyntaxToken token)
            => token.RawKind == syntaxFacts.SyntaxKinds.CharacterLiteralToken;

        public static bool IsStringLiteral(this ISyntaxFacts syntaxFacts, SyntaxToken token)
            => token.RawKind == syntaxFacts.SyntaxKinds.StringLiteralToken;

        #endregion

        #region tokens

        public static bool IsIdentifier(this ISyntaxFacts syntaxFacts, SyntaxToken token)
            => token.RawKind == syntaxFacts.SyntaxKinds.IdentifierToken;

        public static bool IsHashToken(this ISyntaxFacts syntaxFacts, SyntaxToken token)
            => token.RawKind == syntaxFacts.SyntaxKinds.HashToken;

        public static bool IsInterpolatedStringTextToken(this ISyntaxFacts syntaxFacts, SyntaxToken token)
            => token.RawKind == syntaxFacts.SyntaxKinds.InterpolatedStringTextToken;

        #endregion

        #region names

        public static bool IsGenericName(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.GenericName;

        public static bool IsIdentifierName(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.IdentifierName;

        public static bool IsQualifiedName(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.QualifiedName;

        #endregion

        #region types

        public static bool IsTupleType(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.TupleType;

        #endregion

        #region literal expressions

        public static bool IsCharacterLiteralExpression(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.CharacterLiteralExpression;

        public static bool IsDefaultLiteralExpression(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.DefaultLiteralExpression;

        public static bool IsFalseLiteralExpression(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.FalseLiteralExpression;

        public static bool IsNumericLiteralExpression(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.NumericLiteralExpression;

        public static bool IsNullLiteralExpression(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.NullLiteralExpression;

        public static bool IsStringLiteralExpression(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.StringLiteralExpression;

        public static bool IsTrueLiteralExpression(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.TrueLiteralExpression;

        #endregion

        #region expressions

        public static bool IsArrayCreationExpression(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.ArrayCreationExpression;

        public static bool IsAwaitExpression(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.AwaitExpression;

        public static bool IsBaseExpression(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.BaseExpression;

        public static bool IsConditionalExpression(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.ConditionalExpression;

        public static bool IsConditionalAccessExpression(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.ConditionalAccessExpression;

        public static bool IsImplicitArrayCreationExpression(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.ImplicitArrayCreationExpression;

        public static bool IsImplicitObjectCreationExpression(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node != null && node.RawKind == syntaxFacts.SyntaxKinds.ImplicitObjectCreationExpression;

        public static bool IsIndexExpression(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.IndexExpression;

        public static bool IsInterpolatedStringExpression(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.InterpolatedStringExpression;

        public static bool IsInterpolation(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.Interpolation;

        public static bool IsInterpolatedStringText(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.InterpolatedStringText;

        public static bool IsInvocationExpression(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.InvocationExpression;

        public static bool IsIsTypeExpression(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.IsTypeExpression;

        public static bool IsIsNotTypeExpression(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.IsNotTypeExpression;

        public static bool IsIsPatternExpression(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.IsPatternExpression;

        public static bool IsLogicalAndExpression(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.LogicalAndExpression;

        public static bool IsLogicalOrExpression(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.LogicalOrExpression;

        public static bool IsLogicalNotExpression(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.LogicalNotExpression;

        public static bool IsObjectCreationExpression(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.ObjectCreationExpression;

        public static bool IsParenthesizedExpression(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.ParenthesizedExpression;

        public static bool IsQueryExpression(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.QueryExpression;

        public static bool IsRangeExpression(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.RangeExpression;

        public static bool IsRefExpression(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.RefExpression;

        public static bool IsSimpleMemberAccessExpression(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.SimpleMemberAccessExpression;

        public static bool IsThisExpression(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.ThisExpression;

        public static bool IsThrowExpression(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node != null && node.RawKind == syntaxFacts.SyntaxKinds.ThrowExpression;

        public static bool IsTupleExpression(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.TupleExpression;

        public static bool ContainsGlobalStatement(this ISyntaxFacts syntaxFacts, SyntaxNode node)
            => node.ChildNodes().Any(c => c.RawKind == syntaxFacts.SyntaxKinds.GlobalStatement);

        #endregion

        #region pattern

        public static bool IsAndPattern(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.AndPattern;

        public static bool IsConstantPattern(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.ConstantPattern;

        public static bool IsDeclarationPattern(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.DeclarationPattern;

        public static bool IsListPattern(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.ListPattern;

        public static bool IsNotPattern(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.NotPattern;

        public static bool IsOrPattern(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.OrPattern;

        public static bool IsParenthesizedPattern(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.ParenthesizedPattern;

        public static bool IsRecursivePattern(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.RecursivePattern;

        public static bool IsRelationalPattern(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.RelationalPattern;

        public static bool IsTypePattern(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.TypePattern;

        public static bool IsVarPattern(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.VarPattern;

        #endregion

        #region statements

        public static bool IsExpressionStatement(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.ExpressionStatement;

        public static bool IsForEachStatement(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.ForEachStatement;

        public static bool IsForStatement(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.ForStatement;

        public static bool IsIfStatement(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.IfStatement;

        public static bool IsLocalDeclarationStatement(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.LocalDeclarationStatement;

        public static bool IsLocalFunctionStatement(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node != null && node.RawKind == syntaxFacts.SyntaxKinds.LocalFunctionStatement;

        public static bool IsLockStatement(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.LockStatement;

        public static bool IsReturnStatement(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.ReturnStatement;

        public static bool IsThrowStatement(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.ThrowStatement;

        public static bool IsUsingStatement(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.UsingStatement;

        public static bool IsWhileStatement(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.WhileStatement;

        public static bool IsYieldReturnStatement(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.YieldReturnStatement;

        #endregion

        #region members/declarations

        public static bool IsAttribute(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.Attribute;

        public static bool IsClassDeclaration(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.ClassDeclaration;

        public static bool IsConstructorDeclaration(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.ConstructorDeclaration;

        public static bool IsEnumDeclaration(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.EnumDeclaration;

        public static bool IsGlobalAttribute(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => syntaxFacts.IsGlobalAssemblyAttribute(node) || syntaxFacts.IsGlobalModuleAttribute(node);

        public static bool IsInterfaceDeclaration(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.InterfaceDeclaration;

        public static bool IsParameter(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.Parameter;

        public static bool IsTypeConstraint(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.TypeConstraint;

        public static bool IsVariableDeclarator(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.VariableDeclarator;

        public static bool IsFieldDeclaration(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.FieldDeclaration;

        public static bool IsPropertyDeclaration(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.PropertyDeclaration;

        public static bool IsStructDeclaration(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.StructDeclaration;

        public static bool IsTypeArgumentList(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.TypeArgumentList;

        #endregion

        #region clauses

        public static bool IsElseClause(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.ElseClause;
        public static bool IsEqualsValueClause(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.EqualsValueClause;

        #endregion

        #region other

        public static bool IsImplicitElementAccess(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.ImplicitElementAccess;

        public static bool IsIndexerMemberCref(this ISyntaxFacts syntaxFacts, [NotNullWhen(true)] SyntaxNode? node)
            => node?.RawKind == syntaxFacts.SyntaxKinds.IndexerMemberCref;

        #endregion

        #endregion
    }
}
