File: EditAndContinue\SyntaxUtilities.cs
Web Access
Project: ..\..\..\src\Features\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.Features.csproj (Microsoft.CodeAnalysis.CSharp.Features)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
#nullable disable
 
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
 
namespace Microsoft.CodeAnalysis.CSharp.EditAndContinue
{
    internal static class SyntaxUtilities
    {
        public static SyntaxNode TryGetMethodDeclarationBody(SyntaxNode node)
        {
            static SyntaxNode BlockOrExpression(BlockSyntax blockBodyOpt, ArrowExpressionClauseSyntax expressionBodyOpt)
                => (SyntaxNode)blockBodyOpt ?? expressionBodyOpt?.Expression;
 
            SyntaxNode result;
            switch (node.Kind())
            {
                case SyntaxKind.MethodDeclaration:
                    var methodDeclaration = (MethodDeclarationSyntax)node;
                    result = BlockOrExpression(methodDeclaration.Body, methodDeclaration.ExpressionBody);
                    break;
 
                case SyntaxKind.ConversionOperatorDeclaration:
                    var conversionDeclaration = (ConversionOperatorDeclarationSyntax)node;
                    result = BlockOrExpression(conversionDeclaration.Body, conversionDeclaration.ExpressionBody);
                    break;
 
                case SyntaxKind.OperatorDeclaration:
                    var operatorDeclaration = (OperatorDeclarationSyntax)node;
                    result = BlockOrExpression(operatorDeclaration.Body, operatorDeclaration.ExpressionBody);
                    break;
 
                case SyntaxKind.SetAccessorDeclaration:
                case SyntaxKind.InitAccessorDeclaration:
                case SyntaxKind.AddAccessorDeclaration:
                case SyntaxKind.RemoveAccessorDeclaration:
                case SyntaxKind.GetAccessorDeclaration:
                    var accessorDeclaration = (AccessorDeclarationSyntax)node;
                    result = BlockOrExpression(accessorDeclaration.Body, accessorDeclaration.ExpressionBody);
                    break;
 
                case SyntaxKind.ConstructorDeclaration:
                    var constructorDeclaration = (ConstructorDeclarationSyntax)node;
                    result = BlockOrExpression(constructorDeclaration.Body, constructorDeclaration.ExpressionBody);
                    break;
 
                case SyntaxKind.DestructorDeclaration:
                    var destructorDeclaration = (DestructorDeclarationSyntax)node;
                    result = BlockOrExpression(destructorDeclaration.Body, destructorDeclaration.ExpressionBody);
                    break;
 
                case SyntaxKind.PropertyDeclaration:
                    var propertyDeclaration = (PropertyDeclarationSyntax)node;
                    result = propertyDeclaration.Initializer?.Value;
                    break;
 
                case SyntaxKind.ArrowExpressionClause:
                    // We associate the body of expression-bodied property/indexer with the ArrowExpressionClause
                    // since that's the syntax node associated with the getter symbol.
                    // The property/indexer itself is considered to not have a body unless the property has an initializer.
                    result = node.Parent.Kind() is SyntaxKind.PropertyDeclaration or SyntaxKind.IndexerDeclaration ?
                        ((ArrowExpressionClauseSyntax)node).Expression : null;
                    break;
 
                default:
                    return null;
            }
 
            if (result != null)
            {
                AssertIsBody(result, allowLambda: false);
            }
 
            return result;
        }
 
        [Conditional("DEBUG")]
        public static void AssertIsBody(SyntaxNode syntax, bool allowLambda)
        {
            // lambda/query
            if (LambdaUtilities.IsLambdaBody(syntax))
            {
                Debug.Assert(allowLambda);
                Debug.Assert(syntax is ExpressionSyntax or BlockSyntax);
                return;
            }
 
            // block body
            if (syntax is BlockSyntax)
            {
                return;
            }
 
            // expression body
            if (syntax is ExpressionSyntax && syntax.Parent is ArrowExpressionClauseSyntax)
            {
                return;
            }
 
            // field initializer
            if (syntax is ExpressionSyntax && syntax.Parent.Parent is VariableDeclaratorSyntax)
            {
                return;
            }
 
            // property initializer
            if (syntax is ExpressionSyntax && syntax.Parent.Parent is PropertyDeclarationSyntax)
            {
                return;
            }
 
            // special case for top level statements, which have no containing block other than the compilation unit
            if (syntax is CompilationUnitSyntax unit && unit.ContainsGlobalStatements())
            {
                return;
            }
 
            Debug.Assert(false);
        }
 
        public static bool ContainsGlobalStatements(this CompilationUnitSyntax compilationUnit)
            => compilationUnit.Members is [GlobalStatementSyntax, ..];
 
        public static void FindLeafNodeAndPartner(SyntaxNode leftRoot, int leftPosition, SyntaxNode rightRoot, out SyntaxNode leftNode, out SyntaxNode rightNodeOpt)
        {
            leftNode = leftRoot;
            rightNodeOpt = rightRoot;
            while (true)
            {
                if (rightNodeOpt != null && leftNode.RawKind != rightNodeOpt.RawKind)
                {
                    rightNodeOpt = null;
                }
 
                var leftChild = leftNode.ChildThatContainsPosition(leftPosition, out var childIndex);
                if (leftChild.IsToken)
                {
                    return;
                }
 
                if (rightNodeOpt != null)
                {
                    var rightNodeChildNodesAndTokens = rightNodeOpt.ChildNodesAndTokens();
                    if (childIndex >= 0 && childIndex < rightNodeChildNodesAndTokens.Count)
                    {
                        rightNodeOpt = rightNodeChildNodesAndTokens[childIndex].AsNode();
                    }
                    else
                    {
                        rightNodeOpt = null;
                    }
                }
 
                leftNode = leftChild.AsNode();
            }
        }
 
        public static SyntaxNode FindPartner(SyntaxNode leftRoot, SyntaxNode rightRoot, SyntaxNode leftNode)
        {
            // Finding a partner of a zero-width node is complicated and not supported atm:
            Debug.Assert(leftNode.FullSpan.Length > 0);
            Debug.Assert(leftNode.SyntaxTree == leftRoot.SyntaxTree);
 
            var originalLeftNode = leftNode;
            var leftPosition = leftNode.SpanStart;
            leftNode = leftRoot;
            var rightNode = rightRoot;
 
            while (leftNode != originalLeftNode)
            {
                Debug.Assert(leftNode.RawKind == rightNode.RawKind);
                var leftChild = leftNode.ChildThatContainsPosition(leftPosition, out var childIndex);
 
                // Can only happen when searching for zero-width node.
                Debug.Assert(!leftChild.IsToken);
 
                rightNode = rightNode.ChildNodesAndTokens()[childIndex].AsNode();
                leftNode = leftChild.AsNode();
            }
 
            return rightNode;
        }
 
        public static bool Any(TypeParameterListSyntax listOpt)
            => listOpt != null && listOpt.ChildNodesAndTokens().Count != 0;
 
        public static SyntaxNode TryGetEffectiveGetterBody(SyntaxNode declaration)
        {
            if (declaration is PropertyDeclarationSyntax property)
            {
                return TryGetEffectiveGetterBody(property.ExpressionBody, property.AccessorList);
            }
 
            if (declaration is IndexerDeclarationSyntax indexer)
            {
                return TryGetEffectiveGetterBody(indexer.ExpressionBody, indexer.AccessorList);
            }
 
            return null;
        }
 
        public static SyntaxNode TryGetEffectiveGetterBody(ArrowExpressionClauseSyntax propertyBody, AccessorListSyntax accessorList)
        {
            if (propertyBody != null)
            {
                return propertyBody.Expression;
            }
 
            var firstGetter = accessorList?.Accessors.Where(a => a.IsKind(SyntaxKind.GetAccessorDeclaration)).FirstOrDefault();
            if (firstGetter == null)
            {
                return null;
            }
 
            return (SyntaxNode)firstGetter.Body ?? firstGetter.ExpressionBody?.Expression;
        }
 
        public static SyntaxTokenList? TryGetFieldOrPropertyModifiers(SyntaxNode node)
        {
            if (node is FieldDeclarationSyntax fieldDecl)
                return fieldDecl.Modifiers;
 
            if (node is PropertyDeclarationSyntax propertyDecl)
                return propertyDecl.Modifiers;
 
            return null;
        }
 
        public static bool IsParameterlessConstructor(SyntaxNode declaration)
        {
            if (declaration is not ConstructorDeclarationSyntax ctor)
            {
                return false;
            }
 
            return ctor.ParameterList.Parameters.Count == 0;
        }
 
        public static bool HasBackingField(PropertyDeclarationSyntax property)
        {
            if (property.Modifiers.Any(SyntaxKind.AbstractKeyword) ||
                property.Modifiers.Any(SyntaxKind.ExternKeyword))
            {
                return false;
            }
 
            return property.ExpressionBody == null
                && property.AccessorList.Accessors.Any(e => e.Body == null && e.ExpressionBody == null);
        }
 
        /// <summary>
        /// True if the specified declaration node is an async method, anonymous function, lambda, local function.
        /// </summary>
        public static bool IsAsyncDeclaration(SyntaxNode declaration)
        {
            // lambdas and anonymous functions
            if (declaration is AnonymousFunctionExpressionSyntax anonymousFunction)
            {
                return anonymousFunction.AsyncKeyword.IsKind(SyntaxKind.AsyncKeyword);
            }
 
            // expression bodied methods/local functions:
            if (declaration.IsKind(SyntaxKind.ArrowExpressionClause))
            {
                declaration = declaration.Parent;
            }
 
            return declaration switch
            {
                MethodDeclarationSyntax method => method.Modifiers.Any(SyntaxKind.AsyncKeyword),
                LocalFunctionStatementSyntax localFunction => localFunction.Modifiers.Any(SyntaxKind.AsyncKeyword),
                _ => false
            };
        }
 
        /// <summary>
        /// Returns a list of all await expressions, await foreach statements, await using declarations and yield statements in the given body,
        /// in the order in which they occur.
        /// </summary>
        /// <returns>
        /// <see cref="AwaitExpressionSyntax"/> for await expressions,
        /// <see cref="YieldStatementSyntax"/> for yield return statements,
        /// <see cref="CommonForEachStatementSyntax"/> for await foreach statements,
        /// <see cref="VariableDeclaratorSyntax"/> for await using declarators.
        /// <see cref="UsingStatementSyntax"/> for await using statements.
        /// </returns>
        public static IEnumerable<SyntaxNode> GetSuspensionPoints(SyntaxNode body)
            => body.DescendantNodesAndSelf(LambdaUtilities.IsNotLambda).Where(SyntaxBindingUtilities.BindsToResumableStateMachineState);
 
        // Presence of yield break or yield return indicates state machine, but yield break does not bind to a resumable state. 
        public static bool IsIterator(SyntaxNode body)
            => body.DescendantNodesAndSelf(LambdaUtilities.IsNotLambda).Any(n => n is YieldStatementSyntax);
    }
}