private static bool IsElseIfOrElseClauseEquivalent( ISyntaxFactsService syntaxFacts, IBlockFactsService blockFacts, IIfLikeStatementGenerator ifGenerator, SyntaxNode elseIfOrElseClause1, SyntaxNode elseIfOrElseClause2) { // Compare Else/ElseIf clauses for equality. var isIfStatement = ifGenerator.IsIfOrElseIf(elseIfOrElseClause1); if (isIfStatement != ifGenerator.IsIfOrElseIf(elseIfOrElseClause2)) { // If we have one Else and one ElseIf, they're not equal. return(false); } if (isIfStatement) { // If we have two ElseIf blocks, their conditions have to match. var condition1 = ifGenerator.GetCondition(elseIfOrElseClause1); var condition2 = ifGenerator.GetCondition(elseIfOrElseClause2); if (!syntaxFacts.AreEquivalent(condition1, condition2)) { return(false); } } var statements1 = WalkDownScopeBlocks(blockFacts, blockFacts.GetStatementContainerStatements(elseIfOrElseClause1)); var statements2 = WalkDownScopeBlocks(blockFacts, blockFacts.GetStatementContainerStatements(elseIfOrElseClause2)); return(statements1.SequenceEqual(statements2, syntaxFacts.AreEquivalent)); }
private static SyntaxRemoveOptions CreateSyntaxRemoveOptions( TLocalDeclarationStatement localDeclaration, IBlockFactsService blockFacts) { var removeOptions = SyntaxGenerator.DefaultRemoveOptions; if (localDeclaration != null) { if (localDeclaration.GetLeadingTrivia().Contains(t => t.IsDirective)) { removeOptions |= SyntaxRemoveOptions.KeepLeadingTrivia; } else { var statementParent = localDeclaration.Parent; if (blockFacts.IsExecutableBlock(statementParent)) { var siblings = blockFacts.GetExecutableBlockStatements(statementParent); var localDeclarationIndex = siblings.IndexOf(localDeclaration); if (localDeclarationIndex != 0) { // if we're removing the first statement in a block, then we // want to have the elastic marker on it so that the next statement // properly formats with the space left behind. But if it's // not the first statement then just keep the trivia as is // so that the statement before and after it stay appropriately // spaced apart. removeOptions &= ~SyntaxRemoveOptions.AddElasticMarker; } } } } return(removeOptions); }
private static bool IsFirstStatementIfStatement( IBlockFactsService blockFacts, IIfLikeStatementGenerator ifGenerator, SyntaxNode ifOrElseIf, out SyntaxNode ifStatement) { // Check whether the first statement inside an if or else if is an if statement. // If the if statement is inside a block, it has to be the first statement of the block. // An if or else if should always be a statement container, but we'll do a defensive check anyway. Debug.Assert(blockFacts.IsStatementContainer(ifOrElseIf)); if (blockFacts.IsStatementContainer(ifOrElseIf)) { var rootStatements = blockFacts.GetStatementContainerStatements(ifOrElseIf); var statements = WalkDownScopeBlocks(blockFacts, rootStatements); if (statements.Count > 0 && ifGenerator.IsIfOrElseIf(statements[0])) { ifStatement = statements[0]; return(true); } } ifStatement = null; return(false); }
protected static void RemoveNode(SyntaxEditor editor, SyntaxNode node, IBlockFactsService blockFacts) { var localDeclaration = node.GetAncestorOrThis <TLocalDeclarationStatement>(); var removeOptions = CreateSyntaxRemoveOptions(localDeclaration, blockFacts); editor.RemoveNode(node, removeOptions); }
private static bool IsFirstStatementOfIfOrElseIf( IBlockFactsService blockFacts, IIfLikeStatementGenerator ifGenerator, SyntaxNode statement, out SyntaxNode ifOrElseIf) { // Check whether the statement is a first statement inside an if or else if. // If it's inside a block, it has to be the first statement of the block. // We can't assume that a statement will always be in a statement container, because an if statement // in top level code will be in a GlobalStatement. if (blockFacts.IsStatementContainer(statement.Parent)) { var statements = blockFacts.GetStatementContainerStatements(statement.Parent); if (statements.Count > 0 && statements[0] == statement) { var rootStatements = WalkUpScopeBlocks(blockFacts, statements); if (rootStatements.Count > 0 && ifGenerator.IsIfOrElseIf(rootStatements[0].Parent)) { ifOrElseIf = rootStatements[0].Parent; return(true); } } } ifOrElseIf = null; return(false); }
private static bool CanBeMergedWithElseIf( ISyntaxFactsService syntaxFacts, IBlockFactsService blockFacts, IIfLikeStatementGenerator ifGenerator, SyntaxNode ifOrElseIf, out SyntaxNode elseIfClause) { return(ifGenerator.HasElseIfClause(ifOrElseIf, out elseIfClause) && ContainEquivalentStatements(syntaxFacts, blockFacts, ifOrElseIf, elseIfClause, out _)); }
private static bool CanBeMergedWithParent( ISyntaxFactsService syntaxFacts, IBlockFactsService blockFacts, IIfLikeStatementGenerator ifGenerator, SyntaxNode ifOrElseIf, out SyntaxNode parentIfOrElseIf) { return(ifGenerator.IsElseIfClause(ifOrElseIf, out parentIfOrElseIf) && ContainEquivalentStatements(syntaxFacts, blockFacts, ifOrElseIf, parentIfOrElseIf, out _)); }
private static bool ContainEquivalentStatements( ISyntaxFactsService syntaxFacts, IBlockFactsService blockFacts, SyntaxNode ifStatement1, SyntaxNode ifStatement2, out IReadOnlyList <SyntaxNode> statements) { var statements1 = WalkDownScopeBlocks(blockFacts, blockFacts.GetStatementContainerStatements(ifStatement1)); var statements2 = WalkDownScopeBlocks(blockFacts, blockFacts.GetStatementContainerStatements(ifStatement2)); statements = statements1; return(statements1.SequenceEqual(statements2, syntaxFacts.AreEquivalent)); }
private static Task <bool> CanBeMergedWithNextStatementAsync( Document document, ISyntaxFactsService syntaxFacts, IBlockFactsService blockFacts, IIfLikeStatementGenerator ifGenerator, SyntaxNode ifOrElseIf, CancellationToken cancellationToken, out SyntaxNode nextStatement) { return(TryGetSiblingStatement(syntaxFacts, blockFacts, ifOrElseIf, relativeIndex: 1, out nextStatement) ? CanStatementsBeMergedAsync(document, syntaxFacts, blockFacts, ifGenerator, ifOrElseIf, nextStatement, cancellationToken) : SpecializedTasks.False); }
protected override bool ShouldOfferFixForLocalDeclaration(IBlockFactsService blockFacts, SyntaxNode node) { // If the fix location is not for a local declaration then we can allow it (eg, when inside a for // or catch). if (node.Parent?.Parent is not LocalDeclarationStatementSyntax localDeclaration) { return(true); } // Local declarations must be parented by an executable block, or global statement, otherwise // removing them would be invalid (and more than likely crash the fixer) return(localDeclaration.Parent is GlobalStatementSyntax || blockFacts.IsExecutableBlock(localDeclaration.Parent)); }
private static async Task <bool> CanStatementsBeMergedAsync( Document document, ISyntaxFactsService syntaxFacts, IBlockFactsService blockFacts, IIfLikeStatementGenerator ifGenerator, SyntaxNode firstStatement, SyntaxNode secondStatement, CancellationToken cancellationToken) { // We don't support cases where the previous if statement has any else-if or else clauses. In order for that // to be mergable, the control flow would have to quit from inside every branch, which is getting a little complex. if (!ifGenerator.IsIfOrElseIf(firstStatement) || ifGenerator.GetElseIfAndElseClauses(firstStatement).Length > 0) { return(false); } if (!ifGenerator.IsIfOrElseIf(secondStatement)) { return(false); } if (!ContainEquivalentStatements(syntaxFacts, blockFacts, firstStatement, secondStatement, out var insideStatements)) { return(false); } if (insideStatements.Count == 0) { // Even though there are no statements inside, we still can't merge these into one statement // because it would change the semantics from always evaluating the second condition to short-circuiting. return(false); } else { // There are statements inside. We can merge these into one statement if // control flow can't reach the end of these statements (otherwise, it would change from running // the second 'if' in the case that both conditions are true to only running the statements once). // 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); } }
private static bool TryGetSiblingStatement( ISyntaxFactsService syntaxFacts, IBlockFactsService blockFacts, SyntaxNode ifOrElseIf, int relativeIndex, out SyntaxNode statement) { if (syntaxFacts.IsExecutableStatement(ifOrElseIf) && blockFacts.IsExecutableBlock(ifOrElseIf.Parent)) { var blockStatements = blockFacts.GetExecutableBlockStatements(ifOrElseIf.Parent); statement = blockStatements.ElementAtOrDefault(blockStatements.IndexOf(ifOrElseIf) + relativeIndex); return(statement != null); } statement = null; return(false); }
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); } }
protected override void RemoveOrReplaceNode(SyntaxEditor editor, SyntaxNode node, IBlockFactsService blockFacts) { switch (node.Kind()) { case SyntaxKind.SimpleAssignmentExpression: editor.ReplaceNode(node, ((AssignmentExpressionSyntax)node).Right); return; default: RemoveNode(editor, node.IsParentKind(SyntaxKind.GlobalStatement) ? node.Parent : node, blockFacts); return; } }
protected abstract void RemoveOrReplaceNode(SyntaxEditor editor, SyntaxNode node, IBlockFactsService blockFacts);
private static async Task <bool> CanBeMergedAsync( Document document, ISyntaxFactsService syntaxFacts, IBlockFactsService blockFacts, IIfLikeStatementGenerator ifGenerator, SyntaxNode outerIfOrElseIf, SyntaxNode innerIfStatement, CancellationToken cancellationToken) { // We can only merge this with the outer if statement if any inner else-if and else clauses are equal // to else-if and else clauses following the outer if statement because we'll be removing the inner ones. // Example of what we can merge: // if (a) // { // if (b) // Console.WriteLine(); // else // Foo(); // } // else // { // Foo(); // } if (!System.Linq.ImmutableArrayExtensions.SequenceEqual( ifGenerator.GetElseIfAndElseClauses(outerIfOrElseIf), ifGenerator.GetElseIfAndElseClauses(innerIfStatement), (a, b) => IsElseIfOrElseClauseEquivalent(syntaxFacts, blockFacts, ifGenerator, a, b))) { return(false); } var statements = blockFacts.GetStatementContainerStatements(innerIfStatement.Parent); if (statements.Count == 1) { // There are no other statements below the inner if statement. Merging is OK. return(true); } else { // There are statements below the inner if statement. We can merge if // 1. there are equivalent statements below the outer 'if', and // 2. control flow can't reach the end of these statements (otherwise, it would continue // below the outer 'if' and run the same statements twice). // This will typically look like a single return, break, continue or a throw statement. // The opposite refactoring (SplitIntoNestedIfStatements) never generates this but we support it anyway. // Example: // if (a) // { // if (b) // Console.WriteLine(); // return; // } // return; // If we have an else-if, get the topmost if statement. var outerIfStatement = ifGenerator.GetRootIfStatement(outerIfOrElseIf); // A statement should always be in a statement container, but we'll do a defensive check anyway so that // we don't crash if the helper is missing some cases or there's a new language feature it didn't account for. Debug.Assert(blockFacts.GetStatementContainer(outerIfStatement) is object); if (blockFacts.GetStatementContainer(outerIfStatement) is not { } container) { return(false); } var outerStatements = blockFacts.GetStatementContainerStatements(container); var outerIfStatementIndex = outerStatements.IndexOf(outerIfStatement); var remainingStatements = statements.Skip(1); var remainingOuterStatements = outerStatements.Skip(outerIfStatementIndex + 1); if (!remainingStatements.SequenceEqual(remainingOuterStatements.Take(statements.Count - 1), syntaxFacts.AreEquivalent)) { return(false); } var semanticModel = await document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); var controlFlow = semanticModel.AnalyzeControlFlow(statements[0], statements[statements.Count - 1]); return(!controlFlow.EndPointIsReachable); } }
protected abstract bool ShouldOfferFixForLocalDeclaration(IBlockFactsService blockFacts, SyntaxNode node);