Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

@cancjs/eslint-plugin

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.

Install

npm install --save-dev @cancjs/eslint-plugin

Peer 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.

Quick start

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.

Typed setup

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.

Presets

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.

Settings

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 in cancelableTypeNames and declared in one of sources. Default ['CancelablePromise'].
  • lazyTypeNames: type names treated as lazy cancelable promises. Default ['LazyPromise'].
  • structuralDetection: default false. When true, any type with a callable then and a callable cancel counts as a cancelable promise.
  • boundaryFunctions: helper names that end a cancelable flow on purpose, such as catchCancel and suppressCancel. Your own helpers are added to the built-in list.

Structural detection trade-off

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.

Interop

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.

Native entry

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.

Coroutine factories

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.

Guidelines

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).

Rules

πŸ’Ό Configurations enabled in.
⚠️ Configurations set to warn 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. βœ… πŸ”’

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages