| File: SplitOrMergeIfStatements\Consecutive\AbstractSplitIntoConsecutiveIfStatementsCodeRefactoringProvider.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.Diagnostics; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.CodeActions; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Formatting; using Microsoft.CodeAnalysis.LanguageService; using Microsoft.CodeAnalysis.Shared.Extensions; namespace Microsoft.CodeAnalysis.SplitOrMergeIfStatements; internal abstract class AbstractSplitIntoConsecutiveIfStatementsCodeRefactoringProvider : AbstractSplitIfStatementCodeRefactoringProvider { // Converts: // if (a || b) // Console.WriteLine(); // // To: // if (a) // Console.WriteLine(); // else if (b) // Console.WriteLine(); // Converts: // if (a || b) // return; // // To: // if (a) // return; // if (b) // return; // The second case is applied if control flow quits from inside the body. protected sealed override int GetLogicalExpressionKind(ISyntaxKindsService syntaxKinds) => syntaxKinds.LogicalOrExpression; protected sealed override CodeAction CreateCodeAction(Func<CancellationToken, Task<Document>> createChangedDocument, string ifKeywordText) => CodeAction.Create( string.Format(FeaturesResources.Split_into_consecutive_0_statements, ifKeywordText), createChangedDocument, nameof(FeaturesResources.Split_into_consecutive_0_statements) + "_" + ifKeywordText); protected sealed override async Task<SyntaxNode> GetChangedRootAsync( Document document, SyntaxNode root, SyntaxNode ifOrElseIf, SyntaxNode leftCondition, SyntaxNode rightCondition, CancellationToken cancellationToken) { var syntaxFacts = document.GetLanguageService<ISyntaxFactsService>(); var blockFacts = document.GetLanguageService<IBlockFactsService>(); var ifGenerator = document.GetLanguageService<IIfLikeStatementGenerator>(); var generator = document.GetLanguageService<SyntaxGenerator>(); leftCondition = leftCondition.WithAdditionalAnnotations(Formatter.Annotation); rightCondition = rightCondition.WithAdditionalAnnotations(Formatter.Annotation); var editor = new SyntaxEditor(root, generator); editor.ReplaceNode(ifOrElseIf, (currentNode, _) => ifGenerator.WithCondition(currentNode, leftCondition)); if (await CanBeSeparateStatementsAsync(document, blockFacts, ifGenerator, ifOrElseIf, cancellationToken).ConfigureAwait(false)) { // Generate: // if (a) // return; // if (b) // return; // At this point, ifLikeStatement must be a standalone if statement with no else clause. Debug.Assert(syntaxFacts.IsExecutableStatement(ifOrElseIf)); Debug.Assert(ifGenerator.GetElseIfAndElseClauses(ifOrElseIf).Length == 0); var secondIfStatement = ifGenerator.WithCondition(ifOrElseIf, rightCondition) .WithPrependedLeadingTrivia(generator.ElasticCarriageReturnLineFeed); if (!blockFacts.IsExecutableBlock(ifOrElseIf.Parent)) { // In order to insert a new statement, we have to be inside a block. editor.ReplaceNode(ifOrElseIf, (currentNode, _) => generator.ScopeBlock([currentNode])); } editor.InsertAfter(ifOrElseIf, secondIfStatement); } else { // Generate: // if (a) // Console.WriteLine(); // else if (b) // Console.WriteLine(); // If the if statement is not an else-if clause, we convert it to an else-if clause first (for VB). // Then we insert it right after our current if statement or else-if clause. var elseIfClause = ifGenerator.WithCondition(ifGenerator.ToElseIfClause(ifOrElseIf), rightCondition); ifGenerator.InsertElseIfClause(editor, ifOrElseIf, elseIfClause); } return editor.GetChangedRoot(); } private static async Task<bool> CanBeSeparateStatementsAsync( Document document, IBlockFactsService blockFacts, IIfLikeStatementGenerator ifGenerator, SyntaxNode ifOrElseIf, CancellationToken cancellationToken) { // In order to make separate statements, ifOrElseIf must be an if statement, not an else-if clause. if (ifGenerator.IsElseIfClause(ifOrElseIf, out _)) { return false; } // If there is an else clause, we *could* in theory separate these and move the current else clause to the second // statement, but we won't. It would break the else-if chain in an odd way. We'll insert an else-if instead. if (ifGenerator.GetElseIfAndElseClauses(ifOrElseIf).Length > 0) { return false; } var insideStatements = blockFacts.GetStatementContainerStatements(ifOrElseIf); if (insideStatements.Count == 0) { // Even though there are no statements inside, we still can't split this into separate statements // because it would change the semantics from short-circuiting to always evaluating the second condition, // breaking code like 'if (a == null || a.InstanceMethod())'. return false; } else { // There are statements inside. We can split this into separate statements and leave out the 'else' if // control flow can't reach the end of these statements (otherwise, it would continue to the second 'if' // and in the case that both conditions are true, run the same statements twice). // This will typically look like a single return, break, continue or a throw statement. var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); var controlFlow = semanticModel.AnalyzeControlFlow(insideStatements[0], insideStatements[insideStatements.Count - 1]); return !controlFlow.EndPointIsReachable; } } }