@typescript-eslint/no-unnecessary-condition

Disallow conditions that the types say are always true or always false

has suggestionsneeds types

Also called typescript-eslint/no-unnecessary-condition, typescript/no-unnecessary-condition

If the types say user is always an object, if (user), user?.name and user ?? fallback test for something that can't happen. Either the check is dead code, or the type is wrong and hides a real case.

The rule checks if, loops, ? :, &&, ||, ??, ?., switch cases, comparisons with null and undefined, comparisons of two literal types, and the callbacks of array methods such as filter. Values of type any or unknown and type parameters without a constraint are never reported.

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-unnecessary-condition" = "error" # or "warn"

Every option, set to its default:

bunfig.toml
[lint.rules]
"@typescript-eslint/no-unnecessary-condition" = ["error", {
  # true, false, "always", "never" or "only-allowed-literals"
  allowConstantLoopConditions = "never",
  checkTypePredicates = false,
  allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing = false,
}]

Examples#

example.ts3 problems
interface User {
  name: string;
  tags: string[];
}

function label(user: User) {
  if (user.tags) {
    console.log(user.tags.length);
  }
  const name = user.name ?? "anonymous";
  return user?.name + name;
}
example.ts2 problems
function greet(name: string) {
  return name === undefined ? "Hello" : `Hello, ${name}`;
}

const users = [{ name: "Ada" }, { name: "Alan" }];
const named = users.filter(user => user);
example.tsNo problems
interface User {
  name?: string;
  tags: string[];
}

function label(user: User | undefined) {
  if (user?.tags.length) {
    console.log(user.tags.length);
  }
  return user?.name ?? "anonymous";
}

An element read from an array by index isn't reported, because the index can be out of range even though the type doesn't say so:

example.tsNo problems
function firstId(items: { id: number }[]) {
  if (!items[0]) {
    return -1;
  }
  return items[0].id;
}

Options#

OptionDefaultWhat it does
allowConstantLoopConditions"never"Which constant loop conditions are allowed. See below.
checkTypePredicatesfalseAlso report a call to a type guard or an assertion function if the argument already has that type.
allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoingfalseWithout strictNullChecks in tsconfig.json, the rule can't tell what can be null, and it reports that once for each file. Set this to true to turn that report off.

allowConstantLoopConditions takes one of these:

ValueWhat it does
"never" or falseReport every constant loop condition, including while (true).
"always" or trueAllow any loop condition of type true.
"only-allowed-literals"Allow only true, false, 0 and 1 written in the condition itself.

Not reported with "only-allowed-literals":

example.tswith { "allowConstantLoopConditions": "only-allowed-literals" }No problems
const queue = [1, 2, 3];

while (true) {
  if (queue.pop() === undefined) break;
}

Reported with checkTypePredicates:

example.tswith { "checkTypePredicates": true }1 problem
declare function isString(value: unknown): value is string;

function print(name: string) {
  if (isString(name)) {
    console.log(name);
  }
}

When not to use it#

The rule trusts your types. If much of your data comes from outside and is typed without being validated, such as the result of JSON.parse cast to an interface, it reports defensive checks you want to keep.