File: src\Workspaces\SharedUtilitiesAndExtensions\Compiler\CSharp\Extensions\SyntaxNodeExtensions.cs
Web Access
Project: src\src\CodeStyle\CSharp\Analyzers\Microsoft.CodeAnalysis.CSharp.CodeStyle.csproj (Microsoft.CodeAnalysis.CSharp.CodeStyle)
// 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;
using Microsoft.CodeAnalysis.CSharp.LanguageService;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.Extensions;
 
internal static partial class SyntaxNodeExtensions
{
    public static LanguageVersion GetLanguageVersion(this SyntaxNode node)
        => ((CSharpParseOptions)node.SyntaxTree.Options).LanguageVersion;
 
    public static void Deconstruct(this SyntaxNode node, out SyntaxKind kind)
        => kind = node.Kind();
 
    public static bool IsKind<TNode>([NotNullWhen(true)] this SyntaxNode? node, SyntaxKind kind, [NotNullWhen(true)] out TNode? result)
        where TNode : SyntaxNode
    {
        if (node.IsKind(kind))
        {
            result = (TNode)node;
            return true;
        }
 
        result = null;
        return false;
    }
 
    public static bool IsParentKind([NotNullWhen(true)] this SyntaxNode? node, SyntaxKind kind)
        => CodeAnalysis.CSharpExtensions.IsKind(node?.Parent, kind);
 
    public static bool IsParentKind<TNode>([NotNullWhen(true)] this SyntaxNode? node, SyntaxKind kind, [NotNullWhen(true)] out TNode? result)
        where TNode : SyntaxNode
    {
        if (node.IsParentKind(kind))
        {
            result = (TNode)node.Parent!;
            return true;
        }
 
        result = null;
        return false;
    }
 
    public static IEnumerable<SyntaxTrivia> GetAllPrecedingTriviaToPreviousToken(
        this SyntaxNode node, SourceText? sourceText = null,
        bool includePreviousTokenTrailingTriviaOnlyIfOnSameLine = false)
        => node.GetFirstToken().GetAllPrecedingTriviaToPreviousToken(
            sourceText, includePreviousTokenTrailingTriviaOnlyIfOnSameLine);
 
    /// <summary>
    /// Returns all of the trivia to the left of this token up to the previous token (concatenates
    /// the previous token's trailing trivia and this token's leading trivia).
    /// </summary>
    public static IEnumerable<SyntaxTrivia> GetAllPrecedingTriviaToPreviousToken(
        this SyntaxToken token, SourceText? sourceText = null,
        bool includePreviousTokenTrailingTriviaOnlyIfOnSameLine = false)
    {
        var prevToken = token.GetPreviousToken(includeSkipped: true);
        if (prevToken.Kind() == SyntaxKind.None)
        {
            return token.LeadingTrivia;
        }
 
        Contract.ThrowIfTrue(sourceText == null && includePreviousTokenTrailingTriviaOnlyIfOnSameLine, "If we are including previous token trailing trivia, we need the text too.");
        if (includePreviousTokenTrailingTriviaOnlyIfOnSameLine &&
            !sourceText!.AreOnSameLine(prevToken, token))
        {
            return token.LeadingTrivia;
        }
 
        return prevToken.TrailingTrivia.Concat(token.LeadingTrivia);
    }
 
    public static bool IsAnyArgumentList([NotNullWhen(true)] this SyntaxNode? node)
    {
        return node?.Kind()
            is SyntaxKind.ArgumentList
            or SyntaxKind.AttributeArgumentList
            or SyntaxKind.BracketedArgumentList
            or SyntaxKind.TypeArgumentList;
    }
 
    public static (SyntaxToken openBrace, SyntaxToken closeBrace) GetBraces(this SyntaxNode? node)
        => node switch
        {
            NamespaceDeclarationSyntax namespaceNode => (namespaceNode.OpenBraceToken, namespaceNode.CloseBraceToken),
            BaseTypeDeclarationSyntax baseTypeNode => (baseTypeNode.OpenBraceToken, baseTypeNode.CloseBraceToken),
            AccessorListSyntax accessorListNode => (accessorListNode.OpenBraceToken, accessorListNode.CloseBraceToken),
            BlockSyntax blockNode => (blockNode.OpenBraceToken, blockNode.CloseBraceToken),
            SwitchStatementSyntax switchStatementNode => (switchStatementNode.OpenBraceToken, switchStatementNode.CloseBraceToken),
            AnonymousObjectCreationExpressionSyntax anonymousObjectCreationExpression => (anonymousObjectCreationExpression.OpenBraceToken, anonymousObjectCreationExpression.CloseBraceToken),
            InitializerExpressionSyntax initializeExpressionNode => (initializeExpressionNode.OpenBraceToken, initializeExpressionNode.CloseBraceToken),
            SwitchExpressionSyntax switchExpression => (switchExpression.OpenBraceToken, switchExpression.CloseBraceToken),
            PropertyPatternClauseSyntax property => (property.OpenBraceToken, property.CloseBraceToken),
            WithExpressionSyntax withExpr => (withExpr.Initializer.OpenBraceToken, withExpr.Initializer.CloseBraceToken),
            ImplicitObjectCreationExpressionSyntax { Initializer: { } initializer } => (initializer.OpenBraceToken, initializer.CloseBraceToken),
            _ => default,
        };
 
    public static bool IsEmbeddedStatementOwner([NotNullWhen(true)] this SyntaxNode? node)
    {
        return node is DoStatementSyntax or
               ElseClauseSyntax or
               FixedStatementSyntax or
               CommonForEachStatementSyntax or
               ForStatementSyntax or
               IfStatementSyntax or
               LabeledStatementSyntax or
               LockStatementSyntax or
               UsingStatementSyntax or
               WhileStatementSyntax;
    }
 
    public static StatementSyntax? GetEmbeddedStatement(this SyntaxNode? node)
        => node switch
        {
            DoStatementSyntax n => n.Statement,
            ElseClauseSyntax n => n.Statement,
            FixedStatementSyntax n => n.Statement,
            CommonForEachStatementSyntax n => n.Statement,
            ForStatementSyntax n => n.Statement,
            IfStatementSyntax n => n.Statement,
            LabeledStatementSyntax n => n.Statement,
            LockStatementSyntax n => n.Statement,
            UsingStatementSyntax n => n.Statement,
            WhileStatementSyntax n => n.Statement,
            _ => null,
        };
 
    public static BaseParameterListSyntax? GetParameterList(this SyntaxNode? declaration)
        => declaration switch
        {
            DelegateDeclarationSyntax delegateDeclaration => delegateDeclaration.ParameterList,
            BaseMethodDeclarationSyntax methodDeclaration => methodDeclaration.ParameterList,
            IndexerDeclarationSyntax indexerDeclaration => indexerDeclaration.ParameterList,
            ParenthesizedLambdaExpressionSyntax parenthesizedLambda => parenthesizedLambda.ParameterList,
            LocalFunctionStatementSyntax localFunction => localFunction.ParameterList,
            AnonymousMethodExpressionSyntax anonymousMethod => anonymousMethod.ParameterList,
            TypeDeclarationSyntax typeDeclaration => typeDeclaration.ParameterList,
            _ => null,
        };
 
    public static SyntaxList<AttributeListSyntax> GetAttributeLists(this SyntaxNode? declaration)
        => declaration switch
        {
            MemberDeclarationSyntax memberDecl => memberDecl.AttributeLists,
            AccessorDeclarationSyntax accessor => accessor.AttributeLists,
            ParameterSyntax parameter => parameter.AttributeLists,
            CompilationUnitSyntax compilationUnit => compilationUnit.AttributeLists,
            StatementSyntax statementSyntax => statementSyntax.AttributeLists,
            TypeParameterSyntax typeParameter => typeParameter.AttributeLists,
            LambdaExpressionSyntax lambdaExpressionSyntax => lambdaExpressionSyntax.AttributeLists,
            _ => default,
        };
 
    public static ConditionalAccessExpressionSyntax? GetParentConditionalAccessExpression(this SyntaxNode? node)
    {
        // Walk upwards based on the grammar/parser rules around ?. expressions (can be seen in
        // LanguageParser.ParseConsequenceSyntax).
 
        // These are the parts of the expression that the ?... expression can end with.  Specifically:
        //
        //  1.      x?.y.M()            // invocation
        //  2.      x?.y[...];          // element access
        //  3.      x?.y.z              // member access
        //  4.      x?.y                // member binding
        //  5.      x?[y]               // element binding
        var current = node;
 
        if ((current?.Parent is MemberAccessExpressionSyntax(SyntaxKind.SimpleMemberAccessExpression) memberAccess && memberAccess.Name == current) ||
            (current?.Parent is MemberBindingExpressionSyntax memberBinding && memberBinding.Name == current))
        {
            current = current.Parent;
        }
 
        // Effectively, if we're on the RHS of the ? we have to walk up the RHS spine first until we hit the first
        // conditional access.
 
        while (current is (kind:
            SyntaxKind.InvocationExpression or
            SyntaxKind.ElementAccessExpression or
            SyntaxKind.SimpleMemberAccessExpression or
            SyntaxKind.MemberBindingExpression or
            SyntaxKind.ElementBindingExpression or
            // Optional exclamations might follow the conditional operation. For example: a.b?.$$c!!!!()
            SyntaxKind.SuppressNullableWarningExpression) &&
            current.Parent is not ConditionalAccessExpressionSyntax)
        {
            current = current.Parent;
        }
 
        // Two cases we have to care about:
        //
        //      1. a?.b.$$c.d        and
        //      2. a?.b.$$c.d?.e...
        //
        // Note that `a?.b.$$c.d?.e.f?.g.h.i` falls into the same bucket as two.  i.e. the parts after `.e` are
        // lower in the tree and are not seen as we walk upwards.
        //
        //
        // To get the root ?. (the one after the `a`) we have to potentially consume the first ?. on the RHS of the
        // right spine (i.e. the one after `d`).  Once we do this, we then see if that itself is on the RHS of a
        // another conditional, and if so we hten return the one on the left.  i.e. for '2' this goes in this direction:
        //
        //      a?.b.$$c.d?.e           // it will do:
        //           ----->
        //       <---------
        //
        // Note that this only one CAE consumption on both sides.  GetRootConditionalAccessExpression can be used to
        // get the root parent in a case like:
        //
        //      x?.y?.z?.a?.b.$$c.d?.e.f?.g.h.i         // it will do:
        //                    ----->
        //                <---------
        //             <---
        //          <---
        //       <---
 
        if (current?.Parent is ConditionalAccessExpressionSyntax conditional1 &&
            conditional1.Expression == current)
        {
            current = conditional1;
        }
 
        if (current?.Parent is ConditionalAccessExpressionSyntax conditional2 &&
            conditional2.WhenNotNull == current)
        {
            current = conditional2;
        }
 
        return current as ConditionalAccessExpressionSyntax;
    }
 
    /// <summary>
    /// <inheritdoc cref="ISyntaxFacts.GetRootConditionalAccessExpression(SyntaxNode)"/>
    /// </summary>>
    public static ConditionalAccessExpressionSyntax? GetRootConditionalAccessExpression(this SyntaxNode? node)
    {
        // Once we've walked up the entire RHS, now we continually walk up the conditional accesses until we're at
        // the root. For example, if we have `a?.b` and we're on the `.b`, this will give `a?.b`.  Similarly with
        // `a?.b?.c` if we're on either `.b` or `.c` this will result in `a?.b?.c` (i.e. the root of this CAE
        // sequence).
 
        var current = node.GetParentConditionalAccessExpression();
        while (current?.Parent is ConditionalAccessExpressionSyntax conditional &&
            conditional.WhenNotNull == current)
        {
            current = conditional;
        }
 
        return current;
    }
 
    public static ConditionalAccessExpressionSyntax? GetInnerMostConditionalAccessExpression(this SyntaxNode node)
    {
        if (node is not ConditionalAccessExpressionSyntax result)
            return null;
 
        while (result.WhenNotNull is ConditionalAccessExpressionSyntax syntax)
            result = syntax;
 
        return result;
    }
 
    public static bool IsAsyncSupportingFunctionSyntax([NotNullWhen(true)] this SyntaxNode? node)
        => node is MethodDeclarationSyntax or AnonymousFunctionExpressionSyntax or LocalFunctionStatementSyntax;
 
    public static bool IsCompoundAssignExpression([NotNullWhen(true)] this SyntaxNode? node)
        => node is AssignmentExpressionSyntax(kind: not SyntaxKind.SimpleAssignmentExpression);
 
    public static bool IsLeftSideOfAssignExpression([NotNullWhen(true)] this SyntaxNode? node)
        => node?.Parent is AssignmentExpressionSyntax(SyntaxKind.SimpleAssignmentExpression) assignment &&
           assignment.Left == node;
 
    public static bool IsLeftSideOfAnyAssignExpression([NotNullWhen(true)] this SyntaxNode? node)
        => node?.Parent is AssignmentExpressionSyntax assignment && assignment.Left == node;
 
    public static bool IsRightSideOfAnyAssignExpression([NotNullWhen(true)] this SyntaxNode? node)
        => node?.Parent is AssignmentExpressionSyntax assignment && assignment.Right == node;
 
    public static bool IsLeftSideOfCompoundAssignExpression([NotNullWhen(true)] this SyntaxNode? node)
    {
        return node?.Parent != null &&
            node.Parent.IsCompoundAssignExpression() &&
            ((AssignmentExpressionSyntax)node.Parent).Left == node;
    }
 
    /// <summary>
    /// Returns the list of using directives that affect <paramref name="node"/>. The list will be returned in
    /// top down order.  
    /// </summary>
    public static IEnumerable<UsingDirectiveSyntax> GetEnclosingUsingDirectives(this SyntaxNode node)
    {
        return node.GetAncestorOrThis<CompilationUnitSyntax>()!.Usings
                   .Concat(node.GetAncestorsOrThis<BaseNamespaceDeclarationSyntax>()
                               .Reverse()
                               .SelectMany(n => n.Usings));
    }
 
    public static IEnumerable<ExternAliasDirectiveSyntax> GetEnclosingExternAliasDirectives(this SyntaxNode node)
    {
        return node.GetAncestorOrThis<CompilationUnitSyntax>()!.Externs
                   .Concat(node.GetAncestorsOrThis<BaseNamespaceDeclarationSyntax>()
                               .Reverse()
                               .SelectMany(n => n.Externs));
    }
 
    public static bool IsUnsafeContext(this SyntaxNode node)
    {
        if (node.GetAncestor<UnsafeStatementSyntax>() != null)
        {
            return true;
        }
 
        return node.GetAncestors<MemberDeclarationSyntax>().Any(
            m => m.GetModifiers().Any(SyntaxKind.UnsafeKeyword));
    }
 
    public static bool IsInStaticContext(this SyntaxNode node)
    {
        for (var current = node; current != null; current = current.Parent)
        {
            switch (current)
            {
                // this/base calls are always static.
                case ConstructorInitializerSyntax:
                    return true;
 
                case LocalFunctionStatementSyntax localFunction when localFunction.Modifiers.Any(SyntaxKind.StaticKeyword):
                    return true;
 
                case AnonymousFunctionExpressionSyntax anonymousFunction when anonymousFunction.Modifiers.Any(SyntaxKind.StaticKeyword):
                    return true;
 
                case BaseMethodDeclarationSyntax or IndexerDeclarationSyntax or EventDeclarationSyntax:
                    return current.GetModifiers().Any(SyntaxKind.StaticKeyword);
 
                case PropertyDeclarationSyntax property:
                    return property.Modifiers.Any(SyntaxKind.StaticKeyword) ||
                        node.IsFoundUnder((PropertyDeclarationSyntax p) => p.Initializer);
 
                case FieldDeclarationSyntax or EventFieldDeclarationSyntax:
                    // Inside a field one can only access static members of a type (unless it's top-level).
                    return !current.Parent.IsKind(SyntaxKind.CompilationUnit);
 
                case GlobalStatementSyntax:
                    // Global statements are not a static context.
                    return false;
            }
        }
 
        // any other location is considered static
        return true;
    }
 
    public static BaseNamespaceDeclarationSyntax? GetInnermostNamespaceDeclarationWithUsings(this SyntaxNode contextNode)
    {
        var usingDirectiveAncestor = contextNode.GetAncestor<UsingDirectiveSyntax>();
        if (usingDirectiveAncestor == null)
        {
            return contextNode.GetAncestorsOrThis<BaseNamespaceDeclarationSyntax>().FirstOrDefault(n => n.Usings.Count > 0);
        }
        else
        {
            // We are inside a using directive. In this case, we should find and return the first 'parent' namespace with usings.
            var containingNamespace = usingDirectiveAncestor.GetAncestor<BaseNamespaceDeclarationSyntax>();
            if (containingNamespace == null)
            {
                // We are inside a top level using directive (i.e. one that's directly in the compilation unit).
                return null;
            }
            else
            {
                return containingNamespace.GetAncestors<BaseNamespaceDeclarationSyntax>().FirstOrDefault(n => n.Usings.Count > 0);
            }
        }
    }
 
    public static bool IsBreakableConstruct(this SyntaxNode node)
    {
        switch (node.Kind())
        {
            case SyntaxKind.DoStatement:
            case SyntaxKind.WhileStatement:
            case SyntaxKind.SwitchStatement:
            case SyntaxKind.ForStatement:
            case SyntaxKind.ForEachStatement:
            case SyntaxKind.ForEachVariableStatement:
                return true;
        }
 
        return false;
    }
 
    public static bool IsContinuableConstruct(this SyntaxNode node)
    {
        switch (node.Kind())
        {
            case SyntaxKind.DoStatement:
            case SyntaxKind.WhileStatement:
            case SyntaxKind.ForStatement:
            case SyntaxKind.ForEachStatement:
            case SyntaxKind.ForEachVariableStatement:
                return true;
        }
 
        return false;
    }
 
    public static bool IsReturnableConstruct(this SyntaxNode node)
    {
        switch (node.Kind())
        {
            case SyntaxKind.AnonymousMethodExpression:
            case SyntaxKind.SimpleLambdaExpression:
            case SyntaxKind.ParenthesizedLambdaExpression:
            case SyntaxKind.LocalFunctionStatement:
            case SyntaxKind.MethodDeclaration:
            case SyntaxKind.ConstructorDeclaration:
            case SyntaxKind.DestructorDeclaration:
            case SyntaxKind.GetAccessorDeclaration:
            case SyntaxKind.SetAccessorDeclaration:
            case SyntaxKind.InitAccessorDeclaration:
            case SyntaxKind.OperatorDeclaration:
            case SyntaxKind.ConversionOperatorDeclaration:
            case SyntaxKind.AddAccessorDeclaration:
            case SyntaxKind.RemoveAccessorDeclaration:
                return true;
        }
 
        return false;
    }
 
    public static bool ContainsYield(this SyntaxNode node)
        => node.DescendantNodes(n => n == node || !n.IsReturnableConstruct()).Any(n => n is YieldStatementSyntax);
 
    public static bool IsReturnableConstructOrTopLevelCompilationUnit(this SyntaxNode node)
        => node.IsReturnableConstruct() || (node is CompilationUnitSyntax compilationUnit && compilationUnit.Members.Any(SyntaxKind.GlobalStatement));
 
    public static bool SpansPreprocessorDirective<TSyntaxNode>(this IEnumerable<TSyntaxNode> list) where TSyntaxNode : SyntaxNode
        => CSharpSyntaxFacts.Instance.SpansPreprocessorDirective(list);
 
    [return: NotNullIfNotNull(nameof(node))]
    public static TNode? ConvertToSingleLine<TNode>(this TNode? node, bool useElasticTrivia = false)
        where TNode : SyntaxNode
    {
        if (node == null)
        {
            return node;
        }
 
        var rewriter = new SingleLineRewriter(useElasticTrivia);
        return (TNode)rewriter.Visit(node);
    }
 
    /// <summary>
    /// Returns true if the passed in node contains an interleaved pp directive.
    /// 
    /// i.e. The following returns false:
    /// 
    ///   void Goo() {
    /// #if true
    /// #endif
    ///   }
    /// 
    /// #if true
    ///   void Goo() {
    ///   }
    /// #endif
    /// 
    /// but these return true:
    /// 
    /// #if true
    ///   void Goo() {
    /// #endif
    ///   }
    /// 
    ///   void Goo() {
    /// #if true
    ///   }
    /// #endif
    /// 
    /// #if true
    ///   void Goo() {
    /// #else
    ///   }
    /// #endif
    /// 
    /// i.e. the method returns true if it contains a PP directive that belongs to a grouping
    /// constructs (like #if/#endif or #region/#endregion), but the grouping construct isn't
    /// entirely contained within the span of the node.
    /// </summary>
    public static bool ContainsInterleavedDirective(this SyntaxNode syntaxNode, CancellationToken cancellationToken)
        => CSharpSyntaxFacts.Instance.ContainsInterleavedDirective(syntaxNode, cancellationToken);
 
    /// <summary>
    /// Similar to <see cref="ContainsInterleavedDirective(SyntaxNode, CancellationToken)"/> except that the span to check
    /// for interleaved directives can be specified separately to the node passed in.
    /// </summary>
    public static bool ContainsInterleavedDirective(this SyntaxNode syntaxNode, TextSpan span, CancellationToken cancellationToken)
        => CSharpSyntaxFacts.Instance.ContainsInterleavedDirective(span, syntaxNode, cancellationToken);
 
    public static bool ContainsInterleavedDirective(
        this SyntaxToken token,
        TextSpan textSpan,
        CancellationToken cancellationToken)
    {
        return
            ContainsInterleavedDirective(textSpan, token.LeadingTrivia, cancellationToken) ||
            ContainsInterleavedDirective(textSpan, token.TrailingTrivia, cancellationToken);
    }
 
    private static bool ContainsInterleavedDirective(
        TextSpan textSpan,
        SyntaxTriviaList list,
        CancellationToken cancellationToken)
    {
        foreach (var trivia in list)
        {
            if (textSpan.Contains(trivia.Span))
            {
                if (ContainsInterleavedDirective(textSpan, trivia, cancellationToken))
                {
                    return true;
                }
            }
        }
 
        return false;
    }
 
    private static bool ContainsInterleavedDirective(
        TextSpan textSpan,
        SyntaxTrivia trivia,
        CancellationToken cancellationToken)
    {
        if (trivia.HasStructure)
        {
            var structure = trivia.GetStructure()!;
            if (trivia.GetStructure() is (kind: SyntaxKind.RegionDirectiveTrivia or SyntaxKind.EndRegionDirectiveTrivia or SyntaxKind.IfDirectiveTrivia or SyntaxKind.EndIfDirectiveTrivia))
            {
                var match = ((DirectiveTriviaSyntax)structure).GetMatchingDirective(cancellationToken);
                if (match != null)
                {
                    var matchSpan = match.Span;
                    if (!textSpan.Contains(matchSpan.Start))
                    {
                        // The match for this pp directive is outside
                        // this node.
                        return true;
                    }
                }
            }
            else if (trivia.GetStructure() is (kind: SyntaxKind.ElseDirectiveTrivia or SyntaxKind.ElifDirectiveTrivia))
            {
                var directives = ((DirectiveTriviaSyntax)structure).GetMatchingConditionalDirectives(cancellationToken);
                if (directives.Length > 0)
                {
                    if (!textSpan.Contains(directives[0].SpanStart) ||
                        !textSpan.Contains(directives.Last().SpanStart))
                    {
                        // This else/elif belongs to a pp span that isn't 
                        // entirely within this node.
                        return true;
                    }
                }
            }
        }
 
        return false;
    }
 
    /// <summary>
    /// Breaks up the list of provided nodes, based on how they are interspersed with pp
    /// directives, into groups.  Within these groups nodes can be moved around safely, without
    /// breaking any pp constructs.
    /// </summary>
    public static IList<IList<TSyntaxNode>> SplitNodesOnPreprocessorBoundaries<TSyntaxNode>(
        this IEnumerable<TSyntaxNode> nodes,
        CancellationToken cancellationToken)
        where TSyntaxNode : SyntaxNode
    {
        var result = new List<IList<TSyntaxNode>>();
 
        var currentGroup = new List<TSyntaxNode>();
        foreach (var node in nodes)
        {
            var hasUnmatchedInteriorDirective = node.ContainsInterleavedDirective(cancellationToken);
            var hasLeadingDirective = node.GetLeadingTrivia().Any(t => SyntaxFacts.IsPreprocessorDirective(t.Kind()));
 
            if (hasUnmatchedInteriorDirective)
            {
                // we have a #if/#endif/#region/#endregion/#else/#elif in
                // this node that belongs to a span of pp directives that
                // is not entirely contained within the node.  i.e.:
                //
                //   void Goo() {
                //      #if ...
                //   }
                //
                // This node cannot be moved at all.  It is in a group that
                // only contains itself (and thus can never be moved).
 
                // add whatever group we've built up to now. And reset the 
                // next group to empty.
                result.Add(currentGroup);
                currentGroup = [];
 
                result.Add([node]);
            }
            else if (hasLeadingDirective)
            {
                // We have a PP directive before us.  i.e.:
                // 
                //   #if ...
                //      void Goo() {
                //
                // That means we start a new group that is contained between
                // the above directive and the following directive.
 
                // add whatever group we've built up to now. And reset the 
                // next group to empty.
                result.Add(currentGroup);
                currentGroup = [node];
            }
            else
            {
                // simple case.  just add ourselves to the current group
                currentGroup.Add(node);
            }
        }
 
        // add the remainder of the final group.
        result.Add(currentGroup);
 
        // Now, filter out any empty groups.
        result = result.Where(group => !group.IsEmpty()).ToList();
        return result;
    }
 
    public static ImmutableArray<SyntaxTrivia> GetLeadingBlankLines<TSyntaxNode>(this TSyntaxNode node) where TSyntaxNode : SyntaxNode
        => CSharpFileBannerFacts.Instance.GetLeadingBlankLines(node);
 
    public static TSyntaxNode GetNodeWithoutLeadingBlankLines<TSyntaxNode>(this TSyntaxNode node) where TSyntaxNode : SyntaxNode
        => CSharpFileBannerFacts.Instance.GetNodeWithoutLeadingBlankLines(node);
 
    public static TSyntaxNode GetNodeWithoutLeadingBlankLines<TSyntaxNode>(this TSyntaxNode node, out ImmutableArray<SyntaxTrivia> strippedTrivia) where TSyntaxNode : SyntaxNode
        => CSharpFileBannerFacts.Instance.GetNodeWithoutLeadingBlankLines(node, out strippedTrivia);
 
    public static ImmutableArray<SyntaxTrivia> GetLeadingBannerAndPreprocessorDirectives<TSyntaxNode>(this TSyntaxNode node) where TSyntaxNode : SyntaxNode
        => CSharpFileBannerFacts.Instance.GetLeadingBannerAndPreprocessorDirectives(node);
 
    public static TSyntaxNode GetNodeWithoutLeadingBannerAndPreprocessorDirectives<TSyntaxNode>(this TSyntaxNode node) where TSyntaxNode : SyntaxNode
        => CSharpFileBannerFacts.Instance.GetNodeWithoutLeadingBannerAndPreprocessorDirectives(node);
 
    public static TSyntaxNode GetNodeWithoutLeadingBannerAndPreprocessorDirectives<TSyntaxNode>(this TSyntaxNode node, out ImmutableArray<SyntaxTrivia> strippedTrivia) where TSyntaxNode : SyntaxNode
        => CSharpFileBannerFacts.Instance.GetNodeWithoutLeadingBannerAndPreprocessorDirectives(node, out strippedTrivia);
 
    public static bool IsVariableDeclaratorValue([NotNullWhen(true)] this SyntaxNode? node)
        => node?.Parent is EqualsValueClauseSyntax { Parent: VariableDeclaratorSyntax } equalsValue &&
           equalsValue.Value == node;
 
    public static BlockSyntax? FindInnermostCommonBlock(this IEnumerable<SyntaxNode> nodes)
        => nodes.FindInnermostCommonNode<BlockSyntax>();
 
    public static IEnumerable<SyntaxNode> GetAncestorsOrThis(this SyntaxNode? node, Func<SyntaxNode, bool> predicate)
    {
        var current = node;
        while (current != null)
        {
            if (predicate(current))
            {
                yield return current;
            }
 
            current = current.Parent;
        }
    }
 
    public static (SyntaxToken openParen, SyntaxToken closeParen) GetParentheses(this SyntaxNode node)
        => node switch
        {
            ParenthesizedExpressionSyntax n => (n.OpenParenToken, n.CloseParenToken),
            MakeRefExpressionSyntax n => (n.OpenParenToken, n.CloseParenToken),
            RefTypeExpressionSyntax n => (n.OpenParenToken, n.CloseParenToken),
            RefValueExpressionSyntax n => (n.OpenParenToken, n.CloseParenToken),
            CheckedExpressionSyntax n => (n.OpenParenToken, n.CloseParenToken),
            DefaultExpressionSyntax n => (n.OpenParenToken, n.CloseParenToken),
            TypeOfExpressionSyntax n => (n.OpenParenToken, n.CloseParenToken),
            SizeOfExpressionSyntax n => (n.OpenParenToken, n.CloseParenToken),
            ArgumentListSyntax n => (n.OpenParenToken, n.CloseParenToken),
            CastExpressionSyntax n => (n.OpenParenToken, n.CloseParenToken),
            WhileStatementSyntax n => (n.OpenParenToken, n.CloseParenToken),
            DoStatementSyntax n => (n.OpenParenToken, n.CloseParenToken),
            ForStatementSyntax n => (n.OpenParenToken, n.CloseParenToken),
            CommonForEachStatementSyntax n => (n.OpenParenToken, n.CloseParenToken),
            UsingStatementSyntax n => (n.OpenParenToken, n.CloseParenToken),
            FixedStatementSyntax n => (n.OpenParenToken, n.CloseParenToken),
            LockStatementSyntax n => (n.OpenParenToken, n.CloseParenToken),
            IfStatementSyntax n => (n.OpenParenToken, n.CloseParenToken),
            SwitchStatementSyntax n when n.OpenParenToken != default => (n.OpenParenToken, n.CloseParenToken),
            TupleExpressionSyntax n => (n.OpenParenToken, n.CloseParenToken),
            CatchDeclarationSyntax n => (n.OpenParenToken, n.CloseParenToken),
            AttributeArgumentListSyntax n => (n.OpenParenToken, n.CloseParenToken),
            ConstructorConstraintSyntax n => (n.OpenParenToken, n.CloseParenToken),
            ParameterListSyntax n => (n.OpenParenToken, n.CloseParenToken),
            _ => default,
        };
 
    public static (SyntaxToken openBracket, SyntaxToken closeBracket) GetBrackets(this SyntaxNode? node)
        => node switch
        {
            ArrayRankSpecifierSyntax n => (n.OpenBracketToken, n.CloseBracketToken),
            BracketedArgumentListSyntax n => (n.OpenBracketToken, n.CloseBracketToken),
            ImplicitArrayCreationExpressionSyntax n => (n.OpenBracketToken, n.CloseBracketToken),
            AttributeListSyntax n => (n.OpenBracketToken, n.CloseBracketToken),
            BracketedParameterListSyntax n => (n.OpenBracketToken, n.CloseBracketToken),
            ListPatternSyntax n => (n.OpenBracketToken, n.CloseBracketToken),
            CollectionExpressionSyntax n => (n.OpenBracketToken, n.CloseBracketToken),
            _ => default,
        };
 
    public static SyntaxTokenList GetModifiers(this SyntaxNode? member)
        => member switch
        {
            AccessorDeclarationSyntax accessor => accessor.Modifiers,
            AnonymousFunctionExpressionSyntax anonymous => anonymous.Modifiers,
            LocalDeclarationStatementSyntax localDeclaration => localDeclaration.Modifiers,
            LocalFunctionStatementSyntax localFunction => localFunction.Modifiers,
            MemberDeclarationSyntax memberDecl => memberDecl.Modifiers,
            ParameterSyntax parameter => parameter.Modifiers,
            _ => default,
        };
 
    public static SyntaxNode? WithModifiers(this SyntaxNode? member, SyntaxTokenList modifiers)
        => member switch
        {
            MemberDeclarationSyntax memberDecl => memberDecl.WithModifiers(modifiers),
            AccessorDeclarationSyntax accessor => accessor.WithModifiers(modifiers),
            AnonymousFunctionExpressionSyntax anonymous => anonymous.WithModifiers(modifiers),
            LocalFunctionStatementSyntax localFunction => localFunction.WithModifiers(modifiers),
            LocalDeclarationStatementSyntax localDeclaration => localDeclaration.WithModifiers(modifiers),
            _ => null,
        };
 
    public static void ForEachMember<TArg>(this SyntaxNode? node, Action<MemberDeclarationSyntax, TArg> callback, TArg arg)
    {
        // Separated out to allow for struct-based enumeration.
        switch (node)
        {
            case CompilationUnitSyntax compilation:
                foreach (var member in compilation.Members)
                    callback(member, arg);
                break;
            case BaseNamespaceDeclarationSyntax @namespace:
                foreach (var member in @namespace.Members)
                    callback(member, arg);
                break;
            case TypeDeclarationSyntax type:
                foreach (var member in type.Members)
                    callback(member, arg);
                break;
            case EnumDeclarationSyntax @enum:
                foreach (var member in @enum.Members)
                    callback(member, arg);
                break;
        }
    }
 
    public static bool IsInExpressionTree(
        [NotNullWhen(true)] this SyntaxNode? node,
        SemanticModel semanticModel,
        [NotNullWhen(true)] INamedTypeSymbol? expressionType,
        CancellationToken cancellationToken)
    {
        if (expressionType != null)
        {
            for (var current = node; current != null; current = current.Parent)
            {
                if (current is LambdaExpressionSyntax)
                {
                    var typeInfo = semanticModel.GetTypeInfo(current, cancellationToken);
                    if (expressionType.Equals(typeInfo.ConvertedType?.OriginalDefinition))
                        return true;
                }
                else if (current is SelectOrGroupClauseSyntax or OrderingSyntax)
                {
                    var info = semanticModel.GetSymbolInfo(current, cancellationToken);
                    if (TakesExpressionTree(info, expressionType))
                        return true;
                }
                else if (current is QueryClauseSyntax queryClause)
                {
                    var info = semanticModel.GetQueryClauseInfo(queryClause, cancellationToken);
                    if (TakesExpressionTree(info.CastInfo, expressionType) ||
                        TakesExpressionTree(info.OperationInfo, expressionType))
                    {
                        return true;
                    }
                }
            }
        }
 
        return false;
 
        static bool TakesExpressionTree(SymbolInfo info, INamedTypeSymbol expressionType)
        {
            foreach (var symbol in info.GetAllSymbols())
            {
                if (symbol is IMethodSymbol method &&
                    method.Parameters.Length > 0 &&
                    expressionType.Equals(method.Parameters[0].Type?.OriginalDefinition))
                {
                    return true;
                }
            }
 
            return false;
        }
    }
 
    public static bool IsInDeconstructionLeft(
        [NotNullWhen(true)] this SyntaxNode? node,
        [NotNullWhen(true)] out SyntaxNode? deconstructionLeft)
    {
        SyntaxNode? previous = null;
        for (var current = node; current != null; current = current.Parent)
        {
            if ((current is AssignmentExpressionSyntax assignment && previous == assignment.Left && assignment.IsDeconstruction()) ||
                (current is ForEachVariableStatementSyntax @foreach && previous == @foreach.Variable))
            {
                deconstructionLeft = previous;
                return true;
            }
 
            if (current is StatementSyntax)
            {
                break;
            }
 
            previous = current;
        }
 
        deconstructionLeft = null;
        return false;
    }
 
    public static T WithCommentsFrom<T>(this T node, SyntaxToken leadingToken, SyntaxToken trailingToken)
        where T : SyntaxNode
        => node.WithCommentsFrom(
            SyntaxNodeOrTokenExtensions.GetTrivia(leadingToken),
            SyntaxNodeOrTokenExtensions.GetTrivia(trailingToken));
 
    public static T WithCommentsFrom<T>(
        this T node,
        IEnumerable<SyntaxToken> leadingTokens,
        IEnumerable<SyntaxToken> trailingTokens)
        where T : SyntaxNode
        => node.WithCommentsFrom(leadingTokens.GetTrivia(), trailingTokens.GetTrivia());
 
    public static T WithCommentsFrom<T>(
        this T node,
        IEnumerable<SyntaxTrivia> leadingTrivia,
        IEnumerable<SyntaxTrivia> trailingTrivia,
        params SyntaxNodeOrToken[] trailingNodesOrTokens)
        where T : SyntaxNode
        => node
            .WithLeadingTrivia(leadingTrivia.Concat(node.GetLeadingTrivia()).FilterComments(addElasticMarker: false))
            .WithTrailingTrivia(
                node.GetTrailingTrivia().Concat(SyntaxNodeOrTokenExtensions.GetTrivia(trailingNodesOrTokens).Concat(trailingTrivia)).FilterComments(addElasticMarker: false));
 
    public static T KeepCommentsAndAddElasticMarkers<T>(this T node) where T : SyntaxNode
        => node
        .WithTrailingTrivia(node.GetTrailingTrivia().FilterComments(addElasticMarker: true))
        .WithLeadingTrivia(node.GetLeadingTrivia().FilterComments(addElasticMarker: true));
 
    public static SyntaxNode WithPrependedNonIndentationTriviaFrom(
        this SyntaxNode to, SyntaxNode from)
    {
        // get all the preceding trivia from the 'from' node, not counting the leading
        // indentation trivia is has.
        var finalTrivia = from.GetLeadingTrivia().ToList();
        while (finalTrivia is [.., (kind: SyntaxKind.WhitespaceTrivia)])
            finalTrivia.RemoveAt(finalTrivia.Count - 1);
 
        // Also, add on the trailing trivia if there are trailing comments.
        var hasTrailingComments = from.GetTrailingTrivia().Any(t => t.IsRegularComment());
        if (hasTrailingComments)
            finalTrivia.AddRange(from.GetTrailingTrivia());
 
        // Merge this trivia with the existing trivia on the node.  Format in case
        // we added comments and need them indented properly.
        return to.WithPrependedLeadingTrivia(finalTrivia);
    }
}