Skip to content

Commit 06925f8

Browse files
authored
feat(eslint-plugin): [no-unnecessary-boolean-literal-compare] enforce strictNullChecks (#10712)
* feat(eslint-plugin): [no-unnecessary-boolean-literal-compare] enforce strictNullChecks * fixup
1 parent 884995d commit 06925f8

File tree

5 files changed

+92
-1
lines changed

5 files changed

+92
-1
lines changed

‎packages/eslint-plugin/docs/rules/no-unnecessary-boolean-literal-compare.mdx

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,22 @@ if (someNullCondition ?? true) {
129129
</TabItem>
130130
</Tabs>
131131

132+
### `allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing`
133+
134+
:::danger Deprecated
135+
136+
This option will be removed in the next major version of typescript-eslint.
137+
138+
:::
139+
140+
{/* insert option description */}
141+
142+
Without `strictNullChecks`, TypeScript essentially erases `undefined` and `null` from the types. This means when this rule inspects the types from a variable, **it will not be able to tell that the variable might be `null` or `undefined`**, which essentially makes this rule useless.
143+
144+
You should be using `strictNullChecks` to ensure complete type-safety in your codebase.
145+
146+
If for some reason you cannot turn on `strictNullChecks`, but still want to use this rule - you can use this option to allow it - but know that the behavior of this rule is _undefined_ with the compiler option turned off. We will not accept bug reports if you are using this option.
147+
132148
## Fixer
133149

134150
| Comparison | Fixer Output | Notes |

‎packages/eslint-plugin/src/rules/no-unnecessary-boolean-literal-compare.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@ export type MessageIds =
1616
| 'comparingNullableToTrueDirect'
1717
| 'comparingNullableToTrueNegated'
1818
| 'direct'
19-
| 'negated';
19+
| 'negated'
20+
| 'noStrictNullCheck';
2021

2122
export type Options = [
2223
{
2324
allowComparingNullableBooleansToFalse?: boolean;
2425
allowComparingNullableBooleansToTrue?: boolean;
26+
allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing?: boolean;
2527
},
2628
];
2729

@@ -57,6 +59,8 @@ export default createRule<Options, MessageIds>({
5759
'This expression unnecessarily compares a boolean value to a boolean instead of using it directly.',
5860
negated:
5961
'This expression unnecessarily compares a boolean value to a boolean instead of negating it.',
62+
noStrictNullCheck:
63+
'This rule requires the `strictNullChecks` compiler option to be turned on to function correctly.',
6064
},
6165
schema: [
6266
{
@@ -73,6 +77,11 @@ export default createRule<Options, MessageIds>({
7377
description:
7478
'Whether to allow comparisons between nullable boolean variables and `true`.',
7579
},
80+
allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing: {
81+
type: 'boolean',
82+
description:
83+
'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`.',
84+
},
7685
},
7786
},
7887
],
@@ -81,11 +90,31 @@ export default createRule<Options, MessageIds>({
8190
{
8291
allowComparingNullableBooleansToFalse: true,
8392
allowComparingNullableBooleansToTrue: true,
93+
allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing: false,
8494
},
8595
],
8696
create(context, [options]) {
8797
const services = getParserServices(context);
8898
const checker = services.program.getTypeChecker();
99+
const compilerOptions = services.program.getCompilerOptions();
100+
101+
const isStrictNullChecks = tsutils.isStrictCompilerOptionEnabled(
102+
compilerOptions,
103+
'strictNullChecks',
104+
);
105+
106+
if (
107+
!isStrictNullChecks &&
108+
options.allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing !== true
109+
) {
110+
context.report({
111+
loc: {
112+
start: { column: 0, line: 0 },
113+
end: { column: 0, line: 0 },
114+
},
115+
messageId: 'noStrictNullCheck',
116+
});
117+
}
89118

90119
function getBooleanComparison(
91120
node: TSESTree.BinaryExpression,

‎packages/eslint-plugin/tests/docs.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ describe('Validating rule docs', () => {
201201
'switch-exhaustiveness-check',
202202
'switch-exhaustiveness-check',
203203
'unbound-method',
204+
'no-unnecessary-boolean-literal-compare',
204205
]);
205206

206207
it('All rules must have a corresponding rule doc', () => {

‎packages/eslint-plugin/tests/rules/no-unnecessary-boolean-literal-compare.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { noFormat, RuleTester } from '@typescript-eslint/rule-tester';
2+
import * as path from 'node:path';
23

34
import rule from '../../src/rules/no-unnecessary-boolean-literal-compare';
45
import { getFixturesRootDir } from '../RuleTester';
@@ -123,6 +124,24 @@ const extendsUnknown: <T extends unknown>(
123124
}
124125
};
125126
`,
127+
{
128+
code: `
129+
function test(a?: boolean): boolean {
130+
// eslint-disable-next-line
131+
return a !== false;
132+
}
133+
`,
134+
languageOptions: {
135+
parserOptions: {
136+
tsconfigRootDir: path.join(rootDir, 'unstrict'),
137+
},
138+
},
139+
options: [
140+
{
141+
allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing: true,
142+
},
143+
],
144+
},
126145
],
127146

128147
invalid: [
@@ -586,5 +605,25 @@ const extendsUnknown: <T extends unknown>(
586605
};
587606
`,
588607
},
608+
{
609+
code: `
610+
function foo(): boolean {}
611+
`,
612+
errors: [
613+
{
614+
messageId: 'noStrictNullCheck',
615+
},
616+
],
617+
languageOptions: {
618+
parserOptions: {
619+
tsconfigRootDir: path.join(rootDir, 'unstrict'),
620+
},
621+
},
622+
options: [
623+
{
624+
allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing: false,
625+
},
626+
],
627+
},
589628
],
590629
});

‎packages/eslint-plugin/tests/schema-snapshots/no-unnecessary-boolean-literal-compare.shot

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)