AphrodyBun GitHub

Runtime docs · Linter · Rules

@typescript-eslint/strict-boolean-expressions

Disallow nullable numbers, strings, and booleans, any, and always-true values in conditions

if (count) on a number | undefined is false for undefined, and also for 0 and NaN. if (name) on an optional string is also false for "". Code that means "was it given?" then skips a valid value.

This rule checks the conditions of if, while, do, for, and ? :, the operand of !, the left side of && and ||, and the return value of callbacks passed to filter, find, some, every, and similar array methods.

By default a condition can be a boolean, a string, a number, or an object that can be null or undefined. These are reported:

  • a boolean, string, number, or enum that can also be null or undefined
  • any and unknown
  • a mix such as string | number
  • an object that's never nullish, which is always true
  • a value that's always nullish, which is always false

This rule needs strictNullChecks (or strict) in your tsconfig.json.

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/strict-boolean-expressions" = "error" # or "warn"

Every option, set to its default:

bunfig.toml
[lint.rules]
"@typescript-eslint/strict-boolean-expressions" = ["error", {
  allowString = true,
  allowNumber = true,
  allowNullableObject = true,
  allowNullableBoolean = false,
  allowNullableString = false,
  allowNullableNumber = false,
  allowNullableEnum = false,
  allowAny = false,
  allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing = false,
}]

Examples

Reported:

function render(width?: number, title?: string, items: string[] = []) {
  if (width) {
    resize(width); // skipped for 0
  }
  const heading = title || "Untitled"; // replaces ""
  if (items) {
    draw(items); // always runs, even for []
  }
}

declare function resize(width: number): void;
declare function draw(items: string[]): void;

Not reported:

function render(width?: number, title?: string, items: string[] = []) {
  if (width !== undefined) {
    resize(width);
  }
  const heading = title ?? "Untitled";
  if (items.length > 0) {
    draw(items);
  }
}

declare function resize(width: number): void;
declare function draw(items: string[]): void;

Array method callbacks, unknown, and mixed types are reported too:

function pick(sizes: (number | undefined)[], raw: unknown, id: string | number) {
  const known = sizes.filter(size => size); // drops 0 too
  if (raw) console.log(raw);
  if (id) console.log(id);
}

A nullable object has only one falsy case, so it's allowed:

function nameOf(user?: { name: string }) {
  return user ? user.name : "guest";
}

Options

OptionDefaultWhat it does
allowStringtrueAllow string.
allowNumbertrueAllow number and bigint.
allowNullableObjecttrueAllow an object, function, or symbol that can be nullish.
allowNullableBooleanfalseAllow boolean | null | undefined.
allowNullableStringfalseAllow string | null | undefined.
allowNullableNumberfalseAllow number | null | undefined.
allowNullableEnumfalseAllow an enum that can be nullish.
allowAnyfalseAllow any and unknown.
allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoingfalseDon't report a project that has strictNullChecks off. Without it TypeScript hides null and undefined from types, so the rule misses most cases.

Turn off allowString and allowNumber to accept only real booleans and nullable objects. These are then reported:

function summary(items: string[], query: string) {
  if (items.length) {
    console.log(items.length);
  }
  return query ? `Results for ${query}` : "All";
}

An optional flag is a common case to allow. Use allowNullableBoolean for that:

function build(options: { minify?: boolean }) {
  if (options.minify) {
    console.log("minifying");
  }
}

When not to use it

In a codebase that relies on truthiness checks, this rule reports a lot of code that works. Start with the allowNullable options on, or leave it off.