|
// 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.Immutable;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeRefactorings.PullMemberUp.Dialog;
using Microsoft.CodeAnalysis.PullMemberUp;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;
using static Microsoft.CodeAnalysis.CodeActions.CodeAction;
namespace Microsoft.CodeAnalysis.CodeRefactorings.PullMemberUp
{
internal abstract partial class AbstractPullMemberUpRefactoringProvider : CodeRefactoringProvider
{
private IPullMemberUpOptionsService? _service;
protected abstract Task<ImmutableArray<SyntaxNode>> GetSelectedNodesAsync(CodeRefactoringContext context);
/// <summary>
/// Test purpose only
/// </summary>
protected AbstractPullMemberUpRefactoringProvider(IPullMemberUpOptionsService? service)
=> _service = service;
public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
{
// Currently support to pull field, method, event, property and indexer up,
// constructor, operator and finalizer are excluded.
var (document, _, cancellationToken) = context;
_service ??= document.Project.Solution.Services.GetService<IPullMemberUpOptionsService>();
if (_service == null)
{
return;
}
var selectedMemberNodes = await GetSelectedNodesAsync(context).ConfigureAwait(false);
if (selectedMemberNodes.IsEmpty)
{
return;
}
var semanticModel = await document.GetRequiredSemanticModelAsync(cancellationToken).ConfigureAwait(false);
var memberNodeSymbolPairs = selectedMemberNodes
.SelectAsArray(m => (node: m, symbol: semanticModel.GetRequiredDeclaredSymbol(m, cancellationToken)))
.WhereAsArray(pair => MemberAndDestinationValidator.IsMemberValid(pair.symbol));
if (memberNodeSymbolPairs.IsEmpty)
{
return;
}
var selectedMembers = memberNodeSymbolPairs.SelectAsArray(pair => pair.symbol);
var containingType = selectedMembers.First().ContainingType;
Contract.ThrowIfNull(containingType);
if (selectedMembers.Any(m => !m.ContainingType.Equals(containingType)))
{
return;
}
// we want to use a span which covers all the selected viable member nodes, so that more specific nodes have priority
var memberSpan = TextSpan.FromBounds(
memberNodeSymbolPairs.First().node.FullSpan.Start,
memberNodeSymbolPairs.Last().node.FullSpan.End);
var allDestinations = FindAllValidDestinations(
selectedMembers,
containingType,
document.Project.Solution,
cancellationToken);
if (allDestinations.Length == 0)
{
return;
}
var allActions = allDestinations.Select(destination => MembersPuller.TryComputeCodeAction(document, selectedMembers, destination, context.Options))
.WhereNotNull()
.Concat(new PullMemberUpWithDialogCodeAction(document, selectedMembers, _service, context.Options))
.ToImmutableArray();
var title = selectedMembers.IsSingle()
? string.Format(FeaturesResources.Pull_0_up, selectedMembers.Single().ToNameDisplayString())
: FeaturesResources.Pull_selected_members_up;
var nestedCodeAction = CodeAction.Create(
title,
allActions, isInlinable: true);
context.RegisterRefactoring(nestedCodeAction, memberSpan);
}
private static ImmutableArray<INamedTypeSymbol> FindAllValidDestinations(
ImmutableArray<ISymbol> selectedMembers,
INamedTypeSymbol containingType,
Solution solution,
CancellationToken cancellationToken)
{
var allDestinations = selectedMembers.All(m => m.IsKind(SymbolKind.Field))
? containingType.GetBaseTypes().ToImmutableArray()
: containingType.AllInterfaces.Concat(containingType.GetBaseTypes()).ToImmutableArray();
return allDestinations.WhereAsArray(destination => MemberAndDestinationValidator.IsDestinationValid(solution, destination, cancellationToken));
}
}
}
|