eslint-plugin-explicit-exceptions
Version:
ESLint rules for explicitly handling exceptions
217 lines (186 loc) • 6.37 kB
Markdown
# eslint-plugin-explicit-exceptions
[](https://www.npmjs.com/package/eslint-plugin-explicit-exceptions)
[](https://github.com/Xvezda/eslint-plugin-explicit-exceptions/actions/workflows/test.yml)
[](https://github.com/Xvezda/eslint-plugin-explicit-exceptions/actions/workflows/test.yml)
https://github.com/user-attachments/assets/4a833442-b8a5-462f-abeb-a28bd0e5863f
Just as [Java’s throws keyword](https://dev.java/learn/exceptions/throwing/) does, enforcing the use of [JSDoc’s `@throws` tag](https://jsdoc.app/tags-throws) to explicitly specify which exceptions a function can throw to solve unpredictable propagation of exceptions happening which also known as a [JavaScript's "hidden exceptions"](https://www.youtube.com/watch?v=3iWoNJbGO2U).
## Features
- Reports and provides fixes for throwable functions that are not annotated with `@throws`.
- Reports and provides fixes for async functions and Promise rejections.
- Verifies that the exception types match the documented types.
## Examples
For functions that propagate exceptions to the caller because they didn’t handle exceptions, this plugin enforces the use of a `@throws` comment.
```javascript
// ❌ Error - no `@throws` tag
function foo() {
throw new RangeError();
}
// ✅ OK - no exception propagation
function bar() {
try {
throw new TypeError();
} catch {}
}
// ❌ Error
function baz() {
maybeThrow();
}
// ✅ OK
/** @throws {Error} */
function qux() {
maybeThrow();
}
```
It also leverages these comments for type checking, helping ensure that errors are handled safely.
```javascript
// ❌ Error - type mismatch
/** @throws {TypeError} */
function foo() {
throw new RangeError();
}
// ✅ OK
/**
* @throws {TypeError}
* @throws {RangeError}
*/
function bar() {
maybeThrowTypeError();
maybeThrowRangeError();
}
// ✅ OK
/** @throws {number} */
function baz() {
throw 42;
}
// ✅ OK
/**
* @throws {"error"}
*/
function qux() {
throw 'error';
}
```
For error classes, since TypeScript uses duck typing for type checking, this plugin treats inherited error classes as different types.
However, documenting a common parent class is permitted.
```javascript
// ✅ OK
/** @throws {RangeError | TypeError} */
function foo() {
maybeThrowRangeError();
maybeThrowTypeError();
}
// ✅ OK
/** @throws {Error} */
function bar() {
maybeThrowRangeError();
maybeThrowTypeError();
}
```
To clearly distinguish between a synchronous throw and an asynchronous promise rejection, this plugin requires that promise rejections be documented in the special form of `Promise<Error>`.
```javascript
/**
* @throws {Promise<Error>}
*/
function foo() {
return new Promise((resolve, reject) => reject(new Error()));
}
/**
* @throws {Promise<TypeError | RangeError>}
*/
async function bar() {
if (randomBool()) {
throw new TypeError(); // This becomes promise rejection
} else {
return maybeThrowRangeError();
}
}
```
For more examples, check out [examples](https://github.com/Xvezda/eslint-plugin-explicit-exceptions/tree/master/examples) directory and rules below.
## Rules
- [`no-undocumented-throws`](https://github.com/Xvezda/eslint-plugin-explicit-exceptions/blob/master/docs/rules/no-undocumented-throws.md)
- [`check-throws-tag-type`](https://github.com/Xvezda/eslint-plugin-explicit-exceptions/blob/master/docs/rules/check-throws-tag-type.md)
## Usage
Install dependencies
```sh
# https://typescript-eslint.io/getting-started/#step-1-installation
npm install --save-dev eslint @eslint/js typescript typescript-eslint
```
Install plugin
```
npm install --save-dev eslint-plugin-explicit-exceptions
```
> [!WARNING]
> > These packages are experimental.
>
> Install custom types for better built-in, libraries lint support.
> ```sh
> # For @types/*, i.e. @types/node
> npm install --save-dev @types-with-exceptions/node
> # For built-in lib replacement
> npm install --save-dev @types-with-exceptions/lib
> ```
> `tsconfig.json`
> ```diff
> {
> // ...
> + "typeRoots": [
> + "node_modules/@types",
> + "node_modules/@types-with-exceptions"
> + ],
> + "libReplacement": true,
> // ...
> }
> ```
> Visit https://github.com/Xvezda/types-with-exceptions to see more.
> [!NOTE]
> I'm working on documentation for frequently used APIs.
> https://github.com/Xvezda/types-with-exceptions
> However, it's still not usable. As a temporary workaround, you can extend the type interfaces in your own type definitions.
> ```typescript
> // e.g. Promise.reject()
> interface PromiseConstructor {
> /**
> * @throws {Promise<unknown>}
> */
> reject(reason?: any): Promise<unknown>;
> }
> ```
Create `eslint.config.mjs`
```javascript
// @ts-check
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
import explicitExceptionsLint from 'eslint-plugin-explicit-exceptions';
export default tseslint.config(
eslint.configs.recommended,
tseslint.configs.recommended,
explicitExceptionsLint.configs.recommendedTypeChecked,
{
languageOptions: {
parserOptions: {
projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
},
);
```
For legacy, `.eslintrc.json`
```json
{
"extends": [
"eslint:recommended",
"plugin:eslint-plugin-explicit-exceptions/recommended-type-checked-legacy"
],
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"],
"parserOptions": {
"projectService": true
},
"root": true
}
```
This project uses [TypeScript](https://www.typescriptlang.org/) and [typescript-eslint](https://typescript-eslint.io/) to leverage type information. To prevent errors or bugs caused by incorrect type data, it is recommended to [set the `tsconfig.json` `"strict"` option to `true`](https://www.typescriptlang.org/tsconfig/#strict).
Check out [`typescript-eslint`](https://typescript-eslint.io/getting-started/) for more information if you having an issue with configuring.
## License
[MIT License](https://github.com/Xvezda/eslint-plugin-explicit-exceptions/blob/master/LICENSE)