File: Syntax\LookupPosition.cs
Web Access
Project: src\src\Compilers\CSharp\Portable\Microsoft.CodeAnalysis.CSharp.csproj (Microsoft.CodeAnalysis.CSharp)
// 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.Diagnostics;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.Syntax
{
    /// <summary>
    /// This class contains a variety of helper methods for determining whether a
    /// position is within the scope (and not just the span) of a node.  In general,
    /// general, the scope extends from the first token up to, but not including,
    /// the last token. For example, the open brace of a block is within the scope
    /// of the block, but the close brace is not.
    /// </summary>
    internal static class LookupPosition
    {
        /// <summary>
        /// A position is considered to be inside a block if it is on or after
        /// the open brace and strictly before the close brace.
        /// </summary>
        internal static bool IsInBlock(int position, BlockSyntax? blockOpt)
        {
            return blockOpt != null && IsBeforeToken(position, blockOpt, blockOpt.CloseBraceToken);
        }
 
        internal static bool IsInExpressionBody(
            int position,
            ArrowExpressionClauseSyntax? expressionBodyOpt,
            SyntaxToken semicolonToken)
        {
            return expressionBodyOpt != null
                && IsBeforeToken(position, expressionBodyOpt, semicolonToken);
        }
 
        private static bool IsInBody(int position, BlockSyntax? blockOpt, ArrowExpressionClauseSyntax? exprOpt, SyntaxToken semiOpt)
        {
            return IsInExpressionBody(position, exprOpt, semiOpt)
                || IsInBlock(position, blockOpt);
        }
 
        /// <summary>
        /// A position is inside a property body only if it is inside an expression body.
        /// All block bodies for properties are part of the accessor declaration (a type
        /// of BaseMethodDeclaration), not the property declaration.
        /// </summary>
        internal static bool IsInBody(int position,
            PropertyDeclarationSyntax property)
            => IsInBody(position, blockOpt: null, property.GetExpressionBodySyntax(), property.SemicolonToken);
 
        /// <summary>
        /// A position is inside a property body only if it is inside an expression body.
        /// All block bodies for properties are part of the accessor declaration (a type
        /// of BaseMethodDeclaration), not the property declaration.
        /// </summary>
        internal static bool IsInBody(int position,
            IndexerDeclarationSyntax indexer)
            => IsInBody(position, blockOpt: null, indexer.GetExpressionBodySyntax(), indexer.SemicolonToken);
 
        /// <summary>
        /// A position is inside an accessor body if it is inside the block or expression
        /// body. 
        /// </summary>
        internal static bool IsInBody(int position, AccessorDeclarationSyntax method)
            => IsInBody(position, method.Body, method.GetExpressionBodySyntax(), method.SemicolonToken);
 
        /// <summary>
        /// A position is inside a body if it is inside the block or expression
        /// body. 
        ///
        /// A position is considered to be inside a block if it is on or after
        /// the open brace and strictly before the close brace. A position is
        /// considered to be inside an expression body if it is on or after
        /// the '=>' and strictly before the semicolon.
        /// </summary>
        internal static bool IsInBody(int position, BaseMethodDeclarationSyntax method)
            => IsInBody(position, method.Body, method.GetExpressionBodySyntax(), method.SemicolonToken);
 
        internal static bool IsBetweenTokens(int position, SyntaxToken firstIncluded, SyntaxToken firstExcluded)
        {
            return position >= firstIncluded.SpanStart && IsBeforeToken(position, firstExcluded);
        }
 
        /// <summary>
        /// Returns true if position is within the given node and before the first excluded token.
        /// </summary>
        private static bool IsBeforeToken(int position, CSharpSyntaxNode node, SyntaxToken firstExcluded)
        {
            return IsBeforeToken(position, firstExcluded) && position >= node.SpanStart;
        }
 
        private static bool IsBeforeToken(int position, SyntaxToken firstExcluded)
        {
            return firstExcluded.Kind() == SyntaxKind.None || position < firstExcluded.SpanStart;
        }
 
        internal static bool IsInAttributeSpecification(int position, SyntaxList<AttributeListSyntax> attributesSyntaxList)
        {
            int count = attributesSyntaxList.Count;
            if (count == 0)
            {
                return false;
            }
 
            var startToken = attributesSyntaxList[0].OpenBracketToken;
            var endToken = attributesSyntaxList[count - 1].CloseBracketToken;
            return IsBetweenTokens(position, startToken, endToken);
        }
 
        internal static bool IsInTypeParameterList(int position, TypeDeclarationSyntax typeDecl)
        {
            var typeParameterListOpt = typeDecl.TypeParameterList;
            return typeParameterListOpt != null && IsBeforeToken(position, typeParameterListOpt, typeParameterListOpt.GreaterThanToken);
        }
 
        internal static bool IsInParameterList(int position, BaseMethodDeclarationSyntax methodDecl)
        {
            var parameterList = methodDecl.ParameterList;
            return IsBeforeToken(position, parameterList, parameterList.CloseParenToken);
        }
 
        internal static bool IsInParameterList(int position, ParameterListSyntax parameterList)
            => parameterList != null && IsBeforeToken(position, parameterList, parameterList.CloseParenToken);
 
        internal static bool IsInMethodDeclaration(int position, BaseMethodDeclarationSyntax methodDecl)
        {
            Debug.Assert(methodDecl != null);
 
            var body = methodDecl.Body;
            if (body == null)
            {
                return IsBeforeToken(position, methodDecl, methodDecl.SemicolonToken);
            }
 
            return IsBeforeToken(position, methodDecl, body.CloseBraceToken) ||
                   IsInExpressionBody(position, methodDecl.GetExpressionBodySyntax(), methodDecl.SemicolonToken);
        }
 
        internal static bool IsInMethodDeclaration(int position, AccessorDeclarationSyntax accessorDecl)
        {
            Debug.Assert(accessorDecl != null);
 
            var body = accessorDecl.Body;
            SyntaxToken lastToken = body == null ? accessorDecl.SemicolonToken : body.CloseBraceToken;
            return IsBeforeToken(position, accessorDecl, lastToken);
        }
 
        internal static bool IsInDelegateDeclaration(int position, DelegateDeclarationSyntax delegateDecl)
        {
            Debug.Assert(delegateDecl != null);
 
            return IsBeforeToken(position, delegateDecl, delegateDecl.SemicolonToken);
        }
 
        internal static bool IsInTypeDeclaration(int position, BaseTypeDeclarationSyntax typeDecl)
        {
            Debug.Assert(typeDecl != null);
 
            return IsBeforeToken(position, typeDecl, typeDecl.CloseBraceToken);
        }
 
        internal static bool IsInNamespaceDeclaration(int position, NamespaceDeclarationSyntax namespaceDecl)
        {
            Debug.Assert(namespaceDecl != null);
 
            return IsBetweenTokens(position, namespaceDecl.NamespaceKeyword, namespaceDecl.CloseBraceToken);
        }
 
        internal static bool IsInNamespaceDeclaration(int position, FileScopedNamespaceDeclarationSyntax namespaceDecl)
        {
            Debug.Assert(namespaceDecl != null);
 
            return position >= namespaceDecl.SpanStart;
        }
 
        internal static bool IsInConstructorParameterScope(int position, ConstructorDeclarationSyntax constructorDecl)
        {
            Debug.Assert(constructorDecl != null);
 
            var initializerOpt = constructorDecl.Initializer;
            var hasBody = constructorDecl.Body != null || constructorDecl.ExpressionBody != null;
 
            if (!hasBody)
            {
                var nextToken = (SyntaxToken)SyntaxNavigator.Instance.GetNextToken(constructorDecl, predicate: null, stepInto: null);
                return initializerOpt == null ?
                    position >= constructorDecl.ParameterList.CloseParenToken.Span.End && IsBeforeToken(position, nextToken) :
                    IsBetweenTokens(position, initializerOpt.ColonToken, nextToken);
            }
 
            return initializerOpt == null ?
                IsInBody(position, constructorDecl) :
                IsBetweenTokens(position, initializerOpt.ColonToken,
                                constructorDecl.SemicolonToken.Kind() == SyntaxKind.None ? constructorDecl.Body!.CloseBraceToken : constructorDecl.SemicolonToken);
        }
 
        internal static bool IsInMethodTypeParameterScope(int position, MethodDeclarationSyntax methodDecl)
        {
            Debug.Assert(methodDecl != null);
            Debug.Assert(IsInMethodDeclaration(position, methodDecl));
 
            if (methodDecl.TypeParameterList == null)
            {
                // no type parameters => they are not in scope
                return false;
            }
 
            // optimization for a common case - when position is in the ReturnType, we can see type parameters
            if (methodDecl.ReturnType.FullSpan.Contains(position))
            {
                return true;
            }
 
            // Must be in the method, but not in an attribute on the method.
            if (IsInAttributeSpecification(position, methodDecl.AttributeLists))
            {
                return false;
            }
 
            var explicitInterfaceSpecifier = methodDecl.ExplicitInterfaceSpecifier;
            var firstNameToken = explicitInterfaceSpecifier == null ? methodDecl.Identifier : explicitInterfaceSpecifier.GetFirstToken();
            var firstPostNameToken = methodDecl.TypeParameterList.LessThanToken;
 
            // Scope does not include method name.
            return !IsBetweenTokens(position, firstNameToken, firstPostNameToken);
        }
 
        internal static bool IsInLocalFunctionTypeParameterScope(int position, LocalFunctionStatementSyntax localFunction)
        {
            Debug.Assert(localFunction != null);
 
            if (localFunction.TypeParameterList == null)
            {
                // no type parameters => they are not in scope
                return false;
            }
 
            // optimization for a common case - when position is in the ReturnType, we can see type parameters
            if (localFunction.ReturnType.FullSpan.Contains(position))
            {
                return true;
            }
 
            // Must be in the local function, but not in an attribute on the method.
            if (IsInAttributeSpecification(position, localFunction.AttributeLists))
            {
                return false;
            }
 
            var firstNameToken = localFunction.Identifier;
            var firstPostNameToken = localFunction.TypeParameterList.LessThanToken;
 
            // Scope does not include local function name.
            return !IsBetweenTokens(position, firstNameToken, firstPostNameToken);
        }
 
        /// <remarks>
        /// Used to determine whether it would be appropriate to use the binder for the statement (if any).
        /// Not used to determine whether the position is syntactically within the statement.
        /// </remarks>
        internal static bool IsInStatementScope(int position, StatementSyntax statement)
        {
            Debug.Assert(statement != null);
 
            if (statement.Kind() == SyntaxKind.EmptyStatement)
            {
                return false;
            }
 
            // CONSIDER: the check for default(SyntaxToken) could go in IsBetweenTokens,
            // but this is where it has special meaning.
            SyntaxToken firstIncludedToken = GetFirstIncludedToken(statement);
            return firstIncludedToken != default(SyntaxToken) &&
                   IsBetweenTokens(position, firstIncludedToken, GetFirstExcludedToken(statement));
        }
 
        /// <remarks>
        /// Used to determine whether it would be appropriate to use the binder for the switch section (if any).
        /// Not used to determine whether the position is syntactically within the statement.
        /// </remarks>
        internal static bool IsInSwitchSectionScope(int position, SwitchSectionSyntax section)
        {
            Debug.Assert(section != null);
            return section.Span.Contains(position);
        }
 
        /// <remarks>
        /// Used to determine whether it would be appropriate to use the binder for the statement (if any).
        /// Not used to determine whether the position is syntactically within the statement.
        /// </remarks>
        internal static bool IsInCatchBlockScope(int position, CatchClauseSyntax catchClause)
        {
            Debug.Assert(catchClause != null);
 
            return IsBetweenTokens(position, catchClause.Block.OpenBraceToken, catchClause.Block.CloseBraceToken);
        }
 
        /// <remarks>
        /// Used to determine whether it would be appropriate to use the binder for the statement (if any).
        /// Not used to determine whether the position is syntactically within the statement.
        /// </remarks>
        internal static bool IsInCatchFilterScope(int position, CatchFilterClauseSyntax filterClause)
        {
            Debug.Assert(filterClause != null);
 
            return IsBetweenTokens(position, filterClause.OpenParenToken, filterClause.CloseParenToken);
        }
 
        private static SyntaxToken GetFirstIncludedToken(StatementSyntax statement)
        {
            Debug.Assert(statement != null);
            switch (statement.Kind())
            {
                case SyntaxKind.Block:
                    return ((BlockSyntax)statement).OpenBraceToken;
                case SyntaxKind.BreakStatement:
                    return ((BreakStatementSyntax)statement).BreakKeyword;
                case SyntaxKind.CheckedStatement:
                case SyntaxKind.UncheckedStatement:
                    return ((CheckedStatementSyntax)statement).Keyword;
                case SyntaxKind.ContinueStatement:
                    return ((ContinueStatementSyntax)statement).ContinueKeyword;
                case SyntaxKind.ExpressionStatement:
                case SyntaxKind.LocalDeclarationStatement:
                    return statement.GetFirstToken();
                case SyntaxKind.DoStatement:
                    return ((DoStatementSyntax)statement).DoKeyword;
                case SyntaxKind.EmptyStatement:
                    return default(SyntaxToken); //The caller will have to check for this.
                case SyntaxKind.FixedStatement:
                    return ((FixedStatementSyntax)statement).FixedKeyword;
                case SyntaxKind.ForEachStatement:
                case SyntaxKind.ForEachVariableStatement:
                    return ((CommonForEachStatementSyntax)statement).OpenParenToken.GetNextToken();
                case SyntaxKind.ForStatement:
                    return ((ForStatementSyntax)statement).OpenParenToken.GetNextToken();
                case SyntaxKind.GotoDefaultStatement:
                case SyntaxKind.GotoCaseStatement:
                case SyntaxKind.GotoStatement:
                    return ((GotoStatementSyntax)statement).GotoKeyword;
                case SyntaxKind.IfStatement:
                    return ((IfStatementSyntax)statement).IfKeyword;
                case SyntaxKind.LabeledStatement:
                    return ((LabeledStatementSyntax)statement).Identifier;
                case SyntaxKind.LockStatement:
                    return ((LockStatementSyntax)statement).LockKeyword;
                case SyntaxKind.ReturnStatement:
                    return ((ReturnStatementSyntax)statement).ReturnKeyword;
                case SyntaxKind.SwitchStatement:
                    return ((SwitchStatementSyntax)statement).Expression.GetFirstToken();
                case SyntaxKind.ThrowStatement:
                    return ((ThrowStatementSyntax)statement).ThrowKeyword;
                case SyntaxKind.TryStatement:
                    return ((TryStatementSyntax)statement).TryKeyword;
                case SyntaxKind.UnsafeStatement:
                    return ((UnsafeStatementSyntax)statement).UnsafeKeyword;
                case SyntaxKind.UsingStatement:
                    return ((UsingStatementSyntax)statement).UsingKeyword;
                case SyntaxKind.WhileStatement:
                    return ((WhileStatementSyntax)statement).WhileKeyword;
                case SyntaxKind.YieldReturnStatement:
                case SyntaxKind.YieldBreakStatement:
                    return ((YieldStatementSyntax)statement).YieldKeyword;
                case SyntaxKind.LocalFunctionStatement:
                    return statement.GetFirstToken();
                default:
                    throw ExceptionUtilities.UnexpectedValue(statement.Kind());
            }
        }
 
        internal static SyntaxToken GetFirstExcludedToken(StatementSyntax statement)
        {
            Debug.Assert(statement != null);
            switch (statement.Kind())
            {
                case SyntaxKind.Block:
                    return ((BlockSyntax)statement).CloseBraceToken;
                case SyntaxKind.BreakStatement:
                    return ((BreakStatementSyntax)statement).SemicolonToken;
                case SyntaxKind.CheckedStatement:
                case SyntaxKind.UncheckedStatement:
                    return ((CheckedStatementSyntax)statement).Block.CloseBraceToken;
                case SyntaxKind.ContinueStatement:
                    return ((ContinueStatementSyntax)statement).SemicolonToken;
                case SyntaxKind.LocalDeclarationStatement:
                    return ((LocalDeclarationStatementSyntax)statement).SemicolonToken;
                case SyntaxKind.DoStatement:
                    return ((DoStatementSyntax)statement).SemicolonToken;
                case SyntaxKind.EmptyStatement:
                    return ((EmptyStatementSyntax)statement).SemicolonToken;
                case SyntaxKind.ExpressionStatement:
                    return ((ExpressionStatementSyntax)statement).SemicolonToken;
                case SyntaxKind.FixedStatement:
                    return GetFirstExcludedToken(((FixedStatementSyntax)statement).Statement);
                case SyntaxKind.ForEachStatement:
                case SyntaxKind.ForEachVariableStatement:
                    return GetFirstExcludedToken(((CommonForEachStatementSyntax)statement).Statement);
                case SyntaxKind.ForStatement:
                    return GetFirstExcludedToken(((ForStatementSyntax)statement).Statement);
                case SyntaxKind.GotoDefaultStatement:
                case SyntaxKind.GotoCaseStatement:
                case SyntaxKind.GotoStatement:
                    return ((GotoStatementSyntax)statement).SemicolonToken;
                case SyntaxKind.IfStatement:
                    return GetFirstExcludedIfStatementToken((IfStatementSyntax)statement);
                case SyntaxKind.LabeledStatement:
                    return GetFirstExcludedToken(((LabeledStatementSyntax)statement).Statement);
                case SyntaxKind.LockStatement:
                    return GetFirstExcludedToken(((LockStatementSyntax)statement).Statement);
                case SyntaxKind.ReturnStatement:
                    return ((ReturnStatementSyntax)statement).SemicolonToken;
                case SyntaxKind.SwitchStatement:
                    return ((SwitchStatementSyntax)statement).CloseBraceToken;
                case SyntaxKind.ThrowStatement:
                    return ((ThrowStatementSyntax)statement).SemicolonToken;
                case SyntaxKind.TryStatement:
                    TryStatementSyntax tryStmt = (TryStatementSyntax)statement;
 
                    FinallyClauseSyntax? finallyClause = tryStmt.Finally;
                    if (finallyClause != null)
                    {
                        return finallyClause.Block.CloseBraceToken;
                    }
 
                    CatchClauseSyntax? lastCatch = tryStmt.Catches.LastOrDefault();
                    if (lastCatch != null)
                    {
                        return lastCatch.Block.CloseBraceToken;
                    }
                    return tryStmt.Block.CloseBraceToken;
                case SyntaxKind.UnsafeStatement:
                    return ((UnsafeStatementSyntax)statement).Block.CloseBraceToken;
                case SyntaxKind.UsingStatement:
                    return GetFirstExcludedToken(((UsingStatementSyntax)statement).Statement);
                case SyntaxKind.WhileStatement:
                    return GetFirstExcludedToken(((WhileStatementSyntax)statement).Statement);
                case SyntaxKind.YieldReturnStatement:
                case SyntaxKind.YieldBreakStatement:
                    return ((YieldStatementSyntax)statement).SemicolonToken;
                case SyntaxKind.LocalFunctionStatement:
                    LocalFunctionStatementSyntax localFunctionStmt = (LocalFunctionStatementSyntax)statement;
                    if (localFunctionStmt.Body != null)
                        return GetFirstExcludedToken(localFunctionStmt.Body);
                    if (localFunctionStmt.SemicolonToken != default(SyntaxToken))
                        return localFunctionStmt.SemicolonToken;
                    return localFunctionStmt.ParameterList.GetLastToken();
                default:
                    throw ExceptionUtilities.UnexpectedValue(statement.Kind());
            }
        }
 
        private static SyntaxToken GetFirstExcludedIfStatementToken(IfStatementSyntax ifStmt)
        {
            while (true)
            {
                ElseClauseSyntax? elseOpt = ifStmt.Else;
                if (elseOpt is null)
                {
                    return GetFirstExcludedToken(ifStmt.Statement);
                }
                if (elseOpt.Statement is IfStatementSyntax nestedIf)
                {
                    ifStmt = nestedIf;
                }
                else
                {
                    return GetFirstExcludedToken(elseOpt.Statement);
                }
            }
        }
 
        internal static bool IsInAnonymousFunctionOrQuery(int position, SyntaxNode lambdaExpressionOrQueryNode)
        {
            Debug.Assert(lambdaExpressionOrQueryNode.IsAnonymousFunction() || lambdaExpressionOrQueryNode.IsQuery());
 
            SyntaxToken firstIncluded;
            CSharpSyntaxNode body;
 
            switch (lambdaExpressionOrQueryNode.Kind())
            {
                case SyntaxKind.SimpleLambdaExpression:
                    SimpleLambdaExpressionSyntax simple = (SimpleLambdaExpressionSyntax)lambdaExpressionOrQueryNode;
                    firstIncluded = simple.ArrowToken;
                    body = simple.Body;
                    break;
 
                case SyntaxKind.ParenthesizedLambdaExpression:
                    ParenthesizedLambdaExpressionSyntax parenthesized = (ParenthesizedLambdaExpressionSyntax)lambdaExpressionOrQueryNode;
                    firstIncluded = parenthesized.ArrowToken;
                    body = parenthesized.Body;
                    break;
 
                case SyntaxKind.AnonymousMethodExpression:
                    AnonymousMethodExpressionSyntax anon = (AnonymousMethodExpressionSyntax)lambdaExpressionOrQueryNode;
                    body = anon.Block;
                    firstIncluded = body.GetFirstToken(includeZeroWidth: true);
                    break;
 
                default:
                    // OK, so we have some kind of query clause.  They all start with a keyword token, so we'll skip that.
                    firstIncluded = lambdaExpressionOrQueryNode.GetFirstToken().GetNextToken();
                    return IsBetweenTokens(position, firstIncluded, lambdaExpressionOrQueryNode.GetLastToken().GetNextToken());
            }
 
            var bodyStatement = body as StatementSyntax;
            var firstExcluded = bodyStatement != null ?
                GetFirstExcludedToken(bodyStatement) :
                (SyntaxToken)SyntaxNavigator.Instance.GetNextToken(body, predicate: null, stepInto: null);
 
            return IsBetweenTokens(position, firstIncluded, firstExcluded);
        }
 
        internal static bool IsInXmlAttributeValue(int position, XmlAttributeSyntax attribute)
        {
            return IsBetweenTokens(position, attribute.StartQuoteToken, attribute.EndQuoteToken);
        }
    }
}