# @typescript-eslint/prefer-optional-chain

> Require `a?.b` instead of `a && a.b`, `!a || !a.b`, or `(a || {}).b`

`user && user.profile && user.profile.city` repeats each step of the path to guard the next one. `user?.profile?.city` says the same thing once.

The rule reports chains of `&&`, chains of `||` with negated operands, comparisons with `null` and `undefined` such as `user != null && user.name`, and an empty object as a fallback, like `(user || {}).name` or `(user ?? {}).name`.

`--fix` rewrites a chain only if that doesn't add `undefined` to the result's type. If `user` is `User | null`, `user && user.name` can be `null` but `user?.name` is `undefined` instead, so it's reported but not fixed. `(user || {}).name` isn't fixed either.

{/* Generated by src/codegen/lint-rule-docs.ts. Do not edit this section. */}

## Configure

Off by default. This rule needs types, so turning it on makes `bun lint` type check your code first.

```toml bunfig.toml
[lint.rules]
"@typescript-eslint/prefer-optional-chain" = "error" # or "warn"
```

Every option, set to its default:

```toml bunfig.toml
[lint.rules]
"@typescript-eslint/prefer-optional-chain" = ["error", {
  checkString = true,
  checkNumber = true,
  checkBoolean = true,
  checkBigInt = true,
  checkAny = true,
  checkUnknown = true,
  requireNullish = false,
  allowPotentiallyUnsafeFixesThatModifyTheReturnTypeIKnowWhatImDoing = false,
}]
```

{/* End of the generated section. */}

## Examples

Reported:

{/* bad {"count": 3} */}
```ts
type User = { profile?: { city?: string; refresh?: () => void } };

function show(user: User | undefined) {
  const city = user && user.profile && user.profile.city;
  if (!user || !user.profile) return;
  return (user.profile || {}).refresh;
}
```

Not reported:

{/* good */}
```ts
type User = { profile?: { city?: string; refresh?: () => void } };

function show(user: User | undefined) {
  const city = user?.profile?.city;
  if (!user?.profile) return;
  return user.profile?.refresh;
}

function both(user: User | undefined, other: User) {
  return user && other.profile; // two different objects
}
```

## Options

| Option | Default | What it does |
| --- | --- | --- |
| `checkString` | `true` | Report `x && x.y` when `x` can be a string. |
| `checkNumber` | `true` | The same for a number. |
| `checkBoolean` | `true` | The same for a boolean. |
| `checkBigInt` | `true` | The same for a bigint. |
| `checkAny` | `true` | The same for `any`. |
| `checkUnknown` | `true` | The same for `unknown`. |
| `requireNullish` | `false` | Only report if something that's tested can be `null` or `undefined`. |
| `allowPotentiallyUnsafeFixesThatModifyTheReturnTypeIKnowWhatImDoing` | `false` | Let `--fix` rewrite `&&` and `\|\|` chains even where that changes the result's type, for example from `null` to `undefined`. |

For an empty string, `name && name.length` is `""` and `name?.length` is `0`. This is reported by default:

{/* bad */}
```ts
function size(name: string | undefined) {
  return name && name.length;
}
```

Set `checkString: false` if your code depends on that difference. Then it isn't reported:

{/* good {"options": [{"checkString": false}]} */}
```ts
function size(name: string | undefined) {
  return name && name.length;
}
```

Here `name` is never `null` or `undefined`. It's still reported by default:

{/* bad */}
```ts
function size(name: string) {
  return name && name.length;
}
```

With `requireNullish: true` it's left alone:

{/* good {"options": [{"requireNullish": true}]} */}
```ts
function size(name: string) {
  return name && name.length;
}
```
