| File: SplitOrMergeIfStatements\AbstractSplitIfStatementCodeRefactoringProvider.cs | Web Access |
| Project: src\roslyn\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.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; 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 AbstractSplitIfStatementCodeRefactoringProvider : CodeRefactoringProvider { protected abstract int GetLogicalExpressionKind(ISyntaxKindsService syntaxKinds); protected abstract CodeAction CreateCodeAction(Func<CancellationToken, Task<Document>> createChangedDocument, string ifKeywordText); protected abstract Task<SyntaxNode> GetChangedRootAsync( Document document, SyntaxNode root, SyntaxNode ifOrElseIf, SyntaxNode leftCondition, SyntaxNode rightCondition, CancellationToken cancellationToken); public sealed override async Task ComputeRefactoringsAsync(CodeRefactoringContext context) { var (document, textSpan, cancellationToken) = context; var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var token = root.FindToken(textSpan.Start); if (textSpan.Length > 0 && textSpan != token.Span) { return; } var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>(); var syntaxKinds = document.GetLanguageService<ISyntaxKindsService>(); var ifGenerator = document.GetLanguageService<IIfLikeStatementGenerator>(); if (IsPartOfBinaryExpressionChain(token, GetLogicalExpressionKind(syntaxKinds), out var rootExpression) && ifGenerator.IsCondition(rootExpression, out var ifOrElseIf)) { context.RegisterRefactoring( CreateCodeAction( c => RefactorAsync(document, token.Span, ifOrElseIf.Span, c), syntaxFacts.GetText(syntaxKinds.IfKeyword)), ifOrElseIf.Span); } } private async Task<Document> RefactorAsync(Document document, TextSpan tokenSpan, TextSpan ifOrElseIfSpan, CancellationToken cancellationToken) { var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>(); var ifGenerator = document.GetLanguageService<IIfLikeStatementGenerator>(); var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); var token = root.FindToken(tokenSpan.Start); var ifOrElseIf = FindIfOrElseIf(ifOrElseIfSpan); var (left, right) = SplitBinaryExpressionChain(token, ifGenerator.GetCondition(ifOrElseIf), syntaxFacts); var newRoot = await GetChangedRootAsync(document, root, ifOrElseIf, left, right, cancellationToken).ConfigureAwait(false); return document.WithSyntaxRoot(newRoot); SyntaxNode FindIfOrElseIf(TextSpan span) { var innerMatch = root.FindNode(span, getInnermostNodeForTie: true); return innerMatch?.FirstAncestorOrSelf<SyntaxNode>( node => ifGenerator.IsIfOrElseIf(node) && node.Span == span); } } private static bool IsPartOfBinaryExpressionChain(SyntaxToken token, int syntaxKind, out SyntaxNode rootExpression) { // Check whether the token is part of a binary expression, and if so, // return the topmost binary expression in the chain (e.g. `a && b && c`). SyntaxNodeOrToken current = token; while (current.Parent?.RawKind == syntaxKind) { current = current.Parent; } rootExpression = current.AsNode(); return current.IsNode; } private static (SyntaxNode left, SyntaxNode right) SplitBinaryExpressionChain( SyntaxToken token, SyntaxNode rootExpression, ISyntaxFactsService syntaxFacts) { // We have a left-associative binary expression chain, e.g. `a && b && c && d`. // Let's say our token is the second `&&` token, between b and c. We'd like to split the chain at this point // and build new expressions for the left side and the right side of this token. This will // effectively change the associativity from `((a && b) && c) && d` to `(a && b) && (c && d)`. // The left side is in the proper shape already and we can build the right side by getting the // topmost expression and replacing our parent with our right side. In the example: `(a && b) && c` to `c`. syntaxFacts.GetPartsOfBinaryExpression(token.Parent, out var parentLeft, out _, out var parentRight); var left = parentLeft; var right = rootExpression.ReplaceNode(token.Parent, parentRight); return (left, right); } }