Skip to content

Commit 02fdffd

Browse files
feat(throw-error)!: stop linting throw statements (#22)
- Resolves #21 . Removes functionality that was linting regular js `throw` statements. Users should prefer https://typescript-eslint.io/rules/only-throw-error/ instead. - Added options to disallow `any` and `unknown` if users want stricter checking. - Moved the report node to the return body of the factory instead of the whole function itself. - Reworked tests to prioritize testing passing a factory function to `throwError` instead of a raw value, because rxjs has deprecated the latter.
1 parent 9ecd973 commit 02fdffd

File tree

4 files changed

+201
-115
lines changed

4 files changed

+201
-115
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,6 @@ The package includes the following rules.
106106
| [no-unsafe-takeuntil](docs/rules/no-unsafe-takeuntil.md) | Disallow applying operators after `takeUntil`. || | | 💭 | |
107107
| [prefer-observer](docs/rules/prefer-observer.md) | Disallow passing separate handlers to `subscribe` and `tap`. | | 🔧 | 💡 | 💭 | |
108108
| [suffix-subjects](docs/rules/suffix-subjects.md) | Enforce the use of a suffix in subject identifiers. | | | | 💭 | |
109-
| [throw-error](docs/rules/throw-error.md) | Enforce passing only `Error` values to error notifications. | | | | 💭 | |
109+
| [throw-error](docs/rules/throw-error.md) | Enforce passing only `Error` values to `throwError`. | | | | 💭 | |
110110

111111
<!-- end auto-generated rules list -->

docs/rules/throw-error.md

Lines changed: 14 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,38 @@
1-
# Enforce passing only `Error` values to error notifications (`rxjs-x/throw-error`)
1+
# Enforce passing only `Error` values to `throwError` (`rxjs-x/throw-error`)
22

33
💭 This rule requires [type information](https://typescript-eslint.io/linting/typed-linting).
44

55
<!-- end auto-generated rule header -->
66

7-
This rule forbids throwing values that are neither `Error` nor `DOMException` instances.
7+
This rule forbids passing values that are not `Error` objects to `throwError`.
8+
It's similar to the typescript-eslint [`only-throw-error`](https://typescript-eslint.io/rules/only-throw-error/) rule,
9+
but is for the `throwError` Observable creation function - not `throw` statements.
810

911
## Rule details
1012

1113
Examples of **incorrect** code for this rule:
1214

13-
```ts
14-
throw "Kaboom!";
15-
```
16-
1715
```ts
1816
import { throwError } from "rxjs";
19-
throwError("Kaboom!");
20-
```
2117

22-
```ts
23-
import { throwError } from "rxjs";
2418
throwError(() => "Kaboom!");
2519
```
2620

2721
Examples of **correct** code for this rule:
2822

2923
```ts
30-
throw new Error("Kaboom!");
31-
```
24+
import { throwError } from "rxjs";
3225

33-
```ts
34-
throw new RangeError("Kaboom!");
26+
throwError(() => new Error("Kaboom!"));
3527
```
3628

37-
```ts
38-
throw new DOMException("Kaboom!");
39-
```
29+
## Options
4030

41-
```ts
42-
import { throwError } from "rxjs";
43-
throwError(new Error("Kaboom!"));
44-
```
31+
<!-- begin auto-generated rule options list -->
4532

46-
```ts
47-
import { throwError } from "rxjs";
48-
throwError(() => new Error("Kaboom!"));
49-
```
33+
| Name | Description | Type | Default |
34+
| :--------------------- | :---------------------------------------------------------- | :------ | :------ |
35+
| `allowThrowingAny` | Whether to always allow throwing values typed as `any`. | Boolean | `true` |
36+
| `allowThrowingUnknown` | Whether to always allow throwing values typed as `unknown`. | Boolean | `true` |
37+
38+
<!-- end auto-generated rule options list -->

src/rules/throw-error.ts

Lines changed: 53 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -4,54 +4,86 @@ import ts from 'typescript';
44
import { couldBeFunction, couldBeType, getTypeServices } from '../etc';
55
import { ruleCreator } from '../utils';
66

7+
const defaultOptions: readonly {
8+
allowThrowingAny?: boolean;
9+
allowThrowingUnknown?: boolean;
10+
}[] = [];
11+
712
export const throwErrorRule = ruleCreator({
8-
defaultOptions: [],
13+
defaultOptions,
914
meta: {
1015
docs: {
1116
description:
12-
'Enforce passing only `Error` values to error notifications.',
17+
'Enforce passing only `Error` values to `throwError`.',
1318
requiresTypeChecking: true,
1419
},
1520
messages: {
16-
forbidden: 'Passing non-Error values are forbidden.',
21+
forbidden: 'Passing non-Error values is forbidden.',
1722
},
18-
schema: [],
23+
schema: [
24+
{
25+
properties: {
26+
allowThrowingAny: { type: 'boolean', default: true, description: 'Whether to always allow throwing values typed as `any`.' },
27+
allowThrowingUnknown: { type: 'boolean', default: true, description: 'Whether to always allow throwing values typed as `unknown`.' },
28+
},
29+
type: 'object',
30+
},
31+
],
1932
type: 'problem',
2033
},
2134
name: 'throw-error',
2235
create: (context) => {
2336
const { esTreeNodeToTSNodeMap, program, getTypeAtLocation } = ESLintUtils.getParserServices(context);
2437
const { couldBeObservable } = getTypeServices(context);
38+
const [config = {}] = context.options;
39+
const { allowThrowingAny = true, allowThrowingUnknown = true } = config;
2540

26-
function checkNode(node: es.Node) {
41+
function checkThrowArgument(node: es.Node) {
2742
let type = getTypeAtLocation(node);
43+
let reportNode = node;
44+
2845
if (couldBeFunction(type)) {
46+
reportNode = (node as es.ArrowFunctionExpression).body ?? node;
47+
2948
const tsNode = esTreeNodeToTSNodeMap.get(node);
3049
const annotation = (tsNode as ts.ArrowFunction).type;
3150
const body = (tsNode as ts.ArrowFunction).body;
3251
type = program.getTypeChecker().getTypeAtLocation(annotation ?? body);
3352
}
34-
if (
35-
!tsutils.isIntrinsicAnyType(type)
36-
&& !tsutils.isIntrinsicUnknownType(type)
37-
&& !couldBeType(type, /^(Error|DOMException)$/)
38-
) {
39-
context.report({
40-
messageId: 'forbidden',
41-
node,
42-
});
53+
54+
if (allowThrowingAny && tsutils.isIntrinsicAnyType(type)) {
55+
return;
56+
}
57+
58+
if (allowThrowingUnknown && tsutils.isIntrinsicUnknownType(type)) {
59+
return;
60+
}
61+
62+
if (couldBeType(type, /^Error$/)) {
63+
return;
64+
}
65+
66+
context.report({
67+
messageId: 'forbidden',
68+
node: reportNode,
69+
});
70+
}
71+
72+
function checkNode(node: es.CallExpression) {
73+
if (couldBeObservable(node)) {
74+
const [arg] = node.arguments;
75+
if (arg) {
76+
checkThrowArgument(arg);
77+
}
4378
}
4479
}
4580

4681
return {
47-
'ThrowStatement > *': checkNode,
4882
'CallExpression[callee.name=\'throwError\']': (node: es.CallExpression) => {
49-
if (couldBeObservable(node)) {
50-
const [arg] = node.arguments;
51-
if (arg) {
52-
checkNode(arg);
53-
}
54-
}
83+
checkNode(node);
84+
},
85+
'CallExpression[callee.property.name=\'throwError\']': (node: es.CallExpression) => {
86+
checkNode(node);
5587
},
5688
};
5789
},

0 commit comments

Comments
 (0)