Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
feat(eslint-plugin): [strict-boolean-expression] rework options (#1631)
- Loading branch information
1 parent
02afc31
commit cd14482
Showing
3 changed files
with
421 additions
and
1,289 deletions.
There are no files selected for viewing
123 changes: 89 additions & 34 deletions
123
packages/eslint-plugin/docs/rules/strict-boolean-expressions.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,67 +1,122 @@ | ||
# Restricts the types allowed in boolean expressions (`strict-boolean-expressions`) | ||
|
||
Requires that any boolean expression is limited to true booleans rather than | ||
casting another primitive to a boolean at runtime. | ||
Forbids usage of non-boolean types in expressions where a boolean is expected. | ||
`boolean` and `never` types are always allowed. | ||
Additional types which are considered safe in a boolean context can be configured via options. | ||
|
||
It is useful to be explicit, for example, if you were trying to check if a | ||
number was defined. Doing `if (number)` would evaluate to `false` if `number` | ||
was defined and `0`. This rule forces these expressions to be explicit and to | ||
strictly use booleans. | ||
The following nodes are considered boolean expressions and their type is checked: | ||
|
||
The following nodes are checked: | ||
|
||
- Arguments to the `!`, `&&`, and `||` operators | ||
- The condition in a conditional expression `(cond ? x : y)` | ||
- Argument to the logical negation operator (`!arg`). | ||
- The condition in a conditional expression (`cond ? x : y`). | ||
- Conditions for `if`, `for`, `while`, and `do-while` statements. | ||
- Operands of logical binary operators (`lhs || rhs` and `lhs && rhs`). | ||
- Right-hand side operand is ignored when it's not a descendant of another boolean expression. | ||
This is to allow usage of boolean operators for their short-circuiting behavior. | ||
|
||
## Examples | ||
|
||
Examples of **incorrect** code for this rule: | ||
|
||
```ts | ||
const number = 0; | ||
if (number) { | ||
return; | ||
// nullable numbers are considered unsafe by default | ||
let num: number | undefined = 0; | ||
if (num) { | ||
console.log('num is defined'); | ||
} | ||
// nullable strings are considered unsafe by default | ||
let str: string | null = null; | ||
if (!str) { | ||
console.log('str is empty'); | ||
} | ||
let foo = bar || 'foobar'; | ||
// nullable booleans are considered unsafe by default | ||
function foo(bool?: boolean) { | ||
if (bool) { | ||
bar(); | ||
} | ||
} | ||
let undefinedItem; | ||
let foo = undefinedItem ? 'foo' : 'bar'; | ||
// `any`, unconstrained generics and unions of more than one primitive type are disallowed | ||
const foo = <T>(arg: T) => (arg ? 1 : 0); | ||
let str = 'foo'; | ||
while (str) { | ||
break; | ||
// always-truthy and always-falsy types are disallowed | ||
let obj = {}; | ||
while (obj) { | ||
obj = getObj(); | ||
} | ||
``` | ||
|
||
Examples of **correct** code for this rule: | ||
|
||
```ts | ||
const number = 0; | ||
if (typeof number !== 'undefined') { | ||
return; | ||
```tsx | ||
// Using logical operators for their side effects is allowed | ||
const Component = () => { | ||
const entry = map.get('foo') || {}; | ||
return entry && <p>Name: {entry.name}</p>; | ||
}; | ||
// nullable values should be checked explicitly against null or undefined | ||
let num: number | undefined = 0; | ||
if (num != null) { | ||
console.log('num is defined'); | ||
} | ||
let foo = typeof bar !== 'undefined' ? bar : 'foobar'; | ||
let undefinedItem; | ||
let foo = typeof undefinedItem !== 'undefined' ? 'foo' : 'bar'; | ||
let str: string | null = null; | ||
if (str != null && !str) { | ||
console.log('str is empty'); | ||
} | ||
let str = 'foo'; | ||
while (typeof str !== 'undefined') { | ||
break; | ||
function foo(bool?: boolean) { | ||
if (bool ?? false) { | ||
bar(); | ||
} | ||
} | ||
// `any` types should be cast to boolean explicitly | ||
const foo = (arg: any) => (Boolean(arg) ? 1 : 0); | ||
``` | ||
|
||
## Options | ||
|
||
Options may be provided as an object with: | ||
|
||
- `allowNullable` to allow `undefined` and `null` in addition to `boolean` as a type of all boolean expressions. (`false` by default). | ||
- `allowSafe` to allow non-falsy types (i.e. non string / number / boolean) in addition to `boolean` as a type of all boolean expressions. (`false` by default). | ||
- `ignoreRhs` to skip the check on the right hand side of expressions like `a && b` or `a || b` - allows these operators to be used for their short-circuiting behavior. (`false` by default). | ||
- `allowString` (`true` by default) - | ||
Allows `string` in a boolean context. | ||
This is safe because strings have only one falsy value (`""`). | ||
Set this to `false` if you prefer the explicit `str != ""` or `str.length > 0` style. | ||
|
||
- `allowNumber` (`true` by default) - | ||
Allows `number` in a boolean context. | ||
This is safe because numbers have only two falsy values (`0` and `NaN`). | ||
Set this to `false` if you prefer the explicit `num != 0` and `!Number.isNaN(num)` style. | ||
|
||
- `allowNullableObject` (`true` by default) - | ||
Allows `object | function | symbol | null | undefined` in a boolean context. | ||
This is safe because objects, functions and symbols don't have falsy values. | ||
Set this to `false` if you prefer the explicit `obj != null` style. | ||
|
||
- `allowNullableBoolean` (`false` by default) - | ||
Allows `boolean | null | undefined` in a boolean context. | ||
This is unsafe because nullable booleans can be either `false` or nullish. | ||
Set this to `false` if you want to enforce explicit `bool ?? false` or `bool ?? true` style. | ||
Set this to `true` if you don't mind implicitly treating false the same as a nullish value. | ||
|
||
- `allowNullableString` (`false` by default) - | ||
Allows `string | null | undefined` in a boolean context. | ||
This is unsafe because nullable strings can be either an empty string or nullish. | ||
Set this to `true` if you don't mind implicitly treating an empty string the same as a nullish value. | ||
|
||
- `allowNullableNumber` (`false` by default) - | ||
Allows `number | null | undefined` in a boolean context. | ||
This is unsafe because nullable numbers can be either a falsy number or nullish. | ||
Set this to `true` if you don't mind implicitly treating zero or NaN the same as a nullish value. | ||
|
||
- `allowAny` (`false` by default) - | ||
Allows `any` in a boolean context. | ||
|
||
## Related To | ||
|
||
- TSLint: [strict-boolean-expressions](https://palantir.github.io/tslint/rules/strict-boolean-expressions) | ||
|
||
- [no-unnecessary-condition](./no-unnecessary-condition.md) - essentially a less opinionated alternative to this rule. `strict-boolean-expressions` enforces a specific code style, while `no-unnecessary-condition` is about correctness. | ||
- [no-unnecessary-condition](./no-unnecessary-condition.md) - Similar rule which reports always-truthy and always-falsy values in conditions |
Oops, something went wrong.