|
// 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;
}
}
}
|