--- /dev/null
+/**
+ * @fileoverview Rule to flag unsafe statements in finally block
+ * @author Onur Temizkan
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const SENTINEL_NODE_TYPE_RETURN_THROW = /^(?:Program|(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression)$/u;
+const SENTINEL_NODE_TYPE_BREAK = /^(?:Program|(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|DoWhileStatement|WhileStatement|ForOfStatement|ForInStatement|ForStatement|SwitchStatement)$/u;
+const SENTINEL_NODE_TYPE_CONTINUE = /^(?:Program|(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|DoWhileStatement|WhileStatement|ForOfStatement|ForInStatement|ForStatement)$/u;
+
+
+//------------------------------------------------------------------------------
+// Rule Definition
+//------------------------------------------------------------------------------
+
+module.exports = {
+ meta: {
+ type: "problem",
+
+ docs: {
+ description: "disallow control flow statements in `finally` blocks",
+ category: "Possible Errors",
+ recommended: true,
+ url: "https://eslint.org/docs/rules/no-unsafe-finally"
+ },
+
+ schema: []
+ },
+ create(context) {
+
+ /**
+ * Checks if the node is the finalizer of a TryStatement
+ * @param {ASTNode} node node to check.
+ * @returns {boolean} - true if the node is the finalizer of a TryStatement
+ */
+ function isFinallyBlock(node) {
+ return node.parent.type === "TryStatement" && node.parent.finalizer === node;
+ }
+
+ /**
+ * Climbs up the tree if the node is not a sentinel node
+ * @param {ASTNode} node node to check.
+ * @param {string} label label of the break or continue statement
+ * @returns {boolean} - return whether the node is a finally block or a sentinel node
+ */
+ function isInFinallyBlock(node, label) {
+ let labelInside = false;
+ let sentinelNodeType;
+
+ if (node.type === "BreakStatement" && !node.label) {
+ sentinelNodeType = SENTINEL_NODE_TYPE_BREAK;
+ } else if (node.type === "ContinueStatement") {
+ sentinelNodeType = SENTINEL_NODE_TYPE_CONTINUE;
+ } else {
+ sentinelNodeType = SENTINEL_NODE_TYPE_RETURN_THROW;
+ }
+
+ for (
+ let currentNode = node;
+ currentNode && !sentinelNodeType.test(currentNode.type);
+ currentNode = currentNode.parent
+ ) {
+ if (currentNode.parent.label && label && (currentNode.parent.label.name === label.name)) {
+ labelInside = true;
+ }
+ if (isFinallyBlock(currentNode)) {
+ if (label && labelInside) {
+ return false;
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Checks whether the possibly-unsafe statement is inside a finally block.
+ * @param {ASTNode} node node to check.
+ * @returns {void}
+ */
+ function check(node) {
+ if (isInFinallyBlock(node, node.label)) {
+ context.report({
+ message: "Unsafe usage of {{nodeType}}.",
+ data: {
+ nodeType: node.type
+ },
+ node,
+ line: node.loc.line,
+ column: node.loc.column
+ });
+ }
+ }
+
+ return {
+ ReturnStatement: check,
+ ThrowStatement: check,
+ BreakStatement: check,
+ ContinueStatement: check
+ };
+ }
+};