Code.mp4
Just as Java’s throws keyword does, enforcing the use of JSDoc’s @throws
tag 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".
- 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.
For functions that propagate exceptions to the caller because they didn’t handle exceptions, this plugin enforces the use of a @throws
comment.
// ❌ 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.
// ❌ 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.
// ✅ 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>
.
/**
* @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 directory and rules below.
Install dependencies
# 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.
# 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
{
// ...
+ "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.
// e.g. Promise.reject()
interface PromiseConstructor {
/**
* @throws {Promise<unknown>}
*/
reject(reason?: any): Promise<unknown>;
}
Create eslint.config.mjs
// @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
{
"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 and typescript-eslint 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
.
Check out typescript-eslint
for more information if you having an issue with configuring.