@typescript-eslint/no-unnecessary-condition
Disallow conditions that the types say are always true or always false
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.
[lint.rules]
"@typescript-eslint/no-unnecessary-condition" = "error" # or "warn"Every option, set to its default:
[lint.rules]
"@typescript-eslint/no-unnecessary-condition" = ["error", {
# true, false, "always", "never" or "only-allowed-literals"
allowConstantLoopConditions = "never",
checkTypePredicates = false,
allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing = false,
}]Examples#
interface User {
name: string;
tags: string[];
}
function label(user: User) {
if (user.tagsUnnecessary conditional, value is always truthy.) {
console.log(user.tags.length);
}
const name = user.nameUnnecessary conditional, expected left-hand side of ?? operator to be possibly null or undefined. ?? "anonymous";
return user?.Unnecessary optional chain on a non-nullish value.name + name;
}function greet(name: string) {
return name === undefinedUnnecessary conditional, the types have no overlap. ? "Hello" : `Hello, ${name}`;
}
const users = [{ name: "Ada" }, { name: "Alan" }];
const named = users.filter(user => userUnnecessary conditional, value is always truthy.);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:
function firstId(items: { id: number }[]) {
if (!items[0]) {
return -1;
}
return items[0].id;
}Options#
| Option | Default | What it does |
|---|---|---|
allowConstantLoopConditions | "never" | Which constant loop conditions are allowed. See below. |
checkTypePredicates | false | Also report a call to a type guard or an assertion function if the argument already has that type. |
allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing | false | Without 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:
| Value | What it does |
|---|---|
"never" or false | Report every constant loop condition, including while (true). |
"always" or true | Allow 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":
{ "allowConstantLoopConditions": "only-allowed-literals" }No problemsconst queue = [1, 2, 3];
while (true) {
if (queue.pop() === undefined) break;
}Reported with checkTypePredicates:
{ "checkTypePredicates": true }1 problemdeclare function isString(value: unknown): value is string;
function print(name: string) {
if (isString(nameUnnecessary conditional, expression already has the type being checked by the type guard.)) {
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.