Skip to content

feat(eslint-plugin): [prefer-nullish-coalescing] add option ignoreBooleanCoercion #9924

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

Merged
22 changes: 22 additions & 0 deletions packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,28 @@ foo ?? 'a string';

Also, if you would like to ignore all primitives types, you can set `ignorePrimitives: true`. It is equivalent to `ignorePrimitives: { string: true, number: true, bigint: true, boolean: true }`.

### `ignoreBooleanCoercion`

Whether to ignore expressions that coerce a value into a boolean: `Boolean(...)`.

Incorrect code for `ignoreBooleanCoercion: false`, and correct code for `ignoreBooleanCoercion: true`:

```ts option='{ "ignoreBooleanCoercion": true }' showPlaygroundButton
let a: string | true | undefined;
let b: string | boolean | undefined;

const x = Boolean(a || b);
```

Correct code for `ignoreBooleanCoercion: false`:

```ts option='{ "ignoreBooleanCoercion": false }' showPlaygroundButton
let a: string | true | undefined;
let b: string | boolean | undefined;

const x = Boolean(a ?? b);
```

### `allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing`

{/* insert option description */}
Expand Down
120 changes: 92 additions & 28 deletions packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
export type Options = [
{
allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing?: boolean;
ignoreBooleanCoercion?: boolean;
ignoreConditionalTests?: boolean;
ignoreMixedLogicalExpressions?: boolean;
ignorePrimitives?:
Expand Down Expand Up @@ -71,6 +72,11 @@
description:
'Unless this is set to `true`, the rule will error on every file whose `tsconfig.json` does _not_ have the `strictNullChecks` compiler option (or `strict`) set to `true`.',
},
ignoreBooleanCoercion: {
type: 'boolean',
description:
'Whether to ignore arguments to the `Boolean` constructor',
},
ignoreConditionalTests: {
type: 'boolean',
description:
Expand Down Expand Up @@ -126,6 +132,7 @@
defaultOptions: [
{
allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing: false,
ignoreBooleanCoercion: false,
ignoreConditionalTests: true,
ignoreMixedLogicalExpressions: false,
ignorePrimitives: {
Expand All @@ -142,6 +149,7 @@
[
{
allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing,
ignoreBooleanCoercion,
ignoreConditionalTests,
ignoreMixedLogicalExpressions,
ignorePrimitives,
Expand Down Expand Up @@ -431,46 +439,102 @@
'LogicalExpression[operator = "||"]'(
node: TSESTree.LogicalExpression,
): void {
if (
ignoreBooleanCoercion === true &&
isBooleanConstructorContext(node, context)
) {
return;
}

checkAssignmentOrLogicalExpression(node, 'or', '');
},
};
},
});

function isConditionalTest(node: TSESTree.Node): boolean {
const parents = new Set<TSESTree.Node | null>([node]);
let current = node.parent;
while (current) {
parents.add(current);
const parent = node.parent;
if (parent == null) {
return false;

Check warning on line 458 in packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts

View check run for this annotation

Codecov / codecov/patch

packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts#L458

Added line #L458 was not covered by tests
}

if (
(current.type === AST_NODE_TYPES.ConditionalExpression ||
current.type === AST_NODE_TYPES.DoWhileStatement ||
current.type === AST_NODE_TYPES.IfStatement ||
current.type === AST_NODE_TYPES.ForStatement ||
current.type === AST_NODE_TYPES.WhileStatement) &&
parents.has(current.test)
) {
return true;
}
if (parent.type === AST_NODE_TYPES.LogicalExpression) {
return isConditionalTest(parent);
}

if (
[
AST_NODE_TYPES.ArrowFunctionExpression,
AST_NODE_TYPES.FunctionExpression,
].includes(current.type)
) {
/**
* This is a weird situation like:
* `if (() => a || b) {}`
* `if (function () { return a || b }) {}`
*/
return false;
}
if (
parent.type === AST_NODE_TYPES.ConditionalExpression &&
(parent.consequent === node || parent.alternate === node)
) {
return isConditionalTest(parent);
}

if (
parent.type === AST_NODE_TYPES.SequenceExpression &&
parent.expressions.at(-1) === node
) {
return isConditionalTest(parent);
}

current = current.parent;
if (
(parent.type === AST_NODE_TYPES.ConditionalExpression ||
parent.type === AST_NODE_TYPES.DoWhileStatement ||
parent.type === AST_NODE_TYPES.IfStatement ||
parent.type === AST_NODE_TYPES.ForStatement ||
parent.type === AST_NODE_TYPES.WhileStatement) &&
parent.test === node
) {
return true;
}

return false;
}

function isBooleanConstructorContext(
node: TSESTree.Node,
context: Readonly<TSESLint.RuleContext<MessageIds, Options>>,
): boolean {
const parent = node.parent;
if (parent == null) {
return false;

Check warning on line 499 in packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts

View check run for this annotation

Codecov / codecov/patch

packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts#L499

Added line #L499 was not covered by tests
}

if (parent.type === AST_NODE_TYPES.LogicalExpression) {
return isBooleanConstructorContext(parent, context);
}

if (
parent.type === AST_NODE_TYPES.ConditionalExpression &&
(parent.consequent === node || parent.alternate === node)
) {
return isBooleanConstructorContext(parent, context);
}

if (
parent.type === AST_NODE_TYPES.SequenceExpression &&
parent.expressions.at(-1) === node
) {
return isBooleanConstructorContext(parent, context);
}

return isBuiltInBooleanCall(parent, context);
}

function isBuiltInBooleanCall(
node: TSESTree.Node,
context: Readonly<TSESLint.RuleContext<MessageIds, Options>>,
): boolean {
if (
node.type === AST_NODE_TYPES.CallExpression &&
node.callee.type === AST_NODE_TYPES.Identifier &&
// eslint-disable-next-line @typescript-eslint/internal/prefer-ast-types-enum
node.callee.name === 'Boolean' &&
node.arguments[0]
) {
const scope = context.sourceCode.getScope(node);
const variable = scope.set.get(AST_TOKEN_TYPES.Boolean);
return variable == null || variable.defs.length === 0;
}
return false;
}

Expand Down

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

Loading
Loading