@typescript-eslint/no-redundant-type-constituents

Disallow members of unions and intersections that change nothing or swallow the rest

needs types

Also called typescript-eslint/no-redundant-type-constituents, typescript/no-redundant-type-constituents

Some members of a union or an intersection don't do what they look like they do. "draft" | string is string, so the editor no longer suggests "draft", and a typo in it is accepted. User | any is any, so nothing about it is checked.

WrittenWhat TypeScript makes of it
T | any, T & anyany
T | unknownunknown
T | never, T & unknownT
T & nevernever
"draft" | string, 0 | number, true | booleanThe wider type.
"draft" & string, 0 & numberThe literal.

T | never is allowed as the return type of a function.

bun lint --type-aware turns this rule on in TypeScript files.

Configure#

Off by default. This rule needs types, so turning it on makes bun lint type check your code first.

bunfig.toml
[lint.rules]
"@typescript-eslint/no-redundant-type-constituents" = "error" # or "warn"

Examples#

example.ts5 problems
type Status = "draft" | "published" | string;

type Payload = { id: number } | any;

type Result = string | never;

function send(body: unknown | string) {}
example.tsNo problems
type Status = "draft" | "published";

type Payload = { id: number } | string;

type Result = string;

function send(body: unknown) {}

function load(id: number): string | never {
  throw new Error(`No item ${id}`);
}

The rule looks through type aliases:

example.ts1 problem
type Id = number;

type Key = Id | 0;

A type that can't be resolved acts as any, so it's reported too. Here ./missing doesn't exist:

example.ts1 problem
import type { User } from "./missing";

type Owner = User | string;