Linter · Rules
@typescript-eslint/no-unnecessary-condition
Disallow conditions that the types say are always true or always false
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
Reported:
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;
}
function greet(name: string) {
return name === undefined ? "Hello" : `Hello, ${name}`;
}
const users = [{ name: "Ada" }, { name: "Alan" }];
const named = users.filter(user => user);
Not reported:
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":
const queue = [1, 2, 3];
while (true) {
if (queue.pop() === undefined) break;
}
Reported with checkTypePredicates:
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.