@typescript-eslint/prefer-nullish-coalescing

Require ?? instead of || or a ternary to give a default for null or undefined

has suggestionsneeds types

Also called typescript-eslint/prefer-nullish-coalescing, typescript/prefer-nullish-coalescing

port || 3000 falls back to 3000 for every falsy value, so a port of 0 is replaced, and so are "" and false. port ?? 3000 only falls back when port is null or undefined.

|| and ||= are reported when the left side's type includes null or undefined. A ternary or an if that does the job of ?? or ??= by hand is reported too.

--fix doesn't change these. Look at each one before you change it, because ?? keeps 0, "", and false where || replaced them.

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/prefer-nullish-coalescing" = "error" # or "warn"

Every option, set to its default:

bunfig.toml
[lint.rules]
"@typescript-eslint/prefer-nullish-coalescing" = ["error", {
  ignoreConditionalTests = true,
  ignoreTernaryTests = false,
  ignoreIfStatements = false,
  ignoreMixedLogicalExpressions = false,
  ignoreBooleanCoercion = false,
  ignorePrimitives = {
    bigint = false,
    boolean = false,
    number = false,
    string = false,
  },
  allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing = false,
}]

Examples#

example.ts4 problems
function connect(options: { port?: number; host: string | null; name?: string; tags?: string[] }) {
  const port = options.port || 3000;
  const host = options.host !== null ? options.host : "localhost";
  options.name ||= "default";
  if (!options.tags) {
    options.tags = [];
  }
}
example.tsNo problems
function connect(options: { port?: number; host: string | null; name?: string; tags?: string[] }) {
  const port = options.port ?? 3000;
  const host = options.host ?? "localhost";
  options.name ??= "default";
  options.tags ??= [];
}

function label(text: string) {
  return text || "untitled"; // never null or undefined
}

Options#

OptionDefaultWhat it does
ignoreConditionalTeststrueAllow || in the test of an if, a loop, or a ternary, where only true or false matters.
ignoreTernaryTestsfalseDon't report ternaries.
ignoreIfStatementsfalseDon't report if statements.
ignoreMixedLogicalExpressionsfalseAllow || in an expression that also has &&.
ignoreBooleanCoercionfalseAllow || inside a Boolean(...) call.
ignorePrimitives{}Allow || when the left side can be one of these: { string, number, boolean, bigint }, each true or false. true means all four.
allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoingfalseWithout strictNullChecks, 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.

The test of an if is allowed by default:

example.tsNo problems
function show(title: string | undefined, subtitle: string) {
  if (title || subtitle) {
    console.log(title, subtitle);
  }
}

With ignoreConditionalTests: false it's reported:

example.tswith { "ignoreConditionalTests": false }1 problem
function show(title: string | undefined, subtitle: string) {
  if (title || subtitle) {
    console.log(title, subtitle);
  }
}

Use ignorePrimitives: { string: true } if you want an empty string to get the default too:

example.tswith { "ignorePrimitives": { "string": true } }No problems
function greet(name: string | undefined) {
  return "Hello, " + (name || "stranger");
}

A number is still reported with that setting:

example.tswith { "ignorePrimitives": { "string": true } }1 problem
function retries(count: number | undefined) {
  return count || 3;
}

ignoreMixedLogicalExpressions allows the first line, and ignoreBooleanCoercion allows the second:

example.tswith { "ignoreMixedLogicalExpressions": true, "ignoreBooleanCoercion": true }No problems
function ready(user: { name: string } | null, guest: { name: string } | null, open: boolean) {
  const allowed = user || (guest && open);
  return Boolean(user || guest) && allowed;
}