1/** 2 * @fileoverview Rule to flag unsafe statements in finally block 3 * @author Onur Temizkan 4 */ 5 6"use strict"; 7 8//------------------------------------------------------------------------------ 9// Helpers 10//------------------------------------------------------------------------------ 11 12const SENTINEL_NODE_TYPE_RETURN_THROW = /^(?:Program|(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression)$/u; 13const SENTINEL_NODE_TYPE_BREAK = /^(?:Program|(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|DoWhileStatement|WhileStatement|ForOfStatement|ForInStatement|ForStatement|SwitchStatement)$/u; 14const SENTINEL_NODE_TYPE_CONTINUE = /^(?:Program|(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|DoWhileStatement|WhileStatement|ForOfStatement|ForInStatement|ForStatement)$/u; 15 16 17//------------------------------------------------------------------------------ 18// Rule Definition 19//------------------------------------------------------------------------------ 20 21module.exports = { 22 meta: { 23 type: "problem", 24 25 docs: { 26 description: "disallow control flow statements in `finally` blocks", 27 category: "Possible Errors", 28 recommended: true, 29 url: "https://eslint.org/docs/rules/no-unsafe-finally" 30 }, 31 32 schema: [], 33 34 messages: { 35 unsafeUsage: "Unsafe usage of {{nodeType}}." 36 } 37 }, 38 create(context) { 39 40 /** 41 * Checks if the node is the finalizer of a TryStatement 42 * @param {ASTNode} node node to check. 43 * @returns {boolean} - true if the node is the finalizer of a TryStatement 44 */ 45 function isFinallyBlock(node) { 46 return node.parent.type === "TryStatement" && node.parent.finalizer === node; 47 } 48 49 /** 50 * Climbs up the tree if the node is not a sentinel node 51 * @param {ASTNode} node node to check. 52 * @param {string} label label of the break or continue statement 53 * @returns {boolean} - return whether the node is a finally block or a sentinel node 54 */ 55 function isInFinallyBlock(node, label) { 56 let labelInside = false; 57 let sentinelNodeType; 58 59 if (node.type === "BreakStatement" && !node.label) { 60 sentinelNodeType = SENTINEL_NODE_TYPE_BREAK; 61 } else if (node.type === "ContinueStatement") { 62 sentinelNodeType = SENTINEL_NODE_TYPE_CONTINUE; 63 } else { 64 sentinelNodeType = SENTINEL_NODE_TYPE_RETURN_THROW; 65 } 66 67 for ( 68 let currentNode = node; 69 currentNode && !sentinelNodeType.test(currentNode.type); 70 currentNode = currentNode.parent 71 ) { 72 if (currentNode.parent.label && label && (currentNode.parent.label.name === label.name)) { 73 labelInside = true; 74 } 75 if (isFinallyBlock(currentNode)) { 76 if (label && labelInside) { 77 return false; 78 } 79 return true; 80 } 81 } 82 return false; 83 } 84 85 /** 86 * Checks whether the possibly-unsafe statement is inside a finally block. 87 * @param {ASTNode} node node to check. 88 * @returns {void} 89 */ 90 function check(node) { 91 if (isInFinallyBlock(node, node.label)) { 92 context.report({ 93 messageId: "unsafeUsage", 94 data: { 95 nodeType: node.type 96 }, 97 node, 98 line: node.loc.line, 99 column: node.loc.column 100 }); 101 } 102 } 103 104 return { 105 ReturnStatement: check, 106 ThrowStatement: check, 107 BreakStatement: check, 108 ContinueStatement: check 109 }; 110 } 111}; 112