prefer-optional-chain
Enforce using concise optional chain expressions instead of chained logical ands, negated logical ors, or empty objects.
Extending "plugin:@typescript-eslint/stylistic-type-checked"
in an ESLint configuration enables this rule.
Some problems reported by this rule are automatically fixable by the --fix
ESLint command line option.
Some problems reported by this rule are manually fixable by editor suggestions.
This rule requires type information to run.
?.
optional chain expressions provide undefined
if an object is null
or undefined
.
Because the optional chain operator only chains when the property value is null
or undefined
, it is much safer than relying upon logical AND operator chaining &&
; which chains on any truthy value.
It is also often less code to use ?.
optional chaining than &&
truthiness checks.
This rule reports on code where an &&
operator can be safely replaced with ?.
optional chaining.
module.exports = {
"rules": {
"@typescript-eslint/prefer-optional-chain": "error"
}
};
Try this rule in the playground ↗
Examples
- ❌ Incorrect
- ✅ Correct
foo && foo.a && foo.a.b && foo.a.b.c;
foo && foo['a'] && foo['a'].b && foo['a'].b.c;
foo && foo.a && foo.a.b && foo.a.b.method && foo.a.b.method();
// With empty objects
(((foo || {}).a || {}).b || {}).c;
(((foo || {})['a'] || {}).b || {}).c;
// With negated `or`s
!foo || !foo.bar;
!foo || !foo[bar];
!foo || !foo.bar || !foo.bar.baz || !foo.bar.baz();
// this rule also supports converting chained strict nullish checks:
foo &&
foo.a != null &&
foo.a.b !== null &&
foo.a.b.c != undefined &&
foo.a.b.c.d !== undefined &&
foo.a.b.c.d.e;
Open in Playgroundfoo?.a?.b?.c;
foo?.['a']?.b?.c;
foo?.a?.b?.method?.();
foo?.a?.b?.c?.d?.e;
!foo?.bar;
!foo?.[bar];
!foo?.bar?.baz?.();
Open in PlaygroundOptions
This rule accepts the following options:
type Options = [
{
/** Allow autofixers that will change the return type of the expression. This option is considered unsafe as it may break the build. */
allowPotentiallyUnsafeFixesThatModifyTheReturnTypeIKnowWhatImDoing?: boolean;
/** Check operands that are typed as `any` when inspecting "loose boolean" operands. */
checkAny?: boolean;
/** Check operands that are typed as `bigint` when inspecting "loose boolean" operands. */
checkBigInt?: boolean;
/** Check operands that are typed as `boolean` when inspecting "loose boolean" operands. */
checkBoolean?: boolean;
/** Check operands that are typed as `number` when inspecting "loose boolean" operands. */
checkNumber?: boolean;
/** Check operands that are typed as `string` when inspecting "loose boolean" operands. */
checkString?: boolean;
/** Check operands that are typed as `unknown` when inspecting "loose boolean" operands. */
checkUnknown?: boolean;
/** Skip operands that are not typed with `null` and/or `undefined` when inspecting "loose boolean" operands. */
requireNullish?: boolean;
},
];
const defaultOptions: Options = [
{
checkAny: true,
checkUnknown: true,
checkString: true,
checkNumber: true,
checkBoolean: true,
checkBigInt: true,
requireNullish: false,
allowPotentiallyUnsafeFixesThatModifyTheReturnTypeIKnowWhatImDoing: false,
},
];
In the context of the descriptions below a "loose boolean" operand is any operand that implicitly coerces the value to a boolean.
Specifically the argument of the not operator (!loose
) or a bare value in a logical expression (loose && looser
).
allowPotentiallyUnsafeFixesThatModifyTheReturnTypeIKnowWhatImDoing
When this option is true
, the rule will provide an auto-fixer for cases where the return type of the expression would change. For example for the expression !foo || foo.bar
the return type of the expression is true | T
, however for the equivalent optional chain foo?.bar
the return type of the expression is undefined | T
. Thus changing the code from a logical expression to an optional chain expression has altered the type of the expression.
In some cases this distinction may matter - which is why these fixers are considered unsafe - they may break the build! For example in the following code:
declare const foo: { bar: boolean } | null | undefined;
declare function acceptsBoolean(arg: boolean): void;
// ✅ typechecks succesfully as the expression only returns `boolean`
acceptsBoolean(foo != null && foo.bar);
// ❌ typechecks UNSUCCESSFULLY as the expression returns `boolean | undefined`
acceptsBoolean(foo?.bar);
Open in PlaygroundThis style of code isn't super common - which means having this option set to true
should be safe in most codebases. However we default it to false
due to its unsafe nature. We have provided this option for convenience because it increases the autofix cases covered by the rule. If you set option to true
the onus is entirely on you and your team to ensure that each fix is correct and safe and that it does not break the build.
When this option is false
unsafe cases will have suggestion fixers provided instead of auto-fixers - meaning you can manually apply the fix using your IDE tooling.
checkAny
When this option is true
the rule will check operands that are typed as any
when inspecting "loose boolean" operands.
- ❌ Incorrect for `checkAny: true`
- ✅ Correct for `checkAny: false`
declare const thing: any;
thing && thing.toString();
Open in Playgrounddeclare const thing: any;
thing && thing.toString();
Open in PlaygroundcheckUnknown
When this option is true
the rule will check operands that are typed as unknown
when inspecting "loose boolean" operands.
- ❌ Incorrect for `checkUnknown: true`
- ✅ Correct for `checkUnknown: false`
declare const thing: unknown;
thing && thing.toString();
Open in Playgrounddeclare const thing: unknown;
thing && thing.toString();
Open in PlaygroundcheckString
When this option is true
the rule will check operands that are typed as string
when inspecting "loose boolean" operands.
- ❌ Incorrect for `checkString: true`
- ✅ Correct for `checkString: false`
declare const thing: string;
thing && thing.toString();
Open in Playgrounddeclare const thing: string;
thing && thing.toString();
Open in PlaygroundcheckNumber
When this option is true
the rule will check operands that are typed as number
when inspecting "loose boolean" operands.
- ❌ Incorrect for `checkNumber: true`
- ✅ Correct for `checkNumber: false`
declare const thing: number;
thing && thing.toString();
Open in Playgrounddeclare const thing: number;
thing && thing.toString();
Open in PlaygroundcheckBoolean
When this option is true
the rule will check operands that are typed as boolean
when inspecting "loose boolean" operands.
This rule intentionally ignores the following case:
declare const x: false | { a: string };
x && x.a;
!x || x.a;
The boolean expression narrows out the non-nullish falsy cases - so converting the chain to x?.a
would introduce a type error.
- ❌ Incorrect for `checkBoolean: true`
- ✅ Correct for `checkBoolean: false`
declare const thing: true;
thing && thing.toString();
Open in Playgrounddeclare const thing: true;
thing && thing.toString();
Open in PlaygroundcheckBigInt
When this option is true
the rule will check operands that are typed as bigint
when inspecting "loose boolean" operands.
- ❌ Incorrect for `checkBigInt: true`
- ✅ Correct for `checkBigInt: false`
declare const thing: bigint;
thing && thing.toString();
Open in Playgrounddeclare const thing: bigint;
thing && thing.toString();
Open in PlaygroundrequireNullish
When this option is true
the rule will skip operands that are not typed with null
and/or undefined
when inspecting "loose boolean" operands.
- ❌ Incorrect for `requireNullish: true`
- ✅ Correct for `requireNullish: true`
declare const thing1: string | null;
thing1 && thing1.toString();
Open in Playgrounddeclare const thing1: string | null;
thing1?.toString();
declare const thing2: string;
thing2 && thing2.toString();
Open in PlaygroundWhen Not To Use It
If your project is not accurately typed, such as if it's in the process of being converted to TypeScript or is susceptible to trade-offs in control flow analysis, it may be difficult to enable this rule for particularly non-type-safe areas of code. You might consider using ESLint disable comments for those specific situations instead of completely disabling this rule.
Further Reading
Type checked lint rules are more powerful than traditional lint rules, but also require configuring type checked linting. See Performance Troubleshooting if you experience performance degredations after enabling type checked rules.