legacy/swift/Sources/PiranhaKit/CleanupStaleFlags/IfStmtRewriter.swift (96 lines of code) (raw):
/**
* Copyright (c) 2021 Uber Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import Foundation
import SwiftSyntax
/// This SyntaxRewriter dedicatedly operates on the if-else condition and also works on the if-else ladder.
/// It does the following:
/// a.) to remove the conditional block that has the false condition because that will never be executed.
/// b.) If there is a conditional block evaluating to true, then it removes all the following conditions as well as their respective bodies because they will never be executed.
final class IfElseStmtRewriter: SyntaxRewriter {
override func visit(_ node: IfStmtSyntax) -> StmtSyntax {
return super.visit(reduce(node))
}
// MARK: Private
private func reduce(_ node: IfStmtSyntax) -> IfStmtSyntax {
var updatedNode = reduceFalseTreeIfApplicable(node)
updatedNode = reduceTrueTreeIfApplicable(updatedNode)
return updatedNode
}
private func reduceFalseTreeIfApplicable(_ node: IfStmtSyntax) -> IfStmtSyntax {
guard node.conditions.count == 1,
let firstCondition = node.conditions.first,
firstCondition.description.trimmingCharacters(in: .whitespacesAndNewlines) == "false",
let booleanLiteralExpr = BooleanLiteralExprSyntax(firstCondition.condition) else {
/// Since this doesn't meet the false cleanup criteria, therefore returning.
return node
}
if case .identifier = booleanLiteralExpr.booleanLiteral.tokenKind {
return reduceFalseTree(node)
} else {
// TokenKind.falseKeyword and others are not considerd by this reducer since we want to limit the scope to Piranha related reduced expressions.
return node
}
}
private func reduceFalseTree(_ node: IfStmtSyntax) -> IfStmtSyntax {
/// Processing else block if present.
if let _ = node.elseKeyword,
let elseBody = node.elseBody {
// Is it an only else condition and there is no if condition.
if let expr = CodeBlockSyntax.init(elseBody) {
var ifStmt = SyntaxFactory.makeBlankIfStmt()
ifStmt = ifStmt.withBody(codeBlockFor(node: node,
referenceCodeBlock: expr))
return ifStmt
}
// If condition is present.
if let ifOfBody = IfStmtSyntax.init(elseBody) {
var ifStmt = SyntaxFactory.makeBlankIfStmt()
ifStmt = reduce(ifOfBody)
return ifStmt
}
return node
} else {
// False conditional and no else body, hence returning a blank statement.
return SyntaxFactory.makeBlankIfStmt()
}
}
/// This update the code block with the appropriate leading and trailing trivia.
private func codeBlockFor(node: IfStmtSyntax,
referenceCodeBlock codeBlock: CodeBlockSyntax,
previousNode: IfStmtSyntax? = nil) -> CodeBlockSyntax {
if node.ifKeyword.previousToken?.tokenKind == .elseKeyword {
/// Since there is an existing else block, we would want to have braces and their respective trivia.
let leading = SyntaxFactory.makeToken(.leftBrace,
presence: .present,
leadingTrivia: Trivia.init(pieces: []),
trailingTrivia: Trivia.init(pieces: []))
let trailing = SyntaxFactory.makeToken(.rightBrace,
presence: .present,
leadingTrivia: codeBlock.rightBrace.leadingTrivia,
trailingTrivia: codeBlock.rightBrace.trailingTrivia)
return SyntaxFactory.makeCodeBlock(leftBrace: leading,
statements: codeBlock.statements,
rightBrace: trailing)
} else {
/// Matching the trivia to the if keyword.
var statements = codeBlock.statements
if let firstModified = statements.first?.withLeadingTrivia(node.ifKeyword.leadingTrivia) {
statements = statements.replacing(childAt: 0, with: firstModified)
}
let leading = SyntaxFactory.makeToken(.identifier(""),
presence: .present,
leadingTrivia: Trivia.init(pieces: []),
trailingTrivia: Trivia.init(pieces: []))
let trailing = SyntaxFactory.makeToken(.identifier(""),
presence: .present,
leadingTrivia: Trivia.init(pieces: []),
trailingTrivia: codeBlock.rightBrace.trailingTrivia)
return SyntaxFactory.makeCodeBlock(leftBrace: leading,
statements: statements,
rightBrace: trailing)
}
}
// If the condition is true, no other if else condition(s) below this node will be executed hence the following blocks will be cleaned-up.
private func reduceTrueTreeIfApplicable(_ node: IfStmtSyntax) -> IfStmtSyntax {
guard node.conditions.count == 1,
let firstCondition = node.conditions.first,
node.conditions.first?.description.trimmingCharacters(in: .whitespacesAndNewlines) == "true",
let booleanLiteralExpr = BooleanLiteralExprSyntax(firstCondition.condition) else {
/// Since this doesn't meet the true cleanup criteria, therefore returning.
return node
}
if case .identifier = booleanLiteralExpr.booleanLiteral.tokenKind {
return reduceTrueTree(node)
} else {
// TokenKind.trueKeyword and others are not considerd by this reducer since we want to limit the scope to Piranha related reduced expressions.
return node
}
}
private func reduceTrueTree(_ node: IfStmtSyntax) -> IfStmtSyntax {
let statement = SyntaxFactory.makeBlankIfStmt()
.withBody(codeBlockFor(node: node,
referenceCodeBlock: node.body))
return statement
}
}