Skip to content

Commit c713ca4

Browse files
phauxJamesHenry
authored andcommitted
feat(eslint-plugin): [strict-boolean-expressions] Add allowNullable option (#794)
1 parent 5715482 commit c713ca4

File tree

3 files changed

+64
-3
lines changed

3 files changed

+64
-3
lines changed

packages/eslint-plugin/docs/rules/strict-boolean-expressions.md

+1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ while (typeof str !== 'undefined') {
5656

5757
Options may be provided as an object with:
5858

59+
- `allowNullable` to allow `undefined` and `null` in addition to `boolean` as a type of all boolean expressions. (`false` by default).
5960
- `ignoreRhs` to skip the check on the right hand side of expressions like `a && b` or `a || b` - allows these operators to be used for their short-circuiting behavior. (`false` by default).
6061

6162
## Related To

packages/eslint-plugin/src/rules/strict-boolean-expressions.ts

+35-3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ type ExpressionWithTest =
1616
type Options = [
1717
{
1818
ignoreRhs?: boolean;
19+
allowNullable?: boolean;
1920
},
2021
];
2122

@@ -36,6 +37,9 @@ export default util.createRule<Options, 'strictBooleanExpression'>({
3637
ignoreRhs: {
3738
type: 'boolean',
3839
},
40+
allowNullable: {
41+
type: 'boolean',
42+
},
3943
},
4044
additionalProperties: false,
4145
},
@@ -47,9 +51,10 @@ export default util.createRule<Options, 'strictBooleanExpression'>({
4751
defaultOptions: [
4852
{
4953
ignoreRhs: false,
54+
allowNullable: false,
5055
},
5156
],
52-
create(context, [{ ignoreRhs }]) {
57+
create(context, [options]) {
5358
const service = util.getParserServices(context);
5459
const checker = service.program.getTypeChecker();
5560

@@ -61,7 +66,34 @@ export default util.createRule<Options, 'strictBooleanExpression'>({
6166
node,
6267
);
6368
const type = util.getConstrainedTypeAtLocation(checker, tsNode);
64-
return tsutils.isTypeFlagSet(type, ts.TypeFlags.BooleanLike);
69+
70+
if (tsutils.isTypeFlagSet(type, ts.TypeFlags.BooleanLike)) {
71+
return true;
72+
}
73+
74+
// Check variants of union
75+
if (tsutils.isTypeFlagSet(type, ts.TypeFlags.Union)) {
76+
let hasBoolean = false;
77+
for (const ty of (type as ts.UnionType).types) {
78+
if (tsutils.isTypeFlagSet(ty, ts.TypeFlags.BooleanLike)) {
79+
hasBoolean = true;
80+
continue;
81+
}
82+
if (options.allowNullable) {
83+
if (tsutils.isTypeFlagSet(ty, ts.TypeFlags.Null)) {
84+
continue;
85+
}
86+
if (tsutils.isTypeFlagSet(ty, ts.TypeFlags.Undefined)) {
87+
continue;
88+
}
89+
}
90+
// Union variant is something else
91+
return false;
92+
}
93+
return hasBoolean;
94+
}
95+
96+
return false;
6597
}
6698

6799
/**
@@ -88,7 +120,7 @@ export default util.createRule<Options, 'strictBooleanExpression'>({
88120
): void {
89121
if (
90122
!isBooleanType(node.left) ||
91-
(!ignoreRhs && !isBooleanType(node.right))
123+
(!options.ignoreRhs && !isBooleanType(node.right))
92124
) {
93125
reportNode(node);
94126
}

packages/eslint-plugin/tests/rules/strict-boolean-expressions.test.ts

+28
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,15 @@ const boolOrObj = bool || obj;
165165
const boolAndObj = bool && obj;
166166
`,
167167
},
168+
{
169+
options: [{ allowNullable: true }],
170+
code: `
171+
const f1 = (x?: boolean) => x ? 1 : 0;
172+
const f2 = (x: boolean | null) => x ? 1 : 0;
173+
const f3 = (x?: true | null) => x ? 1 : 0;
174+
const f4 = (x?: false) => x ? 1 : 0;
175+
`,
176+
},
168177
],
169178

170179
invalid: [
@@ -933,5 +942,24 @@ const objOrBool = obj || bool;
933942
const objAndBool = obj && bool;
934943
`,
935944
},
945+
{
946+
options: [{ allowNullable: true }],
947+
errors: [
948+
{
949+
messageId: 'strictBooleanExpression',
950+
line: 2,
951+
column: 44,
952+
},
953+
{
954+
messageId: 'strictBooleanExpression',
955+
line: 3,
956+
column: 35,
957+
},
958+
],
959+
code: `
960+
const f = (x: null | undefined) => x ? 1 : 0;
961+
const f = (x?: number) => x ? 1 : 0;
962+
`,
963+
},
936964
],
937965
});

0 commit comments

Comments
 (0)