TypeScript Meaningless Intersection
ID |
javascript.ts_meaningless_intersection |
Severity |
high |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Reliability |
Language |
JavaScript |
Tags |
reliability, types, typescript |
Description
Reports a TypeScript intersection one of whose components is any, never or unknown, each of which decides the whole intersection on its own and makes the rest of it dead text.
Only the intersection operator is judged. A union with any is a related defect, spelled differently and with a different fix, and a bare any on its own belongs to javascript.ts_no_explicit_any. A component repeated inside an intersection is reported by javascript.ts_duplicate_union_component, which walks the same spine, and is deliberately not reported twice here.
Rationale
// Bad
type Loose = User & any; // the result is any
type Impossible = User & never; // the result is never
type Opaque = unknown & User; // the result is User
// Good
type Both = User & Admin;
-
anyabsorbs the intersection. The annotation looks like a narrowedUserwhile every value passes and every member access is unchecked — the worst of the three, because it turns type checking off exactly where the author was trying to turn it up. -
neverempties it. No value can ever have the type, so every call site that tries to supply one fails, and the error names the argument rather than the declaration. -
unknownvanishes from it. Harmless, and a sign of a merge or a refactor that left a placeholder behind. -
All three arrive from an edit rather than from a decision: a generic parameter defaulted to
any, a branch of a conditional type that resolved tonever, a placeholder written while the real type was still being worked out.
Remediation
Drop the component that decides the type, or replace it with the constraint that was meant.
// Good — the intersection says what it constrains
type Privileged = User & Admin;
// Good — a generic parameter, constrained rather than widened
function merge<T extends object>(user: User & T): User & T {
return user;
}
// Good — when the value really is unconstrained, say so once
function log(value: unknown): void {
console.log(value);
}