linter: Add a type-aware rule to detect usage of never values
It would be useful to add a TypeScript type-aware rule that reports when a value whose inferred type has been narrowed to never is still being used.
Since never is assignable to every type in TypeScript, this remains valid:
consume(value); // `value` is `never`
This can hide dead code or logic mistakes, especially when narrowing happens through predicates and functional abstractions.
Example
A concrete case can happen inside a pipeline:
const result = pipe(
right("success"),
when(
isLeft,
(value) => {
value satisfies never;
consume(value); // accepted by TypeScript
return value;
},
),
);
Here, right("success") produces a value already known to be a Right.
The isLeft predicate narrows its input using:
Extract<Input, Left>
In this context:
Extract<Right, Left>
resolves to never.
TypeScript therefore correctly infers that value is never, which can be verified with:
value satisfies never;
However, consume(value) is still accepted because never is assignable to any type.
The callback can never be executed at runtime, so this is effectively dead code.
TypeScript Playground reproduction
Why no-unnecessary-condition is not enough
typescript/no-unnecessary-condition works well when the condition is directly visible to the linter.
In this example, however, the control flow is encapsulated inside a function such as when.
The linter does not need, and probably should not need, to understand the semantics of when, pipe, match, or similar abstractions.
The type checker already has the relevant information: the value passed to the callback is never.
Proposed behavior
A rule such as:
typescript/no-never-value
could report usages of expressions whose resolved type is never:
consume(value);
// ^ error: value has type `never`
const result = value;
// ^ error
array.push(value);
// ^ error
The goal would not be to forbid the never type itself.
Intentional uses such as exhaustiveness helpers should remain valid:
function assertNever(value: never): never {
throw new Error("Unexpected value");
}
Motivation
This rule could help detect:
- dead code hidden behind functional abstractions;
- predicates applied to types they can no longer match;
- branches made impossible after refactoring;
- narrowing mistakes that are difficult to notice;
- cases that
no-unnecessary-conditioncannot easily analyze.
The signal is relatively simple: a value that TypeScript already considers impossible (never) is still being consumed as a regular value.
AI assistance disclosure
This issue was drafted with the assistance of AI, then manually reviewed and edited before submission.
The goal of using AI here was only to help structure and phrase the proposal. The technical reasoning, example, and intent were reviewed by the author, and this issue is not intended to contribute to low-quality AI-generated issue spam or "AI slop".
Source: oxc-project/oxc