@typescript-eslint/eslint-plugin
Version:
TypeScript plugin for ESLint
294 lines (210 loc) • 9.02 kB
text/mdx
---
description: 'Disallow conditionals where the type is always truthy or always falsy.'
---
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
> 🛑 This file is source code, not the primary documentation location! 🛑
>
> See **https://typescript-eslint.io/rules/no-unnecessary-condition** for documentation.
Any expression being used as a condition must be able to evaluate as truthy or falsy in order to be considered "necessary".
Conversely, any expression that always evaluates to truthy or always evaluates to falsy, as determined by the type of the expression, is considered unnecessary and will be flagged by this rule.
The following expressions are checked:
- Arguments to the `&&`, `||` and `?:` (ternary) operators
- Conditions for `if`, `for`, `while`, and `do-while` statements
- `case`s in `switch` statements
- Base values of optional chain expressions
## Examples
<Tabs>
<TabItem value="❌ Incorrect">
```ts
function head<T>(items: T[]) {
// items can never be nullable, so this is unnecessary
if (items) {
return items[0].toUpperCase();
}
}
function foo(arg: 'bar' | 'baz') {
// arg is never nullable or empty string, so this is unnecessary
if (arg) {
}
}
function bar<T>(arg: string) {
// arg can never be nullish, so ?. is unnecessary
return arg?.length;
}
// Checks array predicate return types, where possible
[
[1, 2],
[3, 4],
].filter(t => t); // number[] is always truthy
```
</TabItem>
<TabItem value="✅ Correct">
```ts
function head<T>(items: T[]) {
// Necessary, since items.length might be 0
if (items.length) {
return items[0].toUpperCase();
}
}
function foo(arg: string) {
// Necessary, since arg might be ''.
if (arg) {
}
}
function bar(arg?: string | null) {
// Necessary, since arg might be nullish
return arg?.length;
}
[0, 1, 2, 3].filter(t => t); // number can be truthy or falsy
```
</TabItem>
</Tabs>
## Options
### `allowConstantLoopConditions`
{/* insert option description */}
#### `'never'`
Disallow constant conditions in loops. Same as `false`.
Example of incorrect code for `{ allowConstantLoopConditions: 'never' }`:
```ts option='{ "allowConstantLoopConditions": "never" }' showPlaygroundButton
while (true) {
// ...
}
for (; true; ) {
// ...
}
do {
// ...
} while (true);
```
#### `'always'`
Allow constant conditions in loops. Same as `true`.
Example of correct code for `{ allowConstantLoopConditions: 'always' }`:
```ts option='{ "allowConstantLoopConditions": "always" }' showPlaygroundButton
while (true) {
// ...
}
for (; true; ) {
// ...
}
do {
// ...
} while (true);
```
#### `'only-allowed-literals'`
Permit idiomatic constant literal conditions in `while` loop conditions.
Specifically, `true`, `false`, `0`, and `1` are allowed despite always being truthy or falsy, as these are common patterns and are not likely to represent developer errors.
Example of correct code for `{ allowConstantLoopConditions: 'only-allowed-literals' }`:
```ts option='{ "allowConstantLoopConditions": "only-allowed-literals" }' showPlaygroundButton
while (true) {
// ...
}
```
Example of incorrect code for `{ allowConstantLoopConditions: 'only-allowed-literals' }`:
```ts option='{ "allowConstantLoopConditions": "only-allowed-literals" }' showPlaygroundButton
// `alwaysTrue` has the type of `true` (which isn't allowed)
// as only the literal value of `true` is allowed.
declare const alwaysTrue: true;
while (alwaysTrue) {
// ...
}
// not even a variable that references the value of `true` is allowed, only
// the literal value of `true` used directly.
const thisIsTrue = true;
while (thisIsTrue) {
// ...
}
```
### `checkTypePredicates`
{/* insert option description */}
Example of additional incorrect code with `{ checkTypePredicates: true }`:
```ts option='{ "checkTypePredicates": true }' showPlaygroundButton
function assert(condition: unknown): asserts condition {
if (!condition) {
throw new Error('Condition is falsy');
}
}
assert(false); // Unnecessary; condition is always falsy.
const neverNull = {};
assert(neverNull); // Unnecessary; condition is always truthy.
function isString(value: unknown): value is string {
return typeof value === 'string';
}
declare const s: string;
// Unnecessary; s is always a string.
if (isString(s)) {
}
function assertIsString(value: unknown): asserts value is string {
if (!isString(value)) {
throw new Error('Value is not a string');
}
}
assertIsString(s); // Unnecessary; s is always a string.
```
Whether this option makes sense for your project may vary.
Some projects may intentionally use type predicates to ensure that runtime values do indeed match the types according to TypeScript, especially in test code.
Often, it makes sense to use eslint-disable comments in these cases, with a comment indicating why the condition should be checked at runtime, despite appearing unnecessary.
However, in some contexts, it may be more appropriate to keep this option disabled entirely.
### `allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing`
{/* insert option description */}
:::danger Deprecated
This option will be removed in the next major version of typescript-eslint.
:::
If this is set to `false`, then the rule will error on every file whose `tsconfig.json` does _not_ have the `strictNullChecks` compiler option (or `strict`) set to `true`.
Without `strictNullChecks`, TypeScript essentially erases `undefined` and `null` from the types. This means when this rule inspects the types from a variable, **it will not be able to tell that the variable might be `null` or `undefined`**, which essentially makes this rule useless.
You should be using `strictNullChecks` to ensure complete type-safety in your codebase.
If for some reason you cannot turn on `strictNullChecks`, but still want to use this rule - you can use this option to allow it - but know that the behavior of this rule is _undefined_ with the compiler option turned off. We will not accept bug reports if you are using this option.
## Limitations
This rule is powered by TypeScript types, therefore, if the types do not match match the runtime behavior, the rule may report inaccurately.
This can happen in several commonplace scenarios.
### Possibly-undefined indexed access
By default, TypeScript optimistically assumes that indexed access will always return a value.
This means that cases like the following will be erroneously flagged as unnecessary conditions:
```ts showPlaygroundButton
const array: string[] = [];
const firstElement = array[0];
// false positive
if (firstElement != null) {
// ...
}
const record: Record<string, string> = {};
const someValue = record.someKey;
// false positive
if (someValue != null) {
// ...
}
```
To get pessimistic, but correct, types for these cases, you can use TypeScript's [`noUncheckedIndexedAccess` compiler option](https://www.typescriptlang.org/tsconfig/#noUncheckedIndexedAccess), though this is often unwieldy in real-world usage.
Another workaround is to use `array.at(0)` (which is always possibly-undefined) to indicate array access that may be out-of-bounds.
Otherwise, a disable comment will often make sense for these kinds of cases.
### Values modified within function calls
The following code will be erroneously flagged as unnecessary, even though the condition is modified within the function call.
```ts showPlaygroundButton
let condition = false;
const f = () => {
condition = Math.random() > 0.5;
};
f();
if (condition) {
// ...
}
```
This occurs due to limitations of TypeScript's type narrowing.
See [microsoft/TypeScript#9998](https://github.com/microsoft/TypeScript/issues/9998) for details.
We recommend using a [type assertion](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#type-assertions) in these cases, like so:
```ts showPlaygroundButton
let condition = false as boolean;
const f = () => {
condition = Math.random() > 0.5;
};
f();
if (condition) {
// ...
}
```
## When Not To Use It
If your project is not accurately typed, such as if it's in the process of being converted to TypeScript or is susceptible to [trade-offs in control flow analysis](https://github.com/Microsoft/TypeScript/issues/9998), it may be difficult to enable this rule for particularly non-type-safe areas of code.
You might consider using [ESLint disable comments](https://eslint.org/docs/latest/use/configure/rules#using-configuration-comments-1) for those specific situations instead of completely disabling this rule.
## Related To
- ESLint: [no-constant-condition](https://eslint.org/docs/rules/no-constant-condition) - `no-unnecessary-condition` is essentially a stronger version of `no-constant-condition`, but requires type information.
- [strict-boolean-expressions](./strict-boolean-expressions.mdx) - a more opinionated version of `no-unnecessary-condition`. `strict-boolean-expressions` enforces a specific code style, while `no-unnecessary-condition` is about correctness.