[IDE] Refactoring: conversion from “force try” to error handled version (#12063)

* [IDE] Refactoring: conversion from “force try” do error handled version

* Removed unnecessary empty line

* Changed name of the refactor

* Review fixes

* Fixed recognizing try_! location by CursorResolver

* Review fixes
diff --git a/include/swift/IDE/RefactoringKinds.def b/include/swift/IDE/RefactoringKinds.def
index d9b6123..5f436f1 100644
--- a/include/swift/IDE/RefactoringKinds.def
+++ b/include/swift/IDE/RefactoringKinds.def
@@ -40,6 +40,8 @@
 
 CURSOR_REFACTORING(CollapseNestedIfExpr, "Collapse Nested If Expression", collapse.nested.if.expr)
 
+CURSOR_REFACTORING(ConvertToDoCatch, "Convert To Do/Catch", convert.do.catch)
+
 RANGE_REFACTORING(ExtractExpr, "Extract Expression", extract.expr)
 
 RANGE_REFACTORING(ExtractFunction, "Extract Method", extract.function)
diff --git a/lib/IDE/Refactoring.cpp b/lib/IDE/Refactoring.cpp
index d127303..f6bba17 100644
--- a/lib/IDE/Refactoring.cpp
+++ b/lib/IDE/Refactoring.cpp
@@ -12,6 +12,7 @@
 
 #include "swift/IDE/Refactoring.h"
 #include "swift/AST/ASTContext.h"
+#include "swift/AST/ASTPrinter.h"
 #include "swift/AST/Decl.h"
 #include "swift/AST/DiagnosticsRefactoring.h"
 #include "swift/AST/Expr.h"
@@ -1923,6 +1924,60 @@
   return false;
 }
 
+static CharSourceRange
+  findSourceRangeToWrapInCatch(ResolvedCursorInfo CursorInfo,
+                               SourceFile *TheFile,
+                               SourceManager &SM) {
+  Expr *E = CursorInfo.TrailingExpr;
+  if (!E)
+    return CharSourceRange();
+  auto Node = ASTNode(E);
+  auto NodeChecker = [](ASTNode N) { return N.isStmt(StmtKind::Brace); };
+  ContextFinder Finder(*TheFile, Node, NodeChecker);
+  Finder.resolve();
+  auto Contexts = Finder.getContexts();
+  if (Contexts.size() == 0)
+    return CharSourceRange();
+  auto TargetNode = Contexts.back();
+  BraceStmt *BStmt = dyn_cast<BraceStmt>(TargetNode.dyn_cast<Stmt*>());
+  auto ConvertToCharRange = [&SM](SourceRange SR) {
+    return Lexer::getCharSourceRangeFromSourceRange(SM, SR);
+  };
+  assert(BStmt);
+  auto ExprRange = ConvertToCharRange(E->getSourceRange());
+  // Check elements of the deepest BraceStmt, pick one that covers expression.
+  for (auto Elem: BStmt->getElements()) {
+    auto ElemRange = ConvertToCharRange(Elem.getSourceRange());
+    if (ElemRange.contains(ExprRange))
+      TargetNode = Elem;
+  }
+  return ConvertToCharRange(TargetNode.getSourceRange());
+}
+
+bool RefactoringActionConvertToDoCatch::isApplicable(ResolvedCursorInfo Tok) {
+  if (!Tok.TrailingExpr)
+    return false;
+  return isa<ForceTryExpr>(Tok.TrailingExpr);
+}
+
+bool RefactoringActionConvertToDoCatch::performChange() {
+  auto *TryExpr = dyn_cast<ForceTryExpr>(CursorInfo.TrailingExpr);
+  if (!TryExpr)
+    return true;
+  auto Range = findSourceRangeToWrapInCatch(CursorInfo, TheFile, SM);
+  if (!Range.isValid())
+    return true;
+  // Wrap given range in do catch block.
+  EditConsumer.accept(SM, Range.getStart(), "do {\n");
+  EditorConsumerInsertStream OS(EditConsumer, SM, Range.getEnd());
+  OS << "\n} catch {\n" << getCodePlaceholder() << "\n}";
+
+  // Delete ! from try! expression
+  auto ExclaimRange = CharSourceRange(TryExpr->getExclaimLoc(), 1);
+  EditConsumer.accept(SM, ExclaimRange, "");
+  return false;
+}
+
 /// Given a cursor position, this function tries to collect a number literal
 /// expression immediately following the cursor.
 static NumberLiteralExpr *getTrailingNumberLiteral(ResolvedCursorInfo Tok) {
diff --git a/lib/IDE/SwiftSourceDocInfo.cpp b/lib/IDE/SwiftSourceDocInfo.cpp
index 303c389..e2215ea 100644
--- a/lib/IDE/SwiftSourceDocInfo.cpp
+++ b/lib/IDE/SwiftSourceDocInfo.cpp
@@ -188,9 +188,14 @@
         ContainerType = ME->getBase()->getType();
       }
     }
-
+    auto IsProperCursorLocation = E->getStartLoc() == LocToResolve;
+    // Handle cursor placement between try and ! in ForceTryExpr.
+    if (auto *FTE = dyn_cast<ForceTryExpr>(E)) {
+      IsProperCursorLocation = LocToResolve == FTE->getExclaimLoc() ||
+      IsProperCursorLocation;
+    }
     // Keep track of trailing expressions.
-    if (!E->isImplicit() && E->getStartLoc() == LocToResolve)
+    if (!E->isImplicit() && IsProperCursorLocation)
       TrailingExprStack.push_back(E);
   }
   return true;
diff --git a/test/refactoring/ConvertForceTryToTryCatch/Outputs/basic/L5.swift.expected b/test/refactoring/ConvertForceTryToTryCatch/Outputs/basic/L5.swift.expected
new file mode 100644
index 0000000..3bf5e71
--- /dev/null
+++ b/test/refactoring/ConvertForceTryToTryCatch/Outputs/basic/L5.swift.expected
@@ -0,0 +1,8 @@
+func throwingFunc() throws {
+    return
+}
+do {
+try throwingFunc()
+} catch {
+<#code#>
+}
diff --git a/test/refactoring/ConvertForceTryToTryCatch/Outputs/basic_in_func/L5.swift.expected b/test/refactoring/ConvertForceTryToTryCatch/Outputs/basic_in_func/L5.swift.expected
new file mode 100644
index 0000000..8c0477c
--- /dev/null
+++ b/test/refactoring/ConvertForceTryToTryCatch/Outputs/basic_in_func/L5.swift.expected
@@ -0,0 +1,10 @@
+func throwingFunc() throws {
+    return
+}
+func foo() {
+    do {
+try throwingFunc()
+} catch {
+<#code#>
+}
+}
diff --git a/test/refactoring/ConvertForceTryToTryCatch/Outputs/for_loop/L5.swift.expected b/test/refactoring/ConvertForceTryToTryCatch/Outputs/for_loop/L5.swift.expected
new file mode 100644
index 0000000..4ab08b7
--- /dev/null
+++ b/test/refactoring/ConvertForceTryToTryCatch/Outputs/for_loop/L5.swift.expected
@@ -0,0 +1,10 @@
+func throwingFunc() throws -> [Int] {
+    return []
+}
+do {
+for num in try throwingFunc() {
+    let _ = num
+}
+} catch {
+<#code#>
+}
diff --git a/test/refactoring/ConvertForceTryToTryCatch/Outputs/in_if_clause/L5.swift.expected b/test/refactoring/ConvertForceTryToTryCatch/Outputs/in_if_clause/L5.swift.expected
new file mode 100644
index 0000000..28bbad5
--- /dev/null
+++ b/test/refactoring/ConvertForceTryToTryCatch/Outputs/in_if_clause/L5.swift.expected
@@ -0,0 +1,10 @@
+func throwingFunc() throws -> Bool {
+    return true
+}
+do {
+if try throwingFunc() { 
+    let _ = 3
+}
+} catch {
+<#code#>
+}
diff --git a/test/refactoring/ConvertForceTryToTryCatch/Outputs/in_if_clause_nested/L5.swift.expected b/test/refactoring/ConvertForceTryToTryCatch/Outputs/in_if_clause_nested/L5.swift.expected
new file mode 100644
index 0000000..135ab26
--- /dev/null
+++ b/test/refactoring/ConvertForceTryToTryCatch/Outputs/in_if_clause_nested/L5.swift.expected
@@ -0,0 +1,12 @@
+func throwingFunc() throws -> Bool {
+    return true
+}
+if 4 > 3 {
+    do {
+if try throwingFunc() { 
+        let _ = 3
+    }
+} catch {
+<#code#>
+}
+}
diff --git a/test/refactoring/ConvertForceTryToTryCatch/Outputs/in_if_clause_with_let_binding/L5.swift.expected b/test/refactoring/ConvertForceTryToTryCatch/Outputs/in_if_clause_with_let_binding/L5.swift.expected
new file mode 100644
index 0000000..e21b4a4
--- /dev/null
+++ b/test/refactoring/ConvertForceTryToTryCatch/Outputs/in_if_clause_with_let_binding/L5.swift.expected
@@ -0,0 +1,10 @@
+func throwingFunc() throws -> Int? {
+    return nil
+}
+do {
+if let val = try throwingFunc() { 
+    let _ = val
+}
+} catch {
+<#code#>
+}
diff --git a/test/refactoring/ConvertForceTryToTryCatch/Outputs/inside_constructor/L5.swift.expected b/test/refactoring/ConvertForceTryToTryCatch/Outputs/inside_constructor/L5.swift.expected
new file mode 100644
index 0000000..42adf87
--- /dev/null
+++ b/test/refactoring/ConvertForceTryToTryCatch/Outputs/inside_constructor/L5.swift.expected
@@ -0,0 +1,9 @@
+func throwingFunc() throws -> [Int] {
+    return []
+}
+struct X { let array: [Int] }
+do {
+let _ = X(array: try throwingFunc())
+} catch {
+<#code#>
+}
diff --git a/test/refactoring/ConvertForceTryToTryCatch/basic.swift b/test/refactoring/ConvertForceTryToTryCatch/basic.swift
new file mode 100644
index 0000000..54e1982
--- /dev/null
+++ b/test/refactoring/ConvertForceTryToTryCatch/basic.swift
@@ -0,0 +1,7 @@
+func throwingFunc() throws {
+    return
+}
+try! throwingFunc()
+// RUN: rm -rf %t.result && mkdir -p %t.result
+// RUN: %refactor -convert-to-do-catch -source-filename %s -pos=4:3 > %t.result/L5.swift
+// RUN: diff -u %S/Outputs/basic/L5.swift.expected %t.result/L5.swift
diff --git a/test/refactoring/ConvertForceTryToTryCatch/basic_in_func.swift b/test/refactoring/ConvertForceTryToTryCatch/basic_in_func.swift
new file mode 100644
index 0000000..f7e7d41
--- /dev/null
+++ b/test/refactoring/ConvertForceTryToTryCatch/basic_in_func.swift
@@ -0,0 +1,9 @@
+func throwingFunc() throws {
+    return
+}
+func foo() {
+    try! throwingFunc()
+}
+// RUN: rm -rf %t.result && mkdir -p %t.result
+// RUN: %refactor -convert-to-do-catch -source-filename %s -pos=5:7 > %t.result/L5.swift
+// RUN: diff -u %S/Outputs/basic_in_func/L5.swift.expected %t.result/L5.swift
diff --git a/test/refactoring/ConvertForceTryToTryCatch/for_loop.swift b/test/refactoring/ConvertForceTryToTryCatch/for_loop.swift
new file mode 100644
index 0000000..c378013
--- /dev/null
+++ b/test/refactoring/ConvertForceTryToTryCatch/for_loop.swift
@@ -0,0 +1,9 @@
+func throwingFunc() throws -> [Int] {
+    return []
+}
+for num in try! throwingFunc() {
+    let _ = num
+}
+// RUN: rm -rf %t.result && mkdir -p %t.result
+// RUN: %refactor -convert-to-do-catch -source-filename %s -pos=4:14 > %t.result/L5.swift
+// RUN: diff -u %S/Outputs/for_loop/L5.swift.expected %t.result/L5.swift
diff --git a/test/refactoring/ConvertForceTryToTryCatch/in_if_clause.swift b/test/refactoring/ConvertForceTryToTryCatch/in_if_clause.swift
new file mode 100644
index 0000000..6af7fa2
--- /dev/null
+++ b/test/refactoring/ConvertForceTryToTryCatch/in_if_clause.swift
@@ -0,0 +1,9 @@
+func throwingFunc() throws -> Bool {
+    return true
+}
+if try! throwingFunc() { 
+    let _ = 3
+}
+// RUN: rm -rf %t.result && mkdir -p %t.result
+// RUN: %refactor -convert-to-do-catch -source-filename %s -pos=4:4 > %t.result/L5.swift
+// RUN: diff -u %S/Outputs/in_if_clause/L5.swift.expected %t.result/L5.swift
diff --git a/test/refactoring/ConvertForceTryToTryCatch/in_if_clause_nested.swift b/test/refactoring/ConvertForceTryToTryCatch/in_if_clause_nested.swift
new file mode 100644
index 0000000..7cf27bc
--- /dev/null
+++ b/test/refactoring/ConvertForceTryToTryCatch/in_if_clause_nested.swift
@@ -0,0 +1,11 @@
+func throwingFunc() throws -> Bool {
+    return true
+}
+if 4 > 3 {
+    if try! throwingFunc() { 
+        let _ = 3
+    }
+}
+// RUN: rm -rf %t.result && mkdir -p %t.result
+// RUN: %refactor -convert-to-do-catch -source-filename %s -pos=5:10 > %t.result/L5.swift
+// RUN: diff -u %S/Outputs/in_if_clause_nested/L5.swift.expected %t.result/L5.swift
diff --git a/test/refactoring/ConvertForceTryToTryCatch/in_if_clause_with_let_binding.swift b/test/refactoring/ConvertForceTryToTryCatch/in_if_clause_with_let_binding.swift
new file mode 100644
index 0000000..d381356
--- /dev/null
+++ b/test/refactoring/ConvertForceTryToTryCatch/in_if_clause_with_let_binding.swift
@@ -0,0 +1,9 @@
+func throwingFunc() throws -> Int? {
+    return nil
+}
+if let val = try! throwingFunc() { 
+    let _ = val
+}
+// RUN: rm -rf %t.result && mkdir -p %t.result
+// RUN: %refactor -convert-to-do-catch -source-filename %s -pos=4:16 > %t.result/L5.swift
+// RUN: diff -u %S/Outputs/in_if_clause_with_let_binding/L5.swift.expected %t.result/L5.swift
diff --git a/test/refactoring/ConvertForceTryToTryCatch/inside_constructor.swift b/test/refactoring/ConvertForceTryToTryCatch/inside_constructor.swift
new file mode 100644
index 0000000..2c7df9d
--- /dev/null
+++ b/test/refactoring/ConvertForceTryToTryCatch/inside_constructor.swift
@@ -0,0 +1,8 @@
+func throwingFunc() throws -> [Int] {
+    return []
+}
+struct X { let array: [Int] }
+let _ = X(array: try! throwingFunc())
+// RUN: rm -rf %t.result && mkdir -p %t.result
+// RUN: %refactor -convert-to-do-catch -source-filename %s -pos=5:19 > %t.result/L5.swift
+// RUN: diff -u %S/Outputs/inside_constructor/L5.swift.expected %t.result/L5.swift
diff --git a/test/refactoring/RefactoringKind/basic.swift b/test/refactoring/RefactoringKind/basic.swift
index bb8ad22..99edb0a 100644
--- a/test/refactoring/RefactoringKind/basic.swift
+++ b/test/refactoring/RefactoringKind/basic.swift
@@ -211,6 +211,11 @@
   let _ = name + "Bourne"
   let _ = name + one
 }
+
+func testForceTry() {
+  func throwingFunc() throws -> Int { return 3 }
+  let _ = try! throwingFunc()
+}
 // RUN: %refactor -source-filename %s -pos=2:1 -end-pos=5:13 | %FileCheck %s -check-prefix=CHECK1
 // RUN: %refactor -source-filename %s -pos=3:1 -end-pos=5:13 | %FileCheck %s -check-prefix=CHECK1
 // RUN: %refactor -source-filename %s -pos=4:1 -end-pos=5:13 | %FileCheck %s -check-prefix=CHECK1
@@ -291,6 +296,11 @@
 // RUN: %refactor -source-filename %s -pos=211:11 -end-pos=211:26 | %FileCheck %s -check-prefix=CHECK-STRINGS-INTERPOLATION
 // RUN: %refactor -source-filename %s -pos=212:11 -end-pos=212:21 | %FileCheck %s -check-prefix=CHECK-STRINGS-INTERPOLATION
 
+// RUN: %refactor -source-filename %s -pos=217:11 | %FileCheck %s -check-prefix=CHECK-TRY-CATCH
+// RUN: %refactor -source-filename %s -pos=217:12 | %FileCheck %s -check-prefix=CHECK-TRY-CATCH
+// RUN: %refactor -source-filename %s -pos=217:13 | %FileCheck %s -check-prefix=CHECK-TRY-CATCH
+// RUN: %refactor -source-filename %s -pos=217:14 | %FileCheck %s -check-prefix=CHECK-TRY-CATCH
+
 // CHECK1: Action begins
 // CHECK1-NEXT: Extract Method
 // CHECK1-NEXT: Action ends
@@ -330,3 +340,5 @@
 
 // CHECK-STRINGS-INTERPOLATION: Convert to String Interpolation
 
+// CHECK-TRY-CATCH: Convert To Do/Catch
+
diff --git a/tools/swift-refactor/swift-refactor.cpp b/tools/swift-refactor/swift-refactor.cpp
index 84cef21..39deddc 100644
--- a/tools/swift-refactor/swift-refactor.cpp
+++ b/tools/swift-refactor/swift-refactor.cpp
@@ -39,11 +39,13 @@
            clEnumValN(RefactoringKind::LocalizeString,
                       "localize-string", "Perform string localization refactoring"),
            clEnumValN(RefactoringKind::CollapseNestedIfExpr,
-                       "collapse-nested-if", "Perform collapse nested if statements"),
+                      "collapse-nested-if", "Perform collapse nested if statements"),
+           clEnumValN(RefactoringKind::ConvertToDoCatch,
+                      "convert-to-do-catch", "Perform force try to do try catch refactoring"),
            clEnumValN(RefactoringKind::SimplifyNumberLiteral,
                       "simplify-long-number", "Perform simplify long number literal refactoring"),
            clEnumValN(RefactoringKind::ConvertStringsConcatenationToInterpolation,
-                     "strings-concatenation-to-interpolation", "Perform strings concatenation to interpolation refactoring"),
+                      "strings-concatenation-to-interpolation", "Perform strings concatenation to interpolation refactoring"),
            clEnumValN(RefactoringKind::ExtractFunction,
                       "extract-function", "Perform extract function refactoring"),
            clEnumValN(RefactoringKind::GlobalRename,