@typescript-eslint/prefer-optional-chain
Require a?.b instead of a && a.b, !a || !a.b, or (a || {}).b
Also called typescript-eslint/prefer-optional-chain, typescript/prefer-optional-chain
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.
Configure#
Off by default. This rule needs types, so turning it on makes bun lint type check your code first.
[lint.rules]
"@typescript-eslint/prefer-optional-chain" = "error" # or "warn"Every option, set to its default:
[lint.rules]
"@typescript-eslint/prefer-optional-chain" = ["error", {
checkString = true,
checkNumber = true,
checkBoolean = true,
checkBigInt = true,
checkAny = true,
checkUnknown = true,
requireNullish = false,
allowPotentiallyUnsafeFixesThatModifyTheReturnTypeIKnowWhatImDoing = false,
}]Examples#
type User = { profile?: { city?: string; refresh?: () => void } };
function show(user: User | undefined) {
const city = user && user.profile && user.profile.cityPrefer using an optional chain expression instead, as it's more concise and easier to read.;
if (!user || !user.profilePrefer using an optional chain expression instead, as it's more concise and easier to read.) return;
return (user.profile || {}).refreshPrefer using an optional chain expression instead, as it's more concise and easier to read.;
}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:
function size(name: string | undefined) {
return name && name.lengthPrefer using an optional chain expression instead, as it's more concise and easier to read.;
}Set checkString: false if your code depends on that difference. Then it isn't reported:
{ "checkString": false }No problemsfunction size(name: string | undefined) {
return name && name.length;
}Here name is never null or undefined. It's still reported by default:
function size(name: string) {
return name && name.lengthPrefer using an optional chain expression instead, as it's more concise and easier to read.;
}With requireNullish: true it's left alone:
{ "requireNullish": true }No problemsfunction size(name: string) {
return name && name.length;
}