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"
}
};
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;
foo?.a?.b?.c;
foo?.['a']?.b?.c;
foo?.a?.b?.method?.();
foo?.a?.b?.c?.d?.e;
!foo?.bar;
!foo?.[bar];
!foo?.bar?.baz?.();
Options
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 not 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 != null && foo.bar);
This 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();
declare const thing: any;
thing && thing.toString();
checkUnknown
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();
declare const thing: unknown;
thing && thing.toString();
checkString
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();
declare const thing: string;
thing && thing.toString();
checkNumber
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();
declare const thing: number;
thing && thing.toString();
checkBoolean
When this option is true
the rule will check operands that are typed as boolean
when inspecting "loose boolean" operands.
- ❌ Incorrect for checkBoolean: true
- ✅ Correct for checkBoolean: false
declare const thing: boolean;
thing && thing.toString();
declare const thing: boolean;
thing && thing.toString();
checkBigInt
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();
declare const thing: bigint;
thing && thing.toString();
requireNullish
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();
declare const thing1: string | null;
thing1?.toString();
declare const thing2: string;
thing2 && thing2.toString();
When Not To Use It
If you don't mind using more explicit &&
s/||
s, you don't need this rule.