Thanks to visit codestin.com
Credit goes to github.com

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.