Skip to content

Commit 57454eb

Browse files
willtellisnkcsgexi
authored andcommitted
SR-5739 Add refactoring action to collapse nested if (#11851)
1 parent 8bf538c commit 57454eb

File tree

9 files changed

+268
-0
lines changed

9 files changed

+268
-0
lines changed

include/swift/IDE/RefactoringKinds.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ CURSOR_REFACTORING(LocalizeString, "Localize String", localize.string)
3838

3939
CURSOR_REFACTORING(SimplifyNumberLiteral, "Simplify Long Number Literal", simplify.long.number.literal)
4040

41+
CURSOR_REFACTORING(CollapseNestedIfExpr, "Collapse Nested If Expression", collapse.nested.if.expr)
42+
4143
RANGE_REFACTORING(ExtractExpr, "Extract Expression", extract.expr)
4244

4345
RANGE_REFACTORING(ExtractFunction, "Extract Method", extract.function)

lib/IDE/Refactoring.cpp

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1448,6 +1448,113 @@ bool RefactoringActionExtractRepeatedExpr::performChange() {
14481448
EditConsumer).performChange();
14491449
}
14501450

1451+
struct CollapsibleNestedIfInfo {
1452+
IfStmt *OuterIf;
1453+
IfStmt *InnerIf;
1454+
bool FinishedOuterIf;
1455+
bool FoundNonCollapsibleItem;
1456+
CollapsibleNestedIfInfo():
1457+
OuterIf(nullptr), InnerIf(nullptr),
1458+
FinishedOuterIf(false), FoundNonCollapsibleItem(false) {}
1459+
bool isValid() {
1460+
return OuterIf && InnerIf && FinishedOuterIf && !FoundNonCollapsibleItem;
1461+
}
1462+
};
1463+
1464+
static CollapsibleNestedIfInfo findCollapseNestedIfTarget(ResolvedCursorInfo CursorInfo) {
1465+
if (CursorInfo.Kind != CursorInfoKind::StmtStart)
1466+
return CollapsibleNestedIfInfo();
1467+
struct IfStmtFinder: public SourceEntityWalker {
1468+
SourceLoc StartLoc;
1469+
CollapsibleNestedIfInfo IfInfo;
1470+
IfStmtFinder(SourceLoc StartLoc): StartLoc(StartLoc), IfInfo() {}
1471+
bool finishedInnerIfButNotFinishedOuterIf() {
1472+
return IfInfo.InnerIf && !IfInfo.FinishedOuterIf;
1473+
}
1474+
bool walkToStmtPre(Stmt *S) {
1475+
if (finishedInnerIfButNotFinishedOuterIf()) {
1476+
IfInfo.FoundNonCollapsibleItem = true;
1477+
return false;
1478+
}
1479+
1480+
bool StmtIsOuterIfBrace =
1481+
IfInfo.OuterIf && !IfInfo.InnerIf && S->getKind() == StmtKind::Brace;
1482+
if (StmtIsOuterIfBrace) {
1483+
return true;
1484+
}
1485+
1486+
auto *IFS = dyn_cast<IfStmt>(S);
1487+
if (!IFS) {
1488+
return false;
1489+
}
1490+
if (!IfInfo.OuterIf) {
1491+
IfInfo.OuterIf = IFS;
1492+
return true;
1493+
} else {
1494+
IfInfo.InnerIf = IFS;
1495+
return false;
1496+
}
1497+
}
1498+
bool walkToStmtPost(Stmt *S) {
1499+
assert(S != IfInfo.InnerIf && "Should not traverse inner if statement");
1500+
if (S == IfInfo.OuterIf) {
1501+
IfInfo.FinishedOuterIf = true;
1502+
}
1503+
return true;
1504+
}
1505+
bool walkToDeclPre(Decl *D, CharSourceRange Range) {
1506+
if (finishedInnerIfButNotFinishedOuterIf()) {
1507+
IfInfo.FoundNonCollapsibleItem = true;
1508+
return false;
1509+
}
1510+
return true;
1511+
}
1512+
bool walkToExprPre(Expr *E) {
1513+
if (finishedInnerIfButNotFinishedOuterIf()) {
1514+
IfInfo.FoundNonCollapsibleItem = true;
1515+
return false;
1516+
}
1517+
return true;
1518+
}
1519+
1520+
} Walker(CursorInfo.TrailingStmt->getStartLoc());
1521+
Walker.walk(CursorInfo.TrailingStmt);
1522+
return Walker.IfInfo;
1523+
}
1524+
1525+
bool RefactoringActionCollapseNestedIfExpr::isApplicable(ResolvedCursorInfo Tok) {
1526+
return findCollapseNestedIfTarget(Tok).isValid();
1527+
}
1528+
1529+
bool RefactoringActionCollapseNestedIfExpr::performChange() {
1530+
auto Target = findCollapseNestedIfTarget(CursorInfo);
1531+
if (!Target.isValid())
1532+
return true;
1533+
auto OuterIfConds = Target.OuterIf->getCond().vec();
1534+
auto InnerIfConds = Target.InnerIf->getCond().vec();
1535+
1536+
llvm::SmallString<64> DeclBuffer;
1537+
llvm::raw_svector_ostream OS(DeclBuffer);
1538+
OS << tok::kw_if << " ";
1539+
for (auto CI = OuterIfConds.begin(); CI != OuterIfConds.end(); ++CI) {
1540+
OS << (CI != OuterIfConds.begin() ? ", " : "");
1541+
OS << Lexer::getCharSourceRangeFromSourceRange(
1542+
SM, CI->getSourceRange()).str();
1543+
}
1544+
for (auto CI = InnerIfConds.begin(); CI != InnerIfConds.end(); ++CI) {
1545+
OS << ", " << Lexer::getCharSourceRangeFromSourceRange(
1546+
SM, CI->getSourceRange()).str();
1547+
}
1548+
auto ThenStatementText = Lexer::getCharSourceRangeFromSourceRange(
1549+
SM, Target.InnerIf->getThenStmt()->getSourceRange()).str();
1550+
OS << " " << ThenStatementText;
1551+
1552+
auto SourceRange = Lexer::getCharSourceRangeFromSourceRange(
1553+
SM, Target.OuterIf->getSourceRange());
1554+
EditConsumer.accept(SM, SourceRange, DeclBuffer.str());
1555+
return false;
1556+
}
1557+
14511558
/// The helper class analyzes a given nominal decl or an extension decl to
14521559
/// decide whether stubs are required to filled in and the context in which
14531560
/// these stubs should be filled.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
func testCollapseNestedIf() {
2+
let a = 3
3+
if a > 2 {
4+
if a < 10 {}
5+
}
6+
}
7+
func testMultiConditionalNestedIf() {
8+
let a = 3
9+
if a > 2, a != 4 {
10+
if a < 10, a != 5 {}
11+
}
12+
}
13+
func testLetNestedIf() {
14+
let a: Int? = 3
15+
if let b = a, b != 5 {}
16+
}
17+
func testCaseLetNestedIf() {
18+
let a: Int? = 3
19+
if case .some(let b) = a {
20+
if b != 5 {}
21+
}
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
func testCollapseNestedIf() {
2+
let a = 3
3+
if a > 2 {
4+
if a < 10 {}
5+
}
6+
}
7+
func testMultiConditionalNestedIf() {
8+
let a = 3
9+
if a > 2, a != 4 {
10+
if a < 10, a != 5 {}
11+
}
12+
}
13+
func testLetNestedIf() {
14+
let a: Int? = 3
15+
if let b = a {
16+
if b != 5 {}
17+
}
18+
}
19+
func testCaseLetNestedIf() {
20+
let a: Int? = 3
21+
if case .some(let b) = a, b != 5 {}
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
func testCollapseNestedIf() {
2+
let a = 3
3+
if a > 2, a < 10 {}
4+
}
5+
func testMultiConditionalNestedIf() {
6+
let a = 3
7+
if a > 2, a != 4 {
8+
if a < 10, a != 5 {}
9+
}
10+
}
11+
func testLetNestedIf() {
12+
let a: Int? = 3
13+
if let b = a {
14+
if b != 5 {}
15+
}
16+
}
17+
func testCaseLetNestedIf() {
18+
let a: Int? = 3
19+
if case .some(let b) = a {
20+
if b != 5 {}
21+
}
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
func testCollapseNestedIf() {
2+
let a = 3
3+
if a > 2 {
4+
if a < 10 {}
5+
}
6+
}
7+
func testMultiConditionalNestedIf() {
8+
let a = 3
9+
if a > 2, a != 4, a < 10, a != 5 {}
10+
}
11+
func testLetNestedIf() {
12+
let a: Int? = 3
13+
if let b = a {
14+
if b != 5 {}
15+
}
16+
}
17+
func testCaseLetNestedIf() {
18+
let a: Int? = 3
19+
if case .some(let b) = a {
20+
if b != 5 {}
21+
}
22+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
func testCollapseNestedIf() {
2+
let a = 3
3+
if a > 2 {
4+
if a < 10 {}
5+
}
6+
}
7+
func testMultiConditionalNestedIf() {
8+
let a = 3
9+
if a > 2, a != 4 {
10+
if a < 10, a != 5 {}
11+
}
12+
}
13+
func testLetNestedIf() {
14+
let a: Int? = 3
15+
if let b = a {
16+
if b != 5 {}
17+
}
18+
}
19+
func testCaseLetNestedIf() {
20+
let a: Int? = 3
21+
if case .some(let b) = a {
22+
if b != 5 {}
23+
}
24+
}
25+
// RUN: rm -rf %t.result && mkdir -p %t.result
26+
// RUN: %refactor -collapse-nested-if -source-filename %s -pos=3:3 > %t.result/L3-3.swift
27+
// RUN: diff -u %S/Outputs/basic/L3-3.swift.expected %t.result/L3-3.swift
28+
// RUN: %refactor -collapse-nested-if -source-filename %s -pos=9:3 > %t.result/L9-3.swift
29+
// RUN: diff -u %S/Outputs/basic/L9-3.swift.expected %t.result/L9-3.swift
30+
// RUN: %refactor -collapse-nested-if -source-filename %s -pos=15:3 > %t.result/L15-3.swift
31+
// RUN: diff -u %S/Outputs/basic/L15-3.swift.expected %t.result/L15-3.swift
32+
// RUN: %refactor -collapse-nested-if -source-filename %s -pos=21:3 > %t.result/L21-3.swift
33+
// RUN: diff -u %S/Outputs/basic/L21-3.swift.expected %t.result/L21-3.swift

test/refactoring/RefactoringKind/basic.swift

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,35 @@ func testStringLiteral() -> String {
174174
return "abc"
175175
}
176176

177+
func testCollapseNestedIf() {
178+
let a = 3
179+
if a > 2 {
180+
if a < 10 {}
181+
}
182+
}
183+
184+
func testMultiConditionalNestedIf() {
185+
let a = 3
186+
if a > 2, a != 4 {
187+
if a < 10 {}
188+
}
189+
}
190+
191+
func testExtraDeclNestedIf() {
192+
let a = 3
193+
if a > 2 {
194+
if a < 10 {}
195+
let b = 0
196+
}
197+
}
198+
199+
func testExtraIfNestedIf() {
200+
let a = 3
201+
if a > 2 {
202+
if a < 10 {}
203+
let b = 0
204+
}
205+
}
177206
// RUN: %refactor -source-filename %s -pos=2:1 -end-pos=5:13 | %FileCheck %s -check-prefix=CHECK1
178207
// RUN: %refactor -source-filename %s -pos=3:1 -end-pos=5:13 | %FileCheck %s -check-prefix=CHECK1
179208
// RUN: %refactor -source-filename %s -pos=4:1 -end-pos=5:13 | %FileCheck %s -check-prefix=CHECK1
@@ -245,6 +274,11 @@ func testStringLiteral() -> String {
245274

246275
// RUN: %refactor -source-filename %s -pos=173:3 -end-pos=173:27| %FileCheck %s -check-prefix=CHECK-EXTRCT-METHOD
247276

277+
// RUN: %refactor -source-filename %s -pos=179:3 | %FileCheck %s -check-prefix=CHECK-COLLAPSE-NESTED-IF-EXPRESSION
278+
// RUN: %refactor -source-filename %s -pos=186:3 | %FileCheck %s -check-prefix=CHECK-COLLAPSE-NESTED-IF-EXPRESSION
279+
// RUN: %refactor -source-filename %s -pos=193:3 | %FileCheck %s -check-prefix=CHECK-NONE
280+
// RUN: %refactor -source-filename %s -pos=201:3 | %FileCheck %s -check-prefix=CHECK-NONE
281+
248282
// CHECK1: Action begins
249283
// CHECK1-NEXT: Extract Method
250284
// CHECK1-NEXT: Action ends
@@ -279,3 +313,5 @@ func testStringLiteral() -> String {
279313
// CHECK-EXTRCT-METHOD-NEXT: Action ends
280314

281315
// CHECK-LOCALIZE-STRING: Localize String
316+
317+
// CHECK-COLLAPSE-NESTED-IF-EXPRESSION: Collapse Nested If Expression

tools/swift-refactor/swift-refactor.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ Action(llvm::cl::desc("kind:"), llvm::cl::init(RefactoringKind::None),
3838
"expand-default", "Perform expand default statement refactoring"),
3939
clEnumValN(RefactoringKind::LocalizeString,
4040
"localize-string", "Perform string localization refactoring"),
41+
clEnumValN(RefactoringKind::CollapseNestedIfExpr,
42+
"collapse-nested-if", "Perform collapse nested if statements"),
4143
clEnumValN(RefactoringKind::SimplifyNumberLiteral,
4244
"simplify-long-number", "Perform simplify long number literal refactoring"),
4345
clEnumValN(RefactoringKind::ExtractFunction,

0 commit comments

Comments
 (0)