Skip to content

fix(eslint-plugin): [prefer-optional-chain] should report case that can be converted to optional void function call ?.() #11272

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
31 changes: 31 additions & 0 deletions packages/eslint-plugin/docs/rules/prefer-optional-chain.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,37 @@ thing?.toString();
</TabItem>
</Tabs>

### `checkVoid`

{/* insert option description */}

Examples of code for this rule with `{ checkVoid: true }`:

<Tabs>
<TabItem value="❌ Incorrect">

```ts option='{ "checkVoid": true }'
declare const thing: {
method: undefined | (() => void);
};

thing.method && thing.method();
```

</TabItem>
<TabItem value="✅ Correct">

```ts option='{ "checkVoid": true }'
declare const thing: {
method: undefined | (() => void);
};

thing.method?.();
```

</TabItem>
</Tabs>

### `requireNullish`

{/* insert option description */}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ export interface PreferOptionalChainOptions {
checkNumber?: boolean;
checkString?: boolean;
checkUnknown?: boolean;
checkVoid?: boolean;
requireNullish?: boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,6 @@ export interface InvalidOperand {
type: OperandValidity.Invalid;
}
type Operand = InvalidOperand | ValidOperand;

const NULLISH_FLAGS = ts.TypeFlags.Null | ts.TypeFlags.Undefined;
function isValidFalseBooleanCheckType(
node: TSESTree.Node,
disallowFalseyLiteral: boolean,
Expand Down Expand Up @@ -92,26 +90,30 @@ function isValidFalseBooleanCheckType(
return false;
}

let allowedFlags = NULLISH_FLAGS | ts.TypeFlags.Object;
if (options.checkAny === true) {
allowedFlags |= ts.TypeFlags.Any;
let flagsToExcludeFromCheck = 0;
if (options.checkAny !== true) {
flagsToExcludeFromCheck |= ts.TypeFlags.Any;
}
if (options.checkUnknown !== true) {
flagsToExcludeFromCheck |= ts.TypeFlags.Unknown;
}
if (options.checkUnknown === true) {
allowedFlags |= ts.TypeFlags.Unknown;
if (options.checkString !== true) {
flagsToExcludeFromCheck |= ts.TypeFlags.StringLike;
}
if (options.checkString === true) {
allowedFlags |= ts.TypeFlags.StringLike;
if (options.checkNumber !== true) {
flagsToExcludeFromCheck |= ts.TypeFlags.NumberLike;
}
if (options.checkNumber === true) {
allowedFlags |= ts.TypeFlags.NumberLike;
if (options.checkBoolean !== true) {
flagsToExcludeFromCheck |= ts.TypeFlags.BooleanLike;
}
if (options.checkBoolean === true) {
allowedFlags |= ts.TypeFlags.BooleanLike;
if (options.checkBigInt !== true) {
flagsToExcludeFromCheck |= ts.TypeFlags.BigIntLike;
}
if (options.checkBigInt === true) {
allowedFlags |= ts.TypeFlags.BigIntLike;
if (options.checkVoid !== true) {
flagsToExcludeFromCheck |= ts.TypeFlags.Void;
}
return types.every(t => isTypeFlagSet(t, allowedFlags));

return types.every(t => !isTypeFlagSet(t, flagsToExcludeFromCheck));
}

export function gatherLogicalOperands(
Expand Down
6 changes: 6 additions & 0 deletions packages/eslint-plugin/src/rules/prefer-optional-chain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ export default createRule<
description:
'Check operands that are typed as `unknown` when inspecting "loose boolean" operands.',
},
checkVoid: {
type: 'boolean',
description:
'Check operands that are typed as `void` when inspecting "loose boolean" operands.',
},
requireNullish: {
type: 'boolean',
description:
Expand All @@ -100,6 +105,7 @@ export default createRule<
checkNumber: true,
checkString: true,
checkUnknown: true,
checkVoid: true,
requireNullish: false,
},
],
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -1680,6 +1680,48 @@ describe('hand-crafted cases', () => {
],
output: 'a?.prop;',
},
// check void
{
code: `
declare const foo: {
method: undefined | (() => void);
};
foo.method && foo.method();
`,
errors: [{ messageId: 'preferOptionalChain' }],
output: `
declare const foo: {
method: undefined | (() => void);
};
foo.method?.();
`,
},
// Exclude for everything else, an error occurs
{
code: noFormat`declare const foo: { x: { y: string } } | null; foo && foo.x;`,
errors: [
{
messageId: 'preferOptionalChain',
suggestions: [
{
messageId: 'optionalChainSuggest',
output: `declare const foo: { x: { y: string } } | null; foo?.x;`,
},
],
},
],
options: [
{
checkAny: false,
checkBigInt: false,
checkBoolean: false,
checkNumber: false,
checkString: false,
checkUnknown: false,
checkVoid: false,
},
],
},
],
valid: [
'!a || !b;',
Expand Down Expand Up @@ -1878,6 +1920,15 @@ describe('hand-crafted cases', () => {
`,
options: [{ checkUnknown: false }],
},
{
code: `
declare const foo: {
method: undefined | (() => void);
};
foo.method && foo.method();
`,
options: [{ checkVoid: false }],
},
'(x = {}) && (x.y = true) != null && x.y.toString();',
"('x' as `${'x'}`) && ('x' as `${'x'}`).length;",
'`x` && `x`.length;',
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy