Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
feat(eslint-plugin): [strict-boolean-expressions] add allow nullable …
…enum to strict boolean expressions (#6096)

* feat: add allowNullableEnum option

* test: add allowNullableEnum option

* fix: lint error

* chore: erase indent diff

* fix: error column number

* test: fix output

* test: fix output format

* test: add !nullableEnum pattern

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

Co-authored-by: Josh Goldberg <git@joshuakgoldberg.com>

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

Co-authored-by: Josh Goldberg <git@joshuakgoldberg.com>

* chore: yarn lint --fix

* perf: use some instead of length

* test: fix output

* test: split into two cases

* docs: add allowNullableEnum

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

Co-authored-by: Josh Goldberg <git@joshuakgoldberg.com>

* perf: use some instead of filter

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

Co-authored-by: Josh Goldberg <git@joshuakgoldberg.com>

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

Co-authored-by: Josh Goldberg <git@joshuakgoldberg.com>

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

Co-authored-by: Josh Goldberg <git@joshuakgoldberg.com>

* test: add case

* fix: add string condition

* Fix lil lint issue

* Test fix

---------

Co-authored-by: Josh Goldberg <git@joshuakgoldberg.com>
  • Loading branch information
kazizi55 and JoshuaKGoldberg committed Feb 5, 2023
1 parent f330e06 commit d4747cd
Show file tree
Hide file tree
Showing 3 changed files with 233 additions and 0 deletions.
Expand Up @@ -124,6 +124,12 @@ Allows `number | null | undefined` in a boolean context.
This is unsafe because nullable numbers can be either a falsy number or nullish.
Set this to `true` if you don't mind implicitly treating zero or NaN the same as a nullish value.

### `allowNullableEnum`

Allows `enum | null | undefined` in a boolean context.
This is unsafe because nullable enums can be either a falsy number or nullish.
Set this to `true` if you don't mind implicitly treating an enum whose value is zero the same as a nullish value.

### `allowAny`

Allows `any` in a boolean context.
Expand Down
43 changes: 43 additions & 0 deletions packages/eslint-plugin/src/rules/strict-boolean-expressions.ts
Expand Up @@ -13,6 +13,7 @@ export type Options = [
allowNullableBoolean?: boolean;
allowNullableString?: boolean;
allowNullableNumber?: boolean;
allowNullableEnum?: boolean;
allowAny?: boolean;
allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing?: boolean;
},
Expand All @@ -29,6 +30,7 @@ export type MessageId =
| 'conditionErrorNullableNumber'
| 'conditionErrorObject'
| 'conditionErrorNullableObject'
| 'conditionErrorNullableEnum'
| 'noStrictNullCheck'
| 'conditionFixDefaultFalse'
| 'conditionFixDefaultEmptyString'
Expand Down Expand Up @@ -63,6 +65,7 @@ export default util.createRule<Options, MessageId>({
allowNullableBoolean: { type: 'boolean' },
allowNullableString: { type: 'boolean' },
allowNullableNumber: { type: 'boolean' },
allowNullableEnum: { type: 'boolean' },
allowAny: { type: 'boolean' },
allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing: {
type: 'boolean',
Expand Down Expand Up @@ -102,6 +105,9 @@ export default util.createRule<Options, MessageId>({
conditionErrorNullableObject:
'Unexpected nullable object value in conditional. ' +
'An explicit null check is required.',
conditionErrorNullableEnum:
'Unexpected nullable enum value in conditional. ' +
'Please handle the nullish/zero/NaN cases explicitly.',
noStrictNullCheck:
'This rule requires the `strictNullChecks` compiler option to be turned on to function correctly.',

Expand Down Expand Up @@ -137,6 +143,7 @@ export default util.createRule<Options, MessageId>({
allowNullableBoolean: false,
allowNullableString: false,
allowNullableNumber: false,
allowNullableEnum: true,
allowAny: false,
allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing: false,
},
Expand Down Expand Up @@ -718,6 +725,35 @@ export default util.createRule<Options, MessageId>({
return;
}

// nullable enum
if (is('nullish', 'number', 'enum') || is('nullish', 'string', 'enum')) {
if (!options.allowNullableEnum) {
if (isLogicalNegationExpression(node.parent!)) {
context.report({
node,
messageId: 'conditionErrorNullableEnum',
fix: util.getWrappingFixer({
sourceCode,
node: node.parent,
innerNode: node,
wrap: code => `${code} == null`,
}),
});
} else {
context.report({
node,
messageId: 'conditionErrorNullableEnum',
fix: util.getWrappingFixer({
sourceCode,
node,
wrap: code => `${code} != null`,
}),
});
}
}
return;
}

// any
if (is('any')) {
if (!options.allowAny) {
Expand Down Expand Up @@ -753,6 +789,7 @@ export default util.createRule<Options, MessageId>({
| 'number'
| 'truthy number'
| 'object'
| 'enum'
| 'any'
| 'never';

Expand Down Expand Up @@ -814,6 +851,12 @@ export default util.createRule<Options, MessageId>({
}
}

if (
types.some(type => tsutils.isTypeFlagSet(type, ts.TypeFlags.EnumLike))
) {
variantTypes.add('enum');
}

if (
types.some(
type =>
Expand Down
184 changes: 184 additions & 0 deletions packages/eslint-plugin/tests/rules/strict-boolean-expressions.test.ts
Expand Up @@ -133,6 +133,39 @@ ruleTester.run('strict-boolean-expressions', rule, {
`,
}),

// nullable enum in boolean context
{
code: `
enum ExampleEnum {
This = 0,
That = 1,
}
const rand = Math.random();
let theEnum: ExampleEnum | null = null;
if (rand < 0.3) {
theEnum = ExampleEnum.This;
}
if (theEnum) {
}
`,
options: [{ allowNullableEnum: true }],
},
{
code: `
enum ExampleEnum {
This = 0,
That = 1,
}
const rand = Math.random();
let theEnum: ExampleEnum | null = null;
if (rand < 0.3) {
theEnum = ExampleEnum.This;
}
if (!theEnum) {
}
`,
options: [{ allowNullableEnum: true }],
},
{
code: `
declare const x: string[] | null;
Expand Down Expand Up @@ -965,6 +998,157 @@ if (y) {
],
}),

// nullable enum in boolean context
{
options: [{ allowNullableEnum: false }],
code: `
enum ExampleEnum {
This = 0,
That = 1,
}
const theEnum = Math.random() < 0.3 ? ExampleEnum.This : null;
if (theEnum) {
}
`,
errors: [
{
line: 7,
column: 13,
messageId: 'conditionErrorNullableEnum',
endLine: 7,
endColumn: 20,
},
],
output: `
enum ExampleEnum {
This = 0,
That = 1,
}
const theEnum = Math.random() < 0.3 ? ExampleEnum.This : null;
if (theEnum != null) {
}
`,
},
{
options: [{ allowNullableEnum: false }],
code: `
enum ExampleEnum {
This = 0,
That = 1,
}
const theEnum = Math.random() < 0.3 ? ExampleEnum.This : null;
if (!theEnum) {
}
`,
errors: [
{
line: 7,
column: 14,
messageId: 'conditionErrorNullableEnum',
endLine: 7,
endColumn: 21,
},
],
output: `
enum ExampleEnum {
This = 0,
That = 1,
}
const theEnum = Math.random() < 0.3 ? ExampleEnum.This : null;
if (theEnum == null) {
}
`,
},
{
options: [{ allowNullableEnum: false }],
code: `
enum ExampleEnum {
This,
That,
}
const theEnum = Math.random() < 0.3 ? ExampleEnum.This : null;
if (!theEnum) {
}
`,
errors: [
{
line: 7,
column: 14,
messageId: 'conditionErrorNullableEnum',
endLine: 7,
endColumn: 21,
},
],
output: `
enum ExampleEnum {
This,
That,
}
const theEnum = Math.random() < 0.3 ? ExampleEnum.This : null;
if (theEnum == null) {
}
`,
},
{
options: [{ allowNullableEnum: false }],
code: `
enum ExampleEnum {
This = '',
That = 'a',
}
const theEnum = Math.random() < 0.3 ? ExampleEnum.This : null;
if (!theEnum) {
}
`,
errors: [
{
line: 7,
column: 14,
messageId: 'conditionErrorNullableEnum',
endLine: 7,
endColumn: 21,
},
],
output: `
enum ExampleEnum {
This = '',
That = 'a',
}
const theEnum = Math.random() < 0.3 ? ExampleEnum.This : null;
if (theEnum == null) {
}
`,
},
{
options: [{ allowNullableEnum: false }],
code: `
enum ExampleEnum {
This = '',
That = 0,
}
const theEnum = Math.random() < 0.3 ? ExampleEnum.This : null;
if (!theEnum) {
}
`,
errors: [
{
line: 7,
column: 14,
messageId: 'conditionErrorNullableEnum',
endLine: 7,
endColumn: 21,
},
],
output: `
enum ExampleEnum {
This = '',
That = 0,
}
const theEnum = Math.random() < 0.3 ? ExampleEnum.This : null;
if (theEnum == null) {
}
`,
},
// any in boolean context
...batchedSingleLineTests<MessageId, Options>({
code: noFormat`
Expand Down

0 comments on commit d4747cd

Please sign in to comment.