|
// 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;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.LanguageService;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
namespace Microsoft.CodeAnalysis.SplitOrMergeIfStatements;
internal abstract class AbstractMergeIfStatementsCodeRefactoringProvider : CodeRefactoringProvider
{
protected abstract bool IsApplicableSpan(SyntaxNode node, TextSpan span, out SyntaxNode ifOrElseIf);
protected abstract CodeAction CreateCodeAction(
Func<CancellationToken, Task<Document>> createChangedDocument, MergeDirection direction, string ifKeywordText);
protected abstract Task<bool> CanBeMergedUpAsync(
Document document, SyntaxNode ifOrElseIf, CancellationToken cancellationToken, out SyntaxNode upperIfOrElseIf);
protected abstract Task<bool> CanBeMergedDownAsync(
Document document, SyntaxNode ifOrElseIf, CancellationToken cancellationToken, out SyntaxNode lowerIfOrElseIf);
protected abstract SyntaxNode GetChangedRoot(Document document, SyntaxNode root, SyntaxNode upperIfOrElseIf, SyntaxNode lowerIfOrElseIf);
public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
{
var (document, textSpan, cancellationToken) = context;
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var node = root.FindNode(textSpan, getInnermostNodeForTie: true);
if (IsApplicableSpan(node, textSpan, out var ifOrElseIf))
{
var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>();
var syntaxKinds = document.GetLanguageService<ISyntaxKindsService>();
if (await CanBeMergedUpAsync(document, ifOrElseIf, cancellationToken, out var upperIfOrElseIf).ConfigureAwait(false))
RegisterRefactoring(MergeDirection.Up, upperIfOrElseIf.Span, ifOrElseIf.Span);
if (await CanBeMergedDownAsync(document, ifOrElseIf, cancellationToken, out var lowerIfOrElseIf).ConfigureAwait(false))
RegisterRefactoring(MergeDirection.Down, ifOrElseIf.Span, lowerIfOrElseIf.Span);
void RegisterRefactoring(MergeDirection direction, TextSpan upperIfOrElseIfSpan, TextSpan lowerIfOrElseIfSpan)
{
context.RegisterRefactoring(
CreateCodeAction(
c => RefactorAsync(document, upperIfOrElseIfSpan, lowerIfOrElseIfSpan, c),
direction,
syntaxFacts.GetText(syntaxKinds.IfKeyword)),
TextSpan.FromBounds(upperIfOrElseIfSpan.Start, lowerIfOrElseIfSpan.End));
}
}
}
private async Task<Document> RefactorAsync(Document document, TextSpan upperIfOrElseIfSpan, TextSpan lowerIfOrElseIfSpan, CancellationToken cancellationToken)
{
var ifGenerator = document.GetLanguageService<IIfLikeStatementGenerator>();
var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false);
var upperIfOrElseIf = FindIfOrElseIf(upperIfOrElseIfSpan, ifGenerator, root);
var lowerIfOrElseIf = FindIfOrElseIf(lowerIfOrElseIfSpan, ifGenerator, root);
Debug.Assert(ifGenerator.IsIfOrElseIf(upperIfOrElseIf));
Debug.Assert(ifGenerator.IsIfOrElseIf(lowerIfOrElseIf));
var newRoot = GetChangedRoot(document, root, upperIfOrElseIf, lowerIfOrElseIf);
return document.WithSyntaxRoot(newRoot);
static SyntaxNode FindIfOrElseIf(TextSpan span, IIfLikeStatementGenerator ifGenerator, SyntaxNode root)
{
var innerMatch = root.FindNode(span, getInnermostNodeForTie: true);
return innerMatch?.FirstAncestorOrSelf<SyntaxNode>(
node => ifGenerator.IsIfOrElseIf(node) && node.Span == span);
}
}
protected static IReadOnlyList<SyntaxNode> WalkDownScopeBlocks(
IBlockFacts blockFacts,
IReadOnlyList<SyntaxNode> statements)
{
// If our statements only contain a single block, walk down the block and any subsequent nested blocks
// to get the real statements inside.
while (statements.Count == 1 && blockFacts.IsScopeBlock(statements[0]))
statements = blockFacts.GetExecutableBlockStatements(statements[0]);
return statements;
}
protected static IReadOnlyList<SyntaxNode> WalkUpScopeBlocks(
IBlockFactsService blockFacts,
IReadOnlyList<SyntaxNode> statements)
{
// If our statements are inside a block, walk up the block and any subsequent nested blocks that contain
// no other statements to get the topmost block. The last check is necessary to make sure we stop
// walking upwards if there are other statements next to our current block:
// {
// {
// <original statements>
// }
// AnotherStatement();
// }
while (statements is [{ Parent: var parent }, ..] &&
blockFacts.IsScopeBlock(parent) &&
blockFacts.GetExecutableBlockStatements(parent).Count == statements.Count)
{
statements = ImmutableArray.Create(parent);
}
return statements;
}
protected enum MergeDirection { Up, Down }
}
|