]>
Commit | Line | Data |
---|---|---|
eb39fafa DC |
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 | ||
12 | const SENTINEL_NODE_TYPE_RETURN_THROW = /^(?:Program|(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression)$/u; | |
13 | const SENTINEL_NODE_TYPE_BREAK = /^(?:Program|(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|DoWhileStatement|WhileStatement|ForOfStatement|ForInStatement|ForStatement|SwitchStatement)$/u; | |
14 | const SENTINEL_NODE_TYPE_CONTINUE = /^(?:Program|(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|DoWhileStatement|WhileStatement|ForOfStatement|ForInStatement|ForStatement)$/u; | |
15 | ||
16 | ||
17 | //------------------------------------------------------------------------------ | |
18 | // Rule Definition | |
19 | //------------------------------------------------------------------------------ | |
20 | ||
34eeec05 | 21 | /** @type {import('../shared/types').Rule} */ |
eb39fafa DC |
22 | module.exports = { |
23 | meta: { | |
24 | type: "problem", | |
25 | ||
26 | docs: { | |
27 | description: "disallow control flow statements in `finally` blocks", | |
eb39fafa DC |
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 | }; |