rslint_core/groups/errors/
no_unsafe_finally.rs

1use crate::rule_prelude::*;
2use SyntaxKind::*;
3
4declare_lint! {
5    /**
6    Forbid the use of unsafe control flow statements in try and catch blocks.
7
8    JavaScript suspends any running control flow statements inside of `try` and `catch` blocks until
9    `finally` is done executing. This means that any control statements such as `return`, `throw`, `break`,
10    and `continue` which are used inside of a `finally` will override any control statements in `try` and `catch`.
11    This is almost always unexpected behavior.
12
13    ## Incorrect Code Examples
14
15    ```js
16    // We expect 10 to be returned, but 5 is actually returned
17    function foo() {
18        try {
19            return 10;
20        //  ^^^^^^^^^ this statement is executed, but actually returning is paused...
21        } finally {
22            return 5;
23        //  ^^^^^^^^^ ...finally is executed, and this statement returns from the function, **the previous is ignored**
24        }
25    }
26    foo() // 5
27    ```
28
29    Throwing errors inside try statements
30
31    ```js
32    // We expect an error to be thrown, then 5 to be returned, but the error is not thrown
33    function foo() {
34        try {
35            throw new Error("bar");
36        //  ^^^^^^^^^^^^^^^^^^^^^^^ this statement is executed but throwing the error is paused...
37        } finally {
38            return 5;
39        //  ^^^^^^^^^ ...we expect the error to be thrown and then for 5 to be returned,
40        //  but 5 is returned early, **the error is not thrown**.
41        }
42    }
43    foo() // 5
44    ```
45    */
46    #[derive(Default)]
47    NoUnsafeFinally,
48    errors,
49    tags(Recommended),
50    "no-unsafe-finally"
51}
52
53pub const CONTROL_FLOW_STMT: [SyntaxKind; 4] = [BREAK_STMT, CONTINUE_STMT, THROW_STMT, RETURN_STMT];
54
55#[typetag::serde]
56impl CstRule for NoUnsafeFinally {
57    fn check_node(&self, node: &SyntaxNode, ctx: &mut RuleCtx) -> Option<()> {
58        if CONTROL_FLOW_STMT.contains(&node.kind())
59            && node.parent()?.parent()?.is::<ast::Finalizer>()
60        {
61            self.output(node, ctx);
62        }
63        None
64    }
65}
66
67impl NoUnsafeFinally {
68    fn output(&self, node: &SyntaxNode, ctx: &mut RuleCtx) -> Option<()> {
69        let parent = if node.parent()?.kind() == FINALIZER {
70            node.parent()?
71        } else {
72            node.parent()?.parent()?
73        };
74
75        let try_stmt = parent.parent()?.to::<ast::TryStmt>();
76
77        let err = if let Some(control) = try_stmt
78            .test()?
79            .syntax()
80            .children()
81            .find(|it| CONTROL_FLOW_STMT.contains(&it.kind()))
82        {
83            let err = ctx.err(
84                self.name(),
85                format!(
86                    "Unsafe usage of a {} inside of a Try statement",
87                    node.readable_stmt_name()
88                ),
89            );
90
91            let get_kind = |kind: SyntaxKind| match kind {
92                RETURN_STMT => "returning from the block",
93                CONTINUE_STMT => "continuing the loop",
94                THROW_STMT => "throwing this error",
95                BREAK_STMT => "breaking from the loop",
96                _ => unreachable!(),
97            };
98
99            err.secondary(
100                control.clone(),
101                format!(
102                    "{} is paused until the `finally` block is done executing...",
103                    get_kind(control.kind())
104                ),
105            )
106            .primary(
107                node,
108                format!(
109                    "...however, {} exits the statement altogether",
110                    get_kind(node.kind())
111                ),
112            )
113            .primary(
114                node,
115                format!("which makes `{}` never finish running", control),
116            )
117        } else {
118            ctx.err(
119                self.name(),
120                format!(
121                    "Unsafe usage of a {} inside of a Try statement",
122                    node.readable_stmt_name()
123                ),
124            )
125            .primary(
126                node,
127                "this statement abruptly ends execution, yielding unwanted behavior",
128            )
129        };
130
131        ctx.add_err(err);
132        None
133    }
134}
135
136rule_tests! {
137    NoUnsafeFinally::default(),
138    err: {
139        "
140        try {
141            throw A;
142        } finally {
143            return;
144        }
145        ",
146        "
147        try {
148            throw new Error();
149        } catch {
150
151        } finally {
152            continue;
153        }
154        ",
155        /// ignore
156        "
157        try {
158            {}
159        } finally {
160            try {} finally {
161                return 5;
162            }
163        }
164        "
165    },
166    ok: {
167        "
168        try {
169            throw A;
170        } finally {
171            if (false) {
172                return true;
173            }
174        }
175        "
176    }
177}