File: CodeRefactorings\NodeSelectionHelpers.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.
 
#nullable disable
 
using System.Collections.Immutable;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.CSharp.LanguageService;
using Microsoft.CodeAnalysis.CSharp.Syntax;
 
namespace Microsoft.CodeAnalysis.CSharp.CodeRefactorings;
 
internal static class NodeSelectionHelpers
{
    internal static async Task<ImmutableArray<SyntaxNode>> GetSelectedDeclarationsOrVariablesAsync(CodeRefactoringContext context)
    {
        var (document, span, cancellationToken) = context;
        if (span.IsEmpty)
        {
            // if the span is empty then we are only selecting one "member" (which could include a field which declared multiple actual members)
            // Consider:
            // MemberDeclaration: member that can be declared in type (those are the ones we can pull up) 
            // VariableDeclaratorSyntax: for fields the MemberDeclaration can actually represent multiple declarations, e.g. `int a = 0, b = 1;`.
            // ..Since the user might want to select & pull up only one of them (e.g. `int a = 0, [|b = 1|];` we also look for closest VariableDeclaratorSyntax.
            var memberDeclaration = await context.TryGetRelevantNodeAsync<MemberDeclarationSyntax>().ConfigureAwait(false);
            if (memberDeclaration == null)
            {
                // could not find a member, we may be directly on a variable declaration
                var varDeclarator = await context.TryGetRelevantNodeAsync<VariableDeclaratorSyntax>().ConfigureAwait(false);
                return varDeclarator == null
                    ? []
                    : [varDeclarator];
            }
            else
            {
                return memberDeclaration switch
                {
                    FieldDeclarationSyntax fieldDeclaration => fieldDeclaration.Declaration.Variables.AsImmutable<SyntaxNode>(),
                    EventFieldDeclarationSyntax eventFieldDeclaration => eventFieldDeclaration.Declaration.Variables.AsImmutable<SyntaxNode>(),
                    IncompleteMemberSyntax or GlobalStatementSyntax => [],
                    _ => [memberDeclaration],
                };
            }
        }
        else
        {
            // if the span is non-empty, then we get potentially multiple members
            // Note: even though this method handles the empty span case, we don't use it because it doesn't correctly
            // pick up on keywords before the declaration, such as "public static int".
            // We could potentially use it for every case if that behavior changes
            var tree = await document.GetSyntaxTreeAsync(cancellationToken).ConfigureAwait(false);
            var members = await CSharpSelectedMembers.Instance.GetSelectedMembersAsync(tree, span, allowPartialSelection: true, cancellationToken).ConfigureAwait(false);
            // if we get a node that would not have an obtainable symbol (such as the ones below)
            // we return an empty list instead of filtering so we don't get other potentially
            // malformed syntax nodes.
            // Consider pub[||] static int Foo;
            // Which has 2 member nodes (an incomplete and a field), but we'd only expect one
            return members.Any(m => m is GlobalStatementSyntax or IncompleteMemberSyntax)
                ? []
                : members;
        }
    }
}