File: EditAndContinue\BreakpointSpans.cs
Web Access
Project: src\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.
 
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis.CSharp.Extensions;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
 
namespace Microsoft.CodeAnalysis.CSharp.EditAndContinue;
 
internal static class BreakpointSpans
{
    public static bool TryGetBreakpointSpan(SyntaxTree tree, int position, CancellationToken cancellationToken, out TextSpan breakpointSpan)
    {
        var source = tree.GetText(cancellationToken);
 
        // If the line is entirely whitespace, then don't set any breakpoint there.
        var line = source.Lines.GetLineFromPosition(position);
        if (IsBlank(line))
        {
            breakpointSpan = default;
            return false;
        }
 
        // If the user is asking for breakpoint in an inactive region, then just create a line
        // breakpoint there.
        if (tree.IsInInactiveRegion(position, cancellationToken))
        {
            breakpointSpan = default;
            return true;
        }
 
        var root = tree.GetRoot(cancellationToken);
        return TryGetClosestBreakpointSpan(root, position, minLength: 0, out breakpointSpan);
    }
 
    private static bool IsBlank(TextLine line)
    {
        var text = line.ToString();
 
        for (var i = 0; i < text.Length; i++)
        {
            if (!SyntaxFacts.IsWhitespace(text[i]))
            {
                return false;
            }
        }
 
        return true;
    }
 
    /// <summary>
    /// Given a syntax token determines a text span delimited by the closest applicable sequence points 
    /// encompassing the token.
    /// </summary>
    /// <param name="minLength">
    /// In case there are multiple breakpoint spans starting at the given <paramref name="position"/>,
    /// <paramref name="minLength"/> can be used to disambiguate between them. 
    /// The inner-most available span whose length is at least <paramref name="minLength"/> is returned.
    /// </param>
    /// <remarks>
    /// If the span exists it is possible to place a breakpoint at the given position.
    /// </remarks>
    public static bool TryGetClosestBreakpointSpan(SyntaxNode root, int position, int minLength, out TextSpan span)
    {
        var node = root.FindToken(position).Parent;
        var candidate = (TextSpan?)null;
 
        while (node != null)
        {
            var breakpointSpan = TryCreateSpanForNode(node, position);
            if (breakpointSpan.HasValue)
            {
                span = breakpointSpan.Value;
                if (span == default)
                {
                    break;
                }
 
                // the new breakpoint span doesn't alight with the previously found breakpoint span, return the previous one:
                if (candidate.HasValue && breakpointSpan.Value.Start != candidate.Value.Start)
                {
                    span = candidate.Value;
                    return true;
                }
 
                // The span length meets the requirement:
                if (breakpointSpan.Value.Length >= minLength)
                {
                    span = breakpointSpan.Value;
                    return true;
                }
 
                candidate = breakpointSpan;
            }
 
            node = node.Parent;
        }
 
        span = candidate.GetValueOrDefault();
        return candidate.HasValue;
    }
 
    private static TextSpan CreateSpan(SyntaxToken startToken, SyntaxToken endToken)
        => TextSpan.FromBounds(startToken.SpanStart, endToken.Span.End);
 
    private static TextSpan CreateSpan(SyntaxNode node)
        => CreateSpan(node.GetFirstToken(), node.GetLastToken());
 
    private static TextSpan CreateSpan(SyntaxNode node, SyntaxToken token)
        => TextSpan.FromBounds(node.SpanStart, token.Span.End);
 
    private static TextSpan CreateSpan(SyntaxToken token)
        => TextSpan.FromBounds(token.SpanStart, token.Span.End);
 
    private static TextSpan CreateSpan(SyntaxTokenList startOpt, SyntaxNodeOrToken startFallbackOpt, SyntaxNodeOrToken endOpt)
    {
        Debug.Assert(startFallbackOpt != default || endOpt != default);
 
        int startPos;
        if (startOpt.Count > 0)
        {
            startPos = startOpt.First().SpanStart;
        }
        else if (startFallbackOpt != default)
        {
            startPos = startFallbackOpt.SpanStart;
        }
        else
        {
            startPos = endOpt.SpanStart;
        }
 
        int endPos;
        if (endOpt != default)
        {
            endPos = GetEndPosition(endOpt);
        }
        else
        {
            endPos = GetEndPosition(startFallbackOpt);
        }
 
        return TextSpan.FromBounds(startPos, endPos);
    }
 
    private static int GetEndPosition(SyntaxNodeOrToken nodeOrToken)
    {
        if (nodeOrToken.IsToken)
        {
            return nodeOrToken.Span.End;
        }
        else
        {
            return nodeOrToken.AsNode()!.GetLastToken().Span.End;
        }
    }
 
    private static TextSpan? TryCreateSpanForNode(SyntaxNode node, int position)
    {
        if (node == null)
        {
            return null;
        }
 
        switch (node.Kind())
        {
            case SyntaxKind.MethodDeclaration:
            case SyntaxKind.OperatorDeclaration:
            case SyntaxKind.ConversionOperatorDeclaration:
            case SyntaxKind.DestructorDeclaration:
                var methodDeclaration = (BaseMethodDeclarationSyntax)node;
                return (methodDeclaration.Body != null) ? CreateSpanForBlock(methodDeclaration.Body, position) : methodDeclaration.ExpressionBody?.Expression.Span;
 
            case SyntaxKind.ConstructorDeclaration:
                return CreateSpanForConstructorDeclaration((ConstructorDeclarationSyntax)node, position);
 
            case SyntaxKind.RecordDeclaration:
            case SyntaxKind.RecordStructDeclaration:
            case SyntaxKind.StructDeclaration:
            case SyntaxKind.ClassDeclaration:
                var typeDeclaration = (TypeDeclarationSyntax)node;
                if (typeDeclaration.ParameterList != null)
                {
                    // after brace or semicolon
                    // class C<T>(...) {$$ ... }
                    // class C<T>(...) ;$$
                    if (position > LastNotMissing(typeDeclaration.SemicolonToken, typeDeclaration.OpenBraceToken).SpanStart)
                    {
                        return null;
                    }
 
                    // on or after explicit base initializer:
                    //   C<T>(...) :$$ [|B(...)|], I
                    //   C<T>(...) : [|B(...)|], I where ... $$
                    var baseInitializer = (PrimaryConstructorBaseTypeSyntax?)typeDeclaration.BaseList?.Types.FirstOrDefault(t => t.IsKind(SyntaxKind.PrimaryConstructorBaseType));
                    if (baseInitializer != null && position > typeDeclaration.BaseList!.ColonToken.SpanStart)
                    {
                        return CreateSpanForExplicitPrimaryConstructorInitializer(baseInitializer);
                    }
 
                    // record properties and copy constructor
                    if (position >= typeDeclaration.Identifier.SpanStart && node is RecordDeclarationSyntax recordDeclaration)
                    {
                        // on identifier:
                        // record $$C<T>(...) : B(...);
                        // record C<T>$$(...) : B(...);
                        if (position <= typeDeclaration.ParameterList.SpanStart)
                        {
                            // copy-constructor: [|C<T>|]
                            return CreateSpanForCopyConstructor(recordDeclaration);
                        }
 
                        // on parameter:
                        // record C<T>(..., $$ int p, ...) : B(...);
                        if (position < typeDeclaration.ParameterList.CloseParenToken.Span.End)
                        {
                            var parameter = GetParameter(position, typeDeclaration.ParameterList.Parameters);
                            if (parameter != null)
                            {
                                // [A][|int p|] = default
                                return CreateSpanForRecordParameter(parameter);
                            }
 
                            static ParameterSyntax? GetParameter(int position, SeparatedSyntaxList<ParameterSyntax> parameters)
                            {
                                if (parameters.Count == 0)
                                {
                                    return null;
                                }
 
                                for (var i = 0; i < parameters.SeparatorCount; i++)
                                {
                                    var separator = parameters.GetSeparator(i);
                                    if (position <= separator.SpanStart)
                                    {
                                        return parameters[i];
                                    }
                                }
 
                                return parameters.Last();
                            }
                        }
                    }
 
                    // explicit base initializer
                    //   C<T>(...) : [|B(...)|]
                    // implicit base initializer
                    //   [|C<T>(...)|]
                    return (baseInitializer != null)
                        ? CreateSpanForExplicitPrimaryConstructorInitializer(baseInitializer)
                        : CreateSpanForImplicitPrimaryConstructorInitializer(typeDeclaration);
                }
 
                return null;
 
            case SyntaxKind.VariableDeclarator:
                // handled by the parent node
                return null;
 
            case SyntaxKind.VariableDeclaration:
                return TryCreateSpanForVariableDeclaration((VariableDeclarationSyntax)node, position);
 
            case SyntaxKind.EventFieldDeclaration:
            case SyntaxKind.FieldDeclaration:
                return TryCreateSpanForFieldDeclaration((BaseFieldDeclarationSyntax)node, position);
 
            case SyntaxKind.ElseClause:
                return TryCreateSpanForNode(((ElseClauseSyntax)node).Statement, position);
 
            case SyntaxKind.CatchFilterClause:
                return CreateSpan(node);
 
            case SyntaxKind.CatchClause:
                return CreateSpanForCatchClause((CatchClauseSyntax)node);
 
            case SyntaxKind.FinallyClause:
                return TryCreateSpanForNode(((FinallyClauseSyntax)node).Block, position);
 
            case SyntaxKind.CaseSwitchLabel:
            case SyntaxKind.DefaultSwitchLabel:
                return TryCreateSpanForSwitchLabel((SwitchLabelSyntax)node, position);
 
            case SyntaxKind.CasePatternSwitchLabel:
                var caseClause = (CasePatternSwitchLabelSyntax)node;
                return caseClause.WhenClause == null
                    ? TryCreateSpanForSwitchLabel((SwitchLabelSyntax)node, position)
                    : CreateSpan(caseClause.WhenClause);
 
            case SyntaxKind.SwitchExpressionArm:
                var switchArm = (SwitchExpressionArmSyntax)node;
                return createSpanForSwitchArm(switchArm);
 
                TextSpan createSpanForSwitchArm(SwitchExpressionArmSyntax switchArm)
                    => CreateSpan((position <= switchArm.WhenClause?.FullSpan.End == true) ? switchArm.WhenClause : switchArm.Expression);
 
            case SyntaxKind.SwitchExpression when
                        node is SwitchExpressionSyntax switchExpression &&
                        switchExpression.Arms.Count > 0 &&
                        position >= switchExpression.OpenBraceToken.Span.End &&
                        position <= switchExpression.CloseBraceToken.Span.Start:
                // This can occur if the cursor is on a separator. Find the nearest switch arm.
                switchArm = switchExpression.Arms.LastOrDefault(arm => position >= arm.FullSpan.Start) ?? switchExpression.Arms.First();
                return createSpanForSwitchArm(switchArm);
 
            case SyntaxKind.WhenClause:
                return CreateSpan(node);
 
            case SyntaxKind.GetAccessorDeclaration:
            case SyntaxKind.SetAccessorDeclaration:
            case SyntaxKind.InitAccessorDeclaration:
            case SyntaxKind.AddAccessorDeclaration:
            case SyntaxKind.RemoveAccessorDeclaration:
            case SyntaxKind.UnknownAccessorDeclaration:
                var accessor = (AccessorDeclarationSyntax)node;
                if (accessor.ExpressionBody != null)
                {
                    return CreateSpan(accessor.ExpressionBody.Expression);
                }
                else if (accessor.Body != null)
                {
                    return TryCreateSpanForNode(accessor.Body, position);
                }
                else
                {
                    return CreateSpanForAutoPropertyAccessor(accessor);
                }
 
            case SyntaxKind.PropertyDeclaration:
                var property = (PropertyDeclarationSyntax)node;
 
                // Note that expression body, initializer and accessors are mutually exclusive.
 
                // int P => [|expr|]
                if (property.ExpressionBody != null)
                {
                    return property.ExpressionBody.Expression.Span;
                }
 
                // int P { get; set; } = [|expr|]
                if (property.Initializer != null && position >= property.Initializer.FullSpan.Start)
                {
                    return property.Initializer.Value.Span;
                }
 
                // properties without expression body have accessor list:
                Contract.ThrowIfNull(property.AccessorList);
 
                // int P { get [|{|] ... } set { ... } }
                // int P { [|get;|] [|set;|] }
                return CreateSpanForAccessors(property.AccessorList.Accessors, position);
 
            case SyntaxKind.IndexerDeclaration:
                // int this[args] => [|expr|]
                var indexer = (IndexerDeclarationSyntax)node;
                if (indexer.ExpressionBody != null)
                {
                    return indexer.ExpressionBody.Expression.Span;
                }
 
                // indexers without expression body have accessor list:
                Contract.ThrowIfNull(indexer.AccessorList);
 
                // int this[args] { get [|{|] ... } set { ... } }
                return CreateSpanForAccessors(indexer.AccessorList.Accessors, position);
 
            case SyntaxKind.EventDeclaration:
                // event Action P { add [|{|] ... } remove { ... } }
                // event Action P { [|add;|] [|remove;|] }
                var @event = (EventDeclarationSyntax)node;
                return @event.AccessorList != null ? CreateSpanForAccessors(@event.AccessorList.Accessors, position) : null;
 
            case SyntaxKind.BaseConstructorInitializer:
            case SyntaxKind.ThisConstructorInitializer:
                return CreateSpanForExplicitConstructorInitializer((ConstructorInitializerSyntax)node);
 
            // Query clauses:
            // 
            // Used when the user's initial location is on a query keyword itself (as
            // opposed to inside an expression inside the query clause).  It places the bp on the
            // appropriate child expression in the clause.
 
            case SyntaxKind.FromClause:
                var fromClause = (FromClauseSyntax)node;
                return TryCreateSpanForNode(fromClause.Expression, position);
 
            case SyntaxKind.JoinClause:
                var joinClause = (JoinClauseSyntax)node;
                return TryCreateSpanForNode(joinClause.LeftExpression, position);
 
            case SyntaxKind.LetClause:
                var letClause = (LetClauseSyntax)node;
                return TryCreateSpanForNode(letClause.Expression, position);
 
            case SyntaxKind.WhereClause:
                var whereClause = (WhereClauseSyntax)node;
                return TryCreateSpanForNode(whereClause.Condition, position);
 
            case SyntaxKind.OrderByClause:
                var orderByClause = (OrderByClauseSyntax)node;
                return orderByClause.Orderings.Count > 0
                        ? TryCreateSpanForNode(orderByClause.Orderings.First().Expression, position)
                        : null;
 
            case SyntaxKind.SelectClause:
                var selectClause = (SelectClauseSyntax)node;
                return TryCreateSpanForNode(selectClause.Expression, position);
 
            case SyntaxKind.GroupClause:
                var groupClause = (GroupClauseSyntax)node;
                return TryCreateSpanForNode(groupClause.GroupExpression, position);
 
            case SyntaxKind.LocalFunctionStatement:
                var localFunction = (LocalFunctionStatementSyntax)node;
                return (localFunction.Body != null)
                    ? TryCreateSpanForNode(localFunction.Body, position)
                    : TryCreateSpanForNode(localFunction.ExpressionBody!.Expression, position);
 
            default:
                if (node is ExpressionSyntax expression)
                {
                    return IsBreakableExpression(expression) ? CreateSpan(expression) : null;
                }
 
                if (node is StatementSyntax statement)
                {
                    return TryCreateSpanForStatement(statement, position);
                }
 
                return null;
        }
    }
 
    internal static TextSpan? CreateSpanForConstructorDeclaration(ConstructorDeclarationSyntax constructorSyntax, int position)
    {
        if (constructorSyntax.ExpressionBody != null &&
            position > constructorSyntax.ExpressionBody.ArrowToken.Span.Start)
        {
            return constructorSyntax.ExpressionBody.Expression.Span;
        }
 
        if (constructorSyntax.Initializer != null)
        {
            return CreateSpanForExplicitConstructorInitializer(constructorSyntax.Initializer);
        }
 
        // static ctor doesn't have a default initializer:
        if (constructorSyntax.Modifiers.Any(SyntaxKind.StaticKeyword))
        {
            if (constructorSyntax.ExpressionBody != null)
            {
                return constructorSyntax.ExpressionBody.Expression.Span;
            }
 
            if (constructorSyntax.Body != null)
            {
                return CreateSpan(constructorSyntax.Body.OpenBraceToken);
            }
 
            return null;
        }
 
        return CreateSpanForImplicitConstructorInitializer(constructorSyntax);
    }
 
    internal static TextSpan CreateSpanForImplicitConstructorInitializer(ConstructorDeclarationSyntax constructor)
        => CreateSpan(constructor.Modifiers, constructor.Identifier, constructor.ParameterList.CloseParenToken);
 
    internal static IEnumerable<SyntaxToken> GetActiveTokensForImplicitConstructorInitializer(ConstructorDeclarationSyntax constructor)
        => constructor.Modifiers.Concat([constructor.Identifier]).Concat(constructor.ParameterList.DescendantTokens());
 
    internal static TextSpan CreateSpanForExplicitConstructorInitializer(ConstructorInitializerSyntax constructorInitializer)
        => CreateSpan(constructorInitializer.ThisOrBaseKeyword, constructorInitializer.ArgumentList.CloseParenToken);
 
    internal static IEnumerable<SyntaxToken> GetActiveTokensForExplicitConstructorInitializer(ConstructorInitializerSyntax constructorInitializer)
        => [constructorInitializer.ThisOrBaseKeyword, .. constructorInitializer.ArgumentList.DescendantTokens()];
 
    internal static TextSpan CreateSpanForImplicitPrimaryConstructorInitializer(TypeDeclarationSyntax typeDeclaration)
    {
        Debug.Assert(typeDeclaration.ParameterList != null);
        return TextSpan.FromBounds(typeDeclaration.Identifier.SpanStart, typeDeclaration.ParameterList.Span.End);
    }
 
    internal static IEnumerable<SyntaxToken> GetActiveTokensForImplicitPrimaryConstructorInitializer(TypeDeclarationSyntax typeDeclaration)
    {
        Debug.Assert(typeDeclaration.ParameterList != null);
 
        yield return typeDeclaration.Identifier;
 
        if (typeDeclaration.TypeParameterList != null)
        {
            foreach (var token in typeDeclaration.TypeParameterList.DescendantTokens())
                yield return token;
        }
 
        foreach (var token in typeDeclaration.ParameterList.DescendantTokens())
            yield return token;
    }
 
    internal static TextSpan CreateSpanForExplicitPrimaryConstructorInitializer(PrimaryConstructorBaseTypeSyntax baseTypeSyntax)
        => baseTypeSyntax.Span;
 
    internal static IEnumerable<SyntaxToken> GetActiveTokensForExplicitPrimaryConstructorInitializer(PrimaryConstructorBaseTypeSyntax baseTypeSyntax)
        => baseTypeSyntax.DescendantTokens();
 
    internal static TextSpan CreateSpanForCopyConstructor(RecordDeclarationSyntax recordDeclaration)
        => CreateSpan(
            recordDeclaration.Identifier,
            LastNotMissing(recordDeclaration.Identifier, recordDeclaration.TypeParameterList?.GreaterThanToken ?? default));
 
    internal static IEnumerable<SyntaxToken> GetActiveTokensForCopyConstructor(RecordDeclarationSyntax recordDeclaration)
    {
        yield return recordDeclaration.Identifier;
 
        if (recordDeclaration.TypeParameterList != null)
        {
            foreach (var token in recordDeclaration.TypeParameterList.DescendantTokens())
                yield return token;
        }
    }
 
    internal static TextSpan CreateSpanForRecordParameter(ParameterSyntax parameter)
        => CreateSpan(parameter.Modifiers, parameter.Type, parameter.Identifier);
 
    internal static IEnumerable<SyntaxToken> GetActiveTokensForRecordParameter(ParameterSyntax parameter)
    {
        foreach (var modifier in parameter.Modifiers)
            yield return modifier;
 
        if (parameter.Type != null)
        {
            foreach (var token in parameter.Type.DescendantTokens())
                yield return token;
        }
 
        yield return parameter.Identifier;
    }
 
    internal static TextSpan CreateSpanForAutoPropertyAccessor(AccessorDeclarationSyntax accessor)
        => accessor.Span;
 
    internal static IEnumerable<SyntaxToken> GetActiveTokensForAutoPropertyAccessor(AccessorDeclarationSyntax accessor)
        => accessor.DescendantTokens();
 
    private static TextSpan? TryCreateSpanForFieldDeclaration(BaseFieldDeclarationSyntax fieldDeclaration, int position)
        => TryCreateSpanForVariableDeclaration(fieldDeclaration.Declaration, fieldDeclaration.Modifiers, fieldDeclaration.SemicolonToken, position);
 
    private static TextSpan? TryCreateSpanForSwitchLabel(SwitchLabelSyntax switchLabel, int position)
    {
        if (switchLabel.Parent is not SwitchSectionSyntax switchSection || switchSection.Statements.Count == 0)
        {
            return null;
        }
 
        return TryCreateSpanForNode(switchSection.Statements[0], position);
    }
 
    private static TextSpan CreateSpanForBlock(BlockSyntax block, int position)
    {
        // If the user was on the close curly of the block, then set the breakpoint
        // there.  Otherwise, set it on the open curly.
        if (position >= block.OpenBraceToken.FullSpan.End)
        {
            return CreateSpan(block.CloseBraceToken);
        }
        else
        {
            return CreateSpan(block.OpenBraceToken);
        }
    }
 
    private static TextSpan? TryCreateSpanForStatement(StatementSyntax statement, int position)
    {
        if (statement == null)
        {
            return null;
        }
 
        switch (statement.Kind())
        {
            case SyntaxKind.Block:
                return CreateSpanForBlock((BlockSyntax)statement, position);
 
            case SyntaxKind.LocalDeclarationStatement:
                // If the declaration has multiple variables then just set the breakpoint on the first
                // variable declarator.  Otherwise, set the breakpoint over this entire
                // statement.
                var declarationStatement = (LocalDeclarationStatementSyntax)statement;
                return TryCreateSpanForVariableDeclaration(declarationStatement.Declaration, declarationStatement.Modifiers,
                    declarationStatement.SemicolonToken, position);
 
            case SyntaxKind.LabeledStatement:
                // Create the breakpoint on the actual statement we are labeling:
                var labeledStatement = (LabeledStatementSyntax)statement;
                return TryCreateSpanForStatement(labeledStatement.Statement, position);
 
            case SyntaxKind.WhileStatement:
                // Note: if the user was in the body of the while, then we would have hit its
                // nested statement on the way up.  This means we must be on the "while(expr)"
                // part.  Rather than putting a bp on the entire statement, just put it on the
                // top portion.
                var whileStatement = (WhileStatementSyntax)statement;
                return CreateSpan(whileStatement, whileStatement.CloseParenToken);
 
            case SyntaxKind.DoStatement:
                // Note: if the user was in the body of the while, then we would have hit its nested
                // statement on the way up.  This means we're either in the "while(expr)" portion or
                // the "do" portion. 
                var doStatement = (DoStatementSyntax)statement;
                if (position < doStatement.Statement.Span.Start)
                {
                    return TryCreateSpanForStatement(doStatement.Statement, position);
                }
                else
                {
                    return CreateSpan(doStatement.WhileKeyword,
                        LastNotMissing(doStatement.CloseParenToken, doStatement.SemicolonToken));
                }
 
            case SyntaxKind.ForStatement:
                // Note: if the user was in the body of the for, then we would have hit its nested
                // statement on the way up.  If they were in the condition or the incrementors, then
                // we would have those on the way up as well (in TryCreateBreakpointSpanForExpression or
                // CreateBreakpointSpanForVariableDeclarator). So the user must be on the 'for'
                // itself. in that case, set the bp on the variable declaration or initializers
                var forStatement = (ForStatementSyntax)statement;
                if (forStatement.Declaration != null)
                {
                    // for (int i = 0; ...
                    var firstVariable = forStatement.Declaration.Variables.FirstOrDefault();
                    return CreateSpan(default, forStatement.Declaration.Type, firstVariable);
                }
                else if (forStatement.Initializers.Count > 0)
                {
                    // for (i = 0; ...
                    return CreateSpan(forStatement.Initializers[0]);
                }
                else if (forStatement.Condition != null)
                {
                    // for (; i > 0; ...)
                    return CreateSpan(forStatement.Condition);
                }
                else if (forStatement.Incrementors.Count > 0)
                {
                    // for (;;...)
                    return CreateSpan(forStatement.Incrementors[0]);
                }
                else
                {
                    // for (;;)
                    //
                    // In this case, just set the bp on the contained statement.
                    return TryCreateSpanForStatement(forStatement.Statement, position);
                }
 
            case SyntaxKind.ForEachStatement:
            case SyntaxKind.ForEachVariableStatement:
                // Note: if the user was in the body of the foreach, then we would have hit its
                // nested statement on the way up.  If they were in the expression then we would
                // have hit that on the way up as well. In "foreach(var f in expr)" we allow a
                // bp on "foreach", "var f" and "in".
                var forEachStatement = (CommonForEachStatementSyntax)statement;
                if (position < forEachStatement.OpenParenToken.Span.End || position > forEachStatement.CloseParenToken.SpanStart)
                {
                    return CreateSpan(forEachStatement.ForEachKeyword);
                }
                else if (position < forEachStatement.InKeyword.FullSpan.Start)
                {
                    if (forEachStatement.Kind() == SyntaxKind.ForEachStatement)
                    {
                        var simpleForEachStatement = (ForEachStatementSyntax)statement;
                        return CreateSpan(simpleForEachStatement.Type, simpleForEachStatement.Identifier);
                    }
                    else
                    {
                        return ((ForEachVariableStatementSyntax)statement).Variable.Span;
                    }
                }
                else if (position < forEachStatement.Expression.FullSpan.Start)
                {
                    return CreateSpan(forEachStatement.InKeyword);
                }
                else
                {
                    return CreateSpan(forEachStatement.Expression);
                }
 
            case SyntaxKind.UsingStatement:
                var usingStatement = (UsingStatementSyntax)statement;
                if (usingStatement.Declaration != null)
                {
                    return TryCreateSpanForNode(usingStatement.Declaration, position);
                }
                else
                {
                    return CreateSpan(usingStatement, usingStatement.CloseParenToken);
                }
 
            case SyntaxKind.FixedStatement:
                var fixedStatement = (FixedStatementSyntax)statement;
                return TryCreateSpanForNode(fixedStatement.Declaration, position);
 
            case SyntaxKind.CheckedStatement:
            case SyntaxKind.UncheckedStatement:
                var checkedStatement = (CheckedStatementSyntax)statement;
                return TryCreateSpanForStatement(checkedStatement.Block, position);
 
            case SyntaxKind.UnsafeStatement:
                var unsafeStatement = (UnsafeStatementSyntax)statement;
                return TryCreateSpanForStatement(unsafeStatement.Block, position);
 
            case SyntaxKind.LockStatement:
                // Note: if the user was in the body of the 'lock', then we would have hit its
                // nested statement on the way up.  This means we must be on the "lock(expr)" part.
                // Rather than putting a bp on the entire statement, just put it on the top portion.
                var lockStatement = (LockStatementSyntax)statement;
                return CreateSpan(lockStatement, lockStatement.CloseParenToken);
 
            case SyntaxKind.IfStatement:
                // Note: if the user was in the body of the 'if' or the 'else', then we would have
                // hit its nested statement on the way up.  This means we must be on the "if(expr)"
                // part. Rather than putting a bp on the entire statement, just put it on the top
                // portion.
                var ifStatement = (IfStatementSyntax)statement;
                return CreateSpan(ifStatement, ifStatement.CloseParenToken);
 
            case SyntaxKind.SwitchStatement:
                // Note: Any nested statements in the switch will already have been hit on the
                // way up.  Similarly, hitting a 'case' label will already have been taken care
                // of.  So in this case, we just set the bp on the "switch(expr)" itself.
                var switchStatement = (SwitchStatementSyntax)statement;
                return CreateSpan(switchStatement, (switchStatement.CloseParenToken != default) ? switchStatement.CloseParenToken : switchStatement.Expression.GetLastToken());
 
            case SyntaxKind.TryStatement:
                // Note: if the user was in the body of the 'try', then we would have hit its nested
                // statement on the way up.  This means we must be on the "try" part.  In this case,
                // just set the BP on the start of the block.  Note: if they were in a catch or
                // finally section, then that will already have been taken care of above.
                var tryStatement = (TryStatementSyntax)statement;
                return TryCreateSpanForStatement(tryStatement.Block, position);
 
            // All these cases are handled by just putting a breakpoint over the entire
            // statement
            case SyntaxKind.GotoStatement:
            case SyntaxKind.GotoCaseStatement:
            case SyntaxKind.GotoDefaultStatement:
            case SyntaxKind.BreakStatement:
            case SyntaxKind.ContinueStatement:
            case SyntaxKind.ReturnStatement:
            case SyntaxKind.YieldReturnStatement:
            case SyntaxKind.YieldBreakStatement:
            case SyntaxKind.ThrowStatement:
            case SyntaxKind.ExpressionStatement:
            case SyntaxKind.EmptyStatement:
            default:
                // Fallback case.  If it was none of the above types of statements, then we make a span
                // over the entire statement.  Note: this is not a very desirable thing to do (as
                // statements can often span multiple lines.  So, when possible, we should try to do
                // better.
                return CreateSpan(statement);
        }
    }
 
    private static SyntaxToken LastNotMissing(SyntaxToken token1, SyntaxToken token2)
        => token2.IsKind(SyntaxKind.None) || token2.IsMissing ? token1 : token2;
 
    private static TextSpan? TryCreateSpanForVariableDeclaration(VariableDeclarationSyntax declaration, int position)
        => declaration.Parent!.Kind() switch
        {
            // parent node will handle:
            SyntaxKind.LocalDeclarationStatement or SyntaxKind.EventFieldDeclaration or SyntaxKind.FieldDeclaration => null,
 
            _ => TryCreateSpanForVariableDeclaration(declaration, modifiersOpt: default, semicolonOpt: default, position),
        };
 
    private static TextSpan? TryCreateSpanForVariableDeclaration(
        VariableDeclarationSyntax variableDeclaration,
        SyntaxTokenList modifiersOpt,
        SyntaxToken semicolonOpt,
        int position)
    {
        if (variableDeclaration.Variables.Count == 0)
        {
            return null;
        }
 
        if (modifiersOpt.Any(SyntaxKind.ConstKeyword))
        {
            // no sequence points are emitted for const fields/locals
            return default(TextSpan);
        }
 
        if (variableDeclaration.Variables.Count == 1)
        {
            if (variableDeclaration.Variables[0].Initializer == null)
            {
                return default(TextSpan);
            }
 
            return CreateSpan(modifiersOpt, variableDeclaration, semicolonOpt);
        }
 
        if (semicolonOpt != default && position > semicolonOpt.SpanStart)
        {
            position = variableDeclaration.SpanStart;
        }
 
        var variableDeclarator = FindClosestDeclaratorWithInitializer(variableDeclaration.Variables, position);
        if (variableDeclarator == null)
        {
            return default(TextSpan);
        }
 
        if (variableDeclarator == variableDeclaration.Variables[0])
        {
            return CreateSpan(modifiersOpt, variableDeclaration, variableDeclarator);
        }
 
        return CreateSpan(variableDeclarator);
    }
 
    internal static TextSpan CreateSpanForVariableDeclarator(
        VariableDeclaratorSyntax variableDeclarator,
        SyntaxTokenList modifiers,
        SyntaxToken semicolon)
    {
        if (variableDeclarator.Initializer == null || modifiers.Any(SyntaxKind.ConstKeyword))
        {
            return default;
        }
 
        var variableDeclaration = (VariableDeclarationSyntax)variableDeclarator.Parent!;
        if (variableDeclaration.Variables.Count == 1)
        {
            return CreateSpan(modifiers, variableDeclaration, semicolon);
        }
 
        if (variableDeclarator == variableDeclaration.Variables[0])
        {
            return CreateSpan(modifiers, variableDeclaration, variableDeclarator);
        }
 
        return CreateSpan(variableDeclarator);
    }
 
    internal static IEnumerable<SyntaxToken> GetActiveTokensForVariableDeclarator(VariableDeclaratorSyntax variableDeclarator, SyntaxTokenList modifiers, SyntaxToken semicolon)
    {
        if (variableDeclarator.Initializer == null || modifiers.Any(SyntaxKind.ConstKeyword))
            return [];
 
        // [|int F = 1;|]
        var variableDeclaration = (VariableDeclarationSyntax)variableDeclarator.Parent!;
        if (variableDeclaration.Variables.Count == 1)
        {
            return modifiers.Concat(variableDeclaration.DescendantTokens()).Concat(semicolon);
        }
 
        // [|int F = 1|], G = 2;
        if (variableDeclarator == variableDeclaration.Variables[0])
        {
            return modifiers.Concat(variableDeclaration.Type.DescendantTokens()).Concat(variableDeclarator.DescendantTokens());
        }
 
        // int F = 1, [|G = 2|];
        return variableDeclarator.DescendantTokens();
    }
 
    private static VariableDeclaratorSyntax? FindClosestDeclaratorWithInitializer(SeparatedSyntaxList<VariableDeclaratorSyntax> declarators, int position)
    {
        var d = GetItemIndexByPosition(declarators, position);
        var i = 0;
        while (true)
        {
            var left = d - i;
            var right = d + i;
            if (left < 0 && right >= declarators.Count)
            {
                return null;
            }
 
            if (left >= 0 && declarators[left].Initializer != null)
            {
                return declarators[left];
            }
 
            if (right < declarators.Count && declarators[right].Initializer != null)
            {
                return declarators[right];
            }
 
            i += 1;
        }
    }
 
    private static int GetItemIndexByPosition<TNode>(SeparatedSyntaxList<TNode> list, int position)
        where TNode : SyntaxNode
    {
        for (var i = list.SeparatorCount - 1; i >= 0; i--)
        {
            if (position > list.GetSeparator(i).SpanStart)
            {
                return i + 1;
            }
        }
 
        return 0;
    }
 
    private static TextSpan CreateSpanForCatchClause(CatchClauseSyntax catchClause)
    {
        if (catchClause.Filter != null)
        {
            return CreateSpan(catchClause.Filter);
        }
        else if (catchClause.Declaration != null)
        {
            return CreateSpan(catchClause.CatchKeyword, catchClause.Declaration.CloseParenToken);
        }
        else
        {
            return CreateSpan(catchClause.CatchKeyword);
        }
    }
 
    /// <summary>
    /// There are a few places where we allow breakpoints on expressions. 
    ///
    /// 1) When the expression is the body of a lambda/method/operator/property/indexer.
    /// 2) The expression is a breakable expression inside a query expression.
    /// 3) The expression is in a for statement initializer, condition or incrementor.
    /// 4) The expression is a foreach initializer.
    /// 5) The expression is the value of an arm of a switch expression
    /// </summary>
    private static bool IsBreakableExpression(ExpressionSyntax expression)
    {
        if (expression == null || expression.Parent == null)
        {
            return false;
        }
 
        var parent = expression.Parent;
        switch (parent.Kind())
        {
            case SyntaxKind.ArrowExpressionClause:
                Debug.Assert(((ArrowExpressionClauseSyntax)parent).Expression == expression);
                return true;
 
            case SyntaxKind.SwitchExpressionArm:
                Debug.Assert(((SwitchExpressionArmSyntax)parent).Expression == expression);
                return true;
 
            case SyntaxKind.ForStatement:
                var forStatement = (ForStatementSyntax)parent;
                return
                    forStatement.Initializers.Contains(expression) ||
                    forStatement.Condition == expression ||
                    forStatement.Incrementors.Contains(expression);
 
            case SyntaxKind.ForEachStatement:
            case SyntaxKind.ForEachVariableStatement:
                var forEachStatement = (CommonForEachStatementSyntax)parent;
                return forEachStatement.Expression == expression;
 
            default:
                return LambdaUtilities.IsLambdaBodyStatementOrExpression(expression);
        }
    }
 
    private static TextSpan? CreateSpanForAccessors(SyntaxList<AccessorDeclarationSyntax> accessors, int position)
    {
        for (var i = 0; i < accessors.Count; i++)
        {
            if (position <= accessors[i].FullSpan.End || i == accessors.Count - 1)
            {
                return TryCreateSpanForNode(accessors[i], position);
            }
        }
 
        return null;
    }
}