ESLint rules that keep cancellation intact in code that uses @cancjs/*.
Cancellation is viral. A cancelable promise only stays cancelable if every consumer passes the cancel handle along. When a plain async function awaits it, or a native Promise.all wraps it, nobody holds the handle anymore. The work keeps running after the cancel, and the type system does not complain.
This plugin finds those spots. It reports cancelable promises consumed by code that cannot cancel them, executors that start work they never stop, catch blocks that swallow a cancellation, and coroutines that mix helper styles. Each message names the problem, says what goes wrong at runtime and shows the fix.
The blessed idioms from the @cancjs/* docs are never flagged by the recommended preset.
npm install --save-dev @cancjs/eslint-pluginPeer dependencies: eslint 9 or 10. Rules that read types also need @typescript-eslint/parser 8. The plugin needs typescript at runtime; npm 7 and later install it together with the parser. Node versions are as declared in engines.
Add the preset to your flat config. The parser line is only needed for TypeScript files.
// readme-test
import canc from '@cancjs/eslint-plugin';
import tsParser from '@typescript-eslint/parser';
export default [
{ files: ['**/*.ts'], languageOptions: { parser: tsParser } },
...canc.configs.recommended,
];In a plain JavaScript project, spread canc.configs.recommended and nothing else.
Some rules compare types, for example to tell a cancelable promise from a plain one. Turn on projectService to give them type information.
// readme-test
import canc from '@cancjs/eslint-plugin';
import tsParser from '@typescript-eslint/parser';
export default [
{
files: ['**/*.ts'],
languageOptions: { parser: tsParser, parserOptions: { projectService: true } },
},
...canc.configs.recommended,
];Type-aware rules are silent without type information. They do not guess. The one exception is prefer-canc-throw, which still reports without type information. The rules list below marks them with a type information icon.
The icons in the rules list show which preset enables each rule. This table says what each preset is for.
| Preset | Contents |
|---|---|
recommended |
Rules that never flag the blessed idioms. Some rules are set to warn. |
strict |
recommended with warnings promoted to errors, plus the stricter rules. |
stylistic |
Style-only rules, set to warn. |
interopTypescriptEslint |
Settings for typescript-eslint rules that would otherwise fight these idioms. |
interopPromise |
Turns off eslint-plugin-promise rules that conflict with cancelable promises. |
interopUnicorn |
Turns off eslint-plugin-unicorn rules that conflict with cancelable promises. |
The same presets are exported from @cancjs/eslint-plugin/native for the rules that apply to native-twin consumers.
Shared settings live under settings.canc. Every key is optional. List keys add to the built-in values and never replace them.
// readme-test
import canc from '@cancjs/eslint-plugin';
import tsParser from '@typescript-eslint/parser';
export default [
{
files: ['**/*.ts'],
languageOptions: { parser: tsParser },
settings: {
canc: {
sources: ['@acme/cancel-utils'],
cancelableTypeNames: ['AcmeTask'],
},
},
},
...canc.configs.recommended,
];sources: module specifiers that export cancelable APIs. All@cancjs/*packages are built in. Add your own wrappers here.cancelableTypeNames: type names treated as cancelable promises. A type counts only when it is named incancelableTypeNamesand declared in one ofsources. Default['CancelablePromise'].lazyTypeNames: type names treated as lazy cancelable promises. Default['LazyPromise'].structuralDetection: defaultfalse. Whentrue, any type with a callablethenand a callablecancelcounts as a cancelable promise.boundaryFunctions: helper names that end a cancelable flow on purpose, such ascatchCancelandsuppressCancel. Your own helpers are added to the built-in list.
By default a type counts as cancelable only when it is named in cancelableTypeNames and declared in one of sources. Turning on structuralDetection widens that to every type with then and cancel, including types from libraries you do not control. That catches wrapper types you did not list.
The wider match feeds the rules that ask whether a value is cancelable and whose advice holds for any such type: no-cancelability-erasure, no-cancellation-gap, no-await-cancelable-in-async and prefer-canc-combinators. Rules whose advice or fix assumes the canc API (no-throw-in-chain-callback, prefer-canceled-property, no-async-loop-callback, no-swallowed-cancel, prefer-is-cancel-error and the failure-type rules) keep matching canc types only.
The plugin ships configs that settle conflicts with typescript-eslint, eslint-plugin-promise and eslint-plugin-unicorn. They hold rule entries only and add no dependency. See the interop guide.
Packages that use the native-twin builds import from @cancjs/eslint-plugin/native. It exports the same three presets, limited to the rules that apply there: must-consume-lazy, no-abort-listener-leak, no-internal-imports and valid-canc-params. It carries the presets only and has no interop configs.
Rules that check coroutine helpers accept options that add your own factory functions or replace the built-in ones. The options are described on the rule pages, for example exhaustive-catch. The full list of rules follows.
Plugin rules target user domain code (including examples/ and application packages), not @cancjs/* core package internals (which use explicit /* eslint-disable */ comments where implementing interop primitives like toolbox createAbortSignal).
πΌ Configurations enabled in.
β
Set in the recommended configuration.
π Set in the strict configuration.
π¨ Set in the stylistic configuration.
π§ Automatically fixable by the --fix CLI option.
π‘ Manually fixable by editor suggestions.
π Requires type information.
| NameΒ Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β Β | Description | πΌ | π§ | π‘ | π | |
|---|---|---|---|---|---|---|
| canc-throw-tail-position | Require 'return yield* canc.throw(...)' in tail position so following code is unreachable. | β π | π§ | π‘ | ||
| executor-cancel-wiring | Require cancelable promise executors to wire cancellation to underlying operations. | π | ||||
| executor-param-names | Enforce standard parameter names for cancelable promise executors. | π¨ | ||||
| exhaustive-catch | Require coroutine catch blocks to handle or rethrow all declared failure types. | π | β | π‘ | π | |
| handle-cancel-first | Require cancel handlers to be registered synchronously at the top of the executor. | β π | ||||
| must-consume-lazy | Ensure LazyPromise instances are consumed rather than dropped. | β π | ||||
| no-abort-listener-leak | Require removing abort listeners on all code paths or using { once: true } to prevent leaks. | π | ||||
| no-async-executor | Disallow async functions as executor in CancelablePromise or LazyPromise. | β π | ||||
| no-async-loop-callback | Disallow async callbacks in cancForAwait and cancGenForAwait loops. | π | β | π‘ | ||
| no-await-cancelable-in-async | Disallow awaiting cancelable promises in plain async functions | β π | π | |||
| no-cancelability-erasure | Disallow passing or returning cancelable promises where plain promises are expected | β π | π | |||
| no-cancellation-gap | Disallow non-cancelable promise steps inside cancelable flows and chains. | π | ||||
| no-coroutine-dialect-mismatch | Disallow mixing coroutine helper dialects inside coroutine bodies. | β π | ||||
| no-executor-return | Disallow returning values from CancelablePromise or LazyPromise executors. | β π | ||||
| no-failure-widening | Disallow casts and type annotations that drop declared failure types. | π | β | π‘ | π | |
| no-internal-imports | Disallow imports from non-public internal package subpaths in @cancjs. | β π | π§ | π‘ | ||
| no-keyword-alias-import | Disallow importing keyword aliases from @cancjs/coroutine and @cancjs/coroutine/gen directly as named bindings; prefer canonical names or namespace imports. | π | β | π§ | ||
| no-multistep-executor | Disallow starting multiple async operations in one executor. | π | β | |||
| no-swallowed-cancel | Disallow catch handlers on cancelable flows that swallow the cancellation | π | β | π‘ | ||
| no-throw-in-chain-callback | Require returning CancelablePromise.fail() instead of throwing inside chain callbacks on cancelable promises. | π | β | π‘ | π | |
| no-transform-in-executor | Disallow transforming data before resolving in executor. | π¨ | π‘ | |||
| no-unstarred-cancawait | Require yield* on cancAwait calls and disallow unused statement-position cancAwait calls. | β π | π§ | |||
| no-yield-await-in-loop | Disallow serialized yield* cancAwait in loops where steps appear independent. | π | ||||
| prefer-canc-combinators | Prefer CancelablePromise combinators over native Promise combinators when inputs are cancelable | β π | π§ | π‘ | π | |
| prefer-canc-throw | Require yield* canc.throw(...) instead of bare throw in coroutines. | π | β | π‘ | π | |
| prefer-cancawait | Prefer yield* cancAwait(...) over bare yield inside cancAsync coroutines. | π | β | π§ | ||
| prefer-cancel-aware-wrapper | Prefer cancelify or promisify over hand-rolled signal threading and finally abort. | π | ||||
| prefer-canceled-property | Prefer 'canceled' over deprecated 'isCanceled' property on cancelables. | π | β | π§ | π‘ | |
| prefer-is-cancel-error | Prefer brand-checking functions over instanceof or name checks for cancellation errors | β π | π§ | π‘ | π | |
| prefer-signal-accessor | Prefer 'signal' accessor over deprecated 'getSignal()' on executor context. | π | β | π§ | π‘ | |
| prefer-signal-option | Prefer passing signal as constructor option over manual wiring or pre-abort check inside executor. | π | β | π‘ | ||
| prefer-signal-over-controller | Prefer passing AbortSignal instead of AbortController. | π | β | π‘ | ||
| require-error-discriminant | Require declared error classes in failure sets to have a discriminant property. | π | β | π‘ | π | |
| require-loop-advance | Require loops over a cancForAwait handle or sugar form to advance the loop | π | β | π‘ | ||
| require-signal-forwarding | Require AbortController signals to be forwarded to operations when cancel aborts the controller. | π | β | |||
| valid-canc-params | Enforce valid parameter count and shapes for cancelable promise methods. | β π | ||||
| valid-cancasync | Ensure coroutines are driven by valid generator functions with proper type annotations. | β π |