Skip to content

Latest commit

 

History

History
262 lines (164 loc) · 6.49 KB

prefer-optional-chain.md

File metadata and controls

262 lines (164 loc) · 6.49 KB
description
Enforce using concise optional chain expressions instead of chained logical ands, negated logical ors, or empty objects.

🛑 This file is source code, not the primary documentation location! 🛑

See https://typescript-eslint.io/rules/prefer-optional-chain for documentation.

?. 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.

Examples

❌ Incorrect

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;

✅ Correct

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

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?.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

declare const thing: any;

thing && thing.toString();

✅ Correct for checkAny: false

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

declare const thing: unknown;

thing && thing.toString();

✅ Correct for checkUnknown: false

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

declare const thing: string;

thing && thing.toString();

✅ Correct for checkString: false

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

declare const thing: number;

thing && thing.toString();

✅ Correct for checkNumber: false

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

declare const thing: boolean;

thing && thing.toString();

✅ Correct for checkBoolean: false

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

declare const thing: bigint;

thing && thing.toString();

✅ Correct for checkBigInt: false

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

declare const thing1: string | null;
thing1 && thing1.toString();

✅ Correct for requireNullish: true

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.

Further Reading