Skip to content

Commit 7b3b400

Browse files
feat(no-throw-statements)!: replace option allowInAsyncFunctions with allowToRejectPromises
1 parent 9042739 commit 7b3b400

File tree

6 files changed

+70
-19
lines changed

6 files changed

+70
-19
lines changed

docs/rules/no-throw-statements.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,15 @@ This rule accepts an options object of the following type:
5858

5959
```ts
6060
type Options = {
61-
allowInAsyncFunctions: boolean;
61+
allowToRejectPromises: boolean;
6262
};
6363
```
6464

6565
### Default Options
6666

6767
```ts
6868
const defaults = {
69-
allowInAsyncFunctions: false,
69+
allowToRejectPromises: false,
7070
};
7171
```
7272

@@ -76,19 +76,19 @@ const defaults = {
7676

7777
```ts
7878
const recommendedAndLiteOptions = {
79-
allowInAsyncFunctions: true,
79+
allowToRejectPromises: true,
8080
};
8181
```
8282

83-
### `allowInAsyncFunctions`
83+
### `allowToRejectPromises`
8484

85-
If true, throw statements will be allowed within async functions.\
85+
If true, throw statements will be allowed when they are used to reject a promise, such when in an async function.\
8686
This essentially allows throw statements to be used as return statements for errors.
8787

8888
#### ✅ Correct
8989

9090
```js
91-
/* eslint functional/no-throw-statements: ["error", { "allowInAsyncFunctions": true }] */
91+
/* eslint functional/no-throw-statements: ["error", { "allowToRejectPromises": true }] */
9292

9393
async function divide(x, y) {
9494
const [xv, yv] = await Promise.all([x, y]);

docs/rules/prefer-immutable-types.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,7 @@ acceptsCallback((options: CallbackOptions) => {});
426426

427427
```ts
428428
export interface CallbackOptions {
429-
prop: string;
429+
readonly prop: string;
430430
}
431431
type Callback = (options: CallbackOptions) => void;
432432
type AcceptsCallback = (callback: Callback) => void;

eslint.config.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,12 @@ export default rsEslint(
3131
},
3232
},
3333
formatters: true,
34-
functional: "lite",
34+
functional: {
35+
functionalEnforcement: "lite",
36+
overrides: {
37+
"functional/no-throw-statements": "off",
38+
},
39+
},
3540
jsonc: true,
3641
markdown: {
3742
enableTypeRequiredRules: true,
@@ -52,9 +57,6 @@ export default rsEslint(
5257
rules: {
5358
// Some types say they have nonnullable properties, but they don't always.
5459
"ts/no-unnecessary-condition": "off",
55-
56-
// Temp
57-
"functional/no-throw-statements": "off",
5860
},
5961
},
6062
{

src/configs/recommended.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ const overrides = {
6666
[noThrowStatements.fullName]: [
6767
"error",
6868
{
69-
allowInAsyncFunctions: true,
69+
allowToRejectPromises: true,
7070
},
7171
],
7272
[noTryStatements.fullName]: "off",

src/rules/no-throw-statements.ts

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@ import {
88
type RuleResult,
99
createRule,
1010
} from "#/utils/rule";
11-
import { isInFunctionBody } from "#/utils/tree";
11+
import {
12+
getEnclosingFunction,
13+
getEnclosingTryStatement,
14+
isInPromiseHandlerFunction,
15+
} from "#/utils/tree";
1216

1317
/**
1418
* The name of this rule.
@@ -25,7 +29,7 @@ export const fullName = `${ruleNameScope}/${name}`;
2529
*/
2630
type Options = [
2731
{
28-
allowInAsyncFunctions: boolean;
32+
allowToRejectPromises: boolean;
2933
},
3034
];
3135

@@ -36,7 +40,7 @@ const schema: JSONSchema4[] = [
3640
{
3741
type: "object",
3842
properties: {
39-
allowInAsyncFunctions: {
43+
allowToRejectPromises: {
4044
type: "boolean",
4145
},
4246
},
@@ -49,7 +53,7 @@ const schema: JSONSchema4[] = [
4953
*/
5054
const defaultOptions: Options = [
5155
{
52-
allowInAsyncFunctions: false,
56+
allowToRejectPromises: false,
5357
},
5458
];
5559

@@ -84,9 +88,29 @@ function checkThrowStatement(
8488
context: Readonly<RuleContext<keyof typeof errorMessages, Options>>,
8589
options: Readonly<Options>,
8690
): RuleResult<keyof typeof errorMessages, Options> {
87-
const [{ allowInAsyncFunctions }] = options;
91+
const [{ allowToRejectPromises }] = options;
92+
93+
if (!allowToRejectPromises) {
94+
return { context, descriptors: [{ node, messageId: "generic" }] };
95+
}
96+
97+
if (isInPromiseHandlerFunction(node, context)) {
98+
return { context, descriptors: [] };
99+
}
100+
101+
const enclosingFunction = getEnclosingFunction(node);
102+
if (enclosingFunction?.async !== true) {
103+
return { context, descriptors: [{ node, messageId: "generic" }] };
104+
}
88105

89-
if (!allowInAsyncFunctions || !isInFunctionBody(node, true)) {
106+
const enclosingTryStatement = getEnclosingTryStatement(node);
107+
if (
108+
!(
109+
enclosingTryStatement === null ||
110+
getEnclosingFunction(enclosingTryStatement) !== enclosingFunction ||
111+
enclosingTryStatement.handler === null
112+
)
113+
) {
90114
return { context, descriptors: [{ node, messageId: "generic" }] };
91115
}
92116

src/utils/tree.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { type RuleContext } from "@typescript-eslint/utils/ts-eslint";
44

55
import typescript from "#/conditional-imports/typescript";
66

7-
import { type BaseOptions } from "./rule";
7+
import { type BaseOptions, getTypeOfNode } from "./rule";
88
import {
99
isBlockStatement,
1010
isCallExpression,
@@ -18,6 +18,7 @@ import {
1818
isMethodDefinition,
1919
isObjectExpression,
2020
isProgram,
21+
isPromiseType,
2122
isProperty,
2223
isTSInterfaceBody,
2324
isTSInterfaceHeritage,
@@ -117,6 +118,30 @@ export function isInReadonly(node: TSESTree.Node): boolean {
117118
return getReadonly(node) !== null;
118119
}
119120

121+
/**
122+
* Test if the given node is in a handler function callback of a promise.
123+
*/
124+
export function isInPromiseHandlerFunction<
125+
Context extends RuleContext<string, BaseOptions>,
126+
>(node: TSESTree.Node, context: Context): boolean {
127+
const functionNode = getAncestorOfType(
128+
(n, c): n is TSESTree.FunctionLike => isFunctionLike(n) && n.body === c,
129+
node,
130+
);
131+
132+
if (
133+
functionNode === null ||
134+
!isCallExpression(functionNode.parent) ||
135+
!isMemberExpression(functionNode.parent.callee) ||
136+
!isIdentifier(functionNode.parent.callee.property)
137+
) {
138+
return false;
139+
}
140+
141+
const objectType = getTypeOfNode(functionNode.parent.callee.object, context);
142+
return isPromiseType(objectType);
143+
}
144+
120145
/**
121146
* Test if the given node is shallowly inside a `Readonly<{...}>`.
122147
*/

0 commit comments

Comments
 (0)