From b427e6bd6b322c7a5a5eab5c5d5a5266a00bf4ff Mon Sep 17 00:00:00 2001 From: Omri Luzon Date: Sat, 18 Feb 2023 11:09:28 +0200 Subject: [PATCH 01/22] issue-4906 - prefer-nullish-coalescing enhancment --- .../src/rules/prefer-nullish-coalescing.ts | 59 +++++++++++++++++++ .../rules/prefer-nullish-coalescing.test.ts | 30 ++++++++++ 2 files changed, 89 insertions(+) diff --git a/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts b/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts index 14157427efa0..9de178f87538 100644 --- a/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts +++ b/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts @@ -1,6 +1,7 @@ import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES, AST_TOKEN_TYPES } from '@typescript-eslint/utils'; import * as tsutils from 'tsutils'; +import { isTypeFlagSet, isUnionOrIntersectionType } from 'tsutils'; import * as ts from 'typescript'; import * as util from '../util'; @@ -11,6 +12,11 @@ export type Options = [ ignoreTernaryTests?: boolean; ignoreMixedLogicalExpressions?: boolean; allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing?: boolean; + ignorePrimitives?: { + string?: boolean; + boolean?: boolean; + number?: boolean; + }; }, ]; @@ -56,6 +62,14 @@ export default util.createRule({ allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing: { type: 'boolean', }, + ignorePrimitives: { + type: 'object', + properties: { + string: { type: 'boolean' }, + boolean: { type: 'boolean' }, + number: { type: 'boolean' }, + }, + }, }, additionalProperties: false, }, @@ -67,6 +81,11 @@ export default util.createRule({ ignoreTernaryTests: true, ignoreMixedLogicalExpressions: true, allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing: false, + ignorePrimitives: { + string: false, + boolean: false, + number: false, + }, }, ], create( @@ -77,6 +96,7 @@ export default util.createRule({ ignoreTernaryTests, ignoreMixedLogicalExpressions, allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing, + ignorePrimitives, }, ], ) { @@ -279,6 +299,45 @@ export default util.createRule({ return; } + if (isUnionOrIntersectionType(type)) { + if ( + ignorePrimitives?.string && + type.types.some(t => isTypeFlagSet(t, ts.TypeFlags.String)) + ) { + return; + } + if ( + ignorePrimitives?.boolean && + type.types.some(t => isTypeFlagSet(t, ts.TypeFlags.BooleanLiteral)) + ) { + return; + } + if ( + ignorePrimitives?.number && + type.types.some(t => isTypeFlagSet(t, ts.TypeFlags.Number)) + ) { + return; + } + } + + // if (isUnionOrIntersectionType(type)) { + // if ( + // ( + // [ + // ['string', ts.TypeFlags.String], + // ['boolean', ts.TypeFlags.BooleanLiteral], + // ['number', ts.TypeFlags.Number], + // ] as const + // ).some( + // ([ignoredType, flag]) => + // ignorePrimitives?.[ignoredType] && + // type.types.some(t => isTypeFlagSet(t, flag)), + // ) + // ) { + // return; + // } + // } + const barBarOperator = util.nullThrows( sourceCode.getTokenAfter( node.left, diff --git a/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts b/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts index 49e50e741a89..8aee5847f17c 100644 --- a/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts @@ -20,6 +20,7 @@ const ruleTester = new RuleTester({ const types = ['string', 'number', 'boolean', 'object']; const nullishTypes = ['null', 'undefined', 'null | undefined']; +const ignorablePrimitiveTypes = ['string', 'number', 'boolean'] as const; function typeValidTest( cb: (type: string) => TSESLint.ValidTestCase | string, @@ -206,6 +207,13 @@ a && b || c || d; `, options: [{ ignoreMixedLogicalExpressions: true }], })), + ...ignorablePrimitiveTypes.map>(type => ({ + code: ` +declare const x: ${type} | undefined; +x || y; + `, + options: [{ ignorePrimitives: { [type]: true } }], + })), ], invalid: [ ...nullishTypeInvalidTest((nullish, type) => ({ @@ -751,5 +759,27 @@ declare const c: ${type}; }, ], })), + ...ignorablePrimitiveTypes.map< + TSESLint.InvalidTestCase + >(ignoreablePrimitive => ({ + code: ` +declare const x: ${ignoreablePrimitive} | undefined; +x || y; + `, + options: [ + { + ignorePrimitives: Object.fromEntries( + ignorablePrimitiveTypes + .filter(t => t !== ignoreablePrimitive) + .map(t => [t, true]), + ), + }, + ], + errors: [ + { + messageId: 'preferNullishOverOr', + }, + ], + })), ], }); From 8e016d372c4040276a086fe906f5a6c2451b67f1 Mon Sep 17 00:00:00 2001 From: Omri Luzon Date: Sat, 18 Feb 2023 11:13:34 +0200 Subject: [PATCH 02/22] remove comment --- .../src/rules/prefer-nullish-coalescing.ts | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts b/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts index 9de178f87538..ed6a93834d90 100644 --- a/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts +++ b/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts @@ -320,24 +320,6 @@ export default util.createRule({ } } - // if (isUnionOrIntersectionType(type)) { - // if ( - // ( - // [ - // ['string', ts.TypeFlags.String], - // ['boolean', ts.TypeFlags.BooleanLiteral], - // ['number', ts.TypeFlags.Number], - // ] as const - // ).some( - // ([ignoredType, flag]) => - // ignorePrimitives?.[ignoredType] && - // type.types.some(t => isTypeFlagSet(t, flag)), - // ) - // ) { - // return; - // } - // } - const barBarOperator = util.nullThrows( sourceCode.getTokenAfter( node.left, From 09f207abc07456db5de550a1031b5bb13b57134c Mon Sep 17 00:00:00 2001 From: Omri Luzon Date: Sat, 18 Feb 2023 11:25:45 +0200 Subject: [PATCH 03/22] add doc --- .../eslint-plugin/docs/rules/prefer-nullish-coalescing.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md b/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md index d2fc95f4066a..cd1201ac8e7d 100644 --- a/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md +++ b/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md @@ -129,6 +129,14 @@ a ?? (b && c) ?? d; a ?? (b && c && d); ``` +### `ignorePrimitives` +If you would like to ignore certain primitive types that can be falsy then you may pass an object containing a boolean value for each primitive: + +- Option entries in the object: + - `string: true`, ignores `null` or `undefined` unions with `string` (default: false). + - `number: true`, ignores `null` or `undefined` unions with `number` (default: false). + - `boolean: true`, ignores `null` or `undefined` unions with `boolean` (default: false). + **_NOTE:_** Errors for this specific case will be presented as suggestions (see below), instead of fixes. This is because it is not always safe to automatically convert `||` to `??` within a mixed logical expression, as we cannot tell the intended precedence of the operator. Note that by design, `??` requires parentheses when used with `&&` or `||` in the same expression. ## When Not To Use It From 24fb9c565145c67c764519ddc54c1bbd36052368 Mon Sep 17 00:00:00 2001 From: Omri Luzon Date: Fri, 24 Feb 2023 05:22:08 +0100 Subject: [PATCH 04/22] CR: sort keys --- .../src/rules/prefer-nullish-coalescing.ts | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts b/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts index ed6a93834d90..835abee87a8c 100644 --- a/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts +++ b/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts @@ -8,15 +8,15 @@ import * as util from '../util'; export type Options = [ { + allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing?: boolean; ignoreConditionalTests?: boolean; - ignoreTernaryTests?: boolean; ignoreMixedLogicalExpressions?: boolean; - allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing?: boolean; ignorePrimitives?: { - string?: boolean; boolean?: boolean; number?: boolean; + string?: boolean; }; + ignoreTernaryTests?: boolean; }, ]; @@ -50,26 +50,26 @@ export default util.createRule({ { type: 'object', properties: { - ignoreConditionalTests: { + allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing: { type: 'boolean', }, - ignoreTernaryTests: { + ignoreConditionalTests: { type: 'boolean', }, ignoreMixedLogicalExpressions: { type: 'boolean', }, - allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing: { - type: 'boolean', - }, ignorePrimitives: { type: 'object', properties: { - string: { type: 'boolean' }, boolean: { type: 'boolean' }, number: { type: 'boolean' }, + string: { type: 'boolean' }, }, }, + ignoreTernaryTests: { + type: 'boolean', + }, }, additionalProperties: false, }, @@ -77,14 +77,14 @@ export default util.createRule({ }, defaultOptions: [ { + allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing: false, ignoreConditionalTests: true, ignoreTernaryTests: true, ignoreMixedLogicalExpressions: true, - allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing: false, ignorePrimitives: { - string: false, boolean: false, number: false, + string: false, }, }, ], @@ -92,11 +92,11 @@ export default util.createRule({ context, [ { + allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing, ignoreConditionalTests, - ignoreTernaryTests, ignoreMixedLogicalExpressions, - allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing, ignorePrimitives, + ignoreTernaryTests, }, ], ) { From 7aec9fa719d47f4b5fedef4c244061e78c63b43a Mon Sep 17 00:00:00 2001 From: Omri Luzon Date: Fri, 24 Feb 2023 05:22:57 +0100 Subject: [PATCH 05/22] CR: import once --- .../src/rules/prefer-nullish-coalescing.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts b/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts index 835abee87a8c..0cbf349f4691 100644 --- a/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts +++ b/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts @@ -1,7 +1,6 @@ import type { TSESLint, TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES, AST_TOKEN_TYPES } from '@typescript-eslint/utils'; import * as tsutils from 'tsutils'; -import { isTypeFlagSet, isUnionOrIntersectionType } from 'tsutils'; import * as ts from 'typescript'; import * as util from '../util'; @@ -299,22 +298,24 @@ export default util.createRule({ return; } - if (isUnionOrIntersectionType(type)) { + if (tsutils.isUnionOrIntersectionType(type)) { if ( ignorePrimitives?.string && - type.types.some(t => isTypeFlagSet(t, ts.TypeFlags.String)) + type.types.some(t => tsutils.isTypeFlagSet(t, ts.TypeFlags.String)) ) { return; } if ( ignorePrimitives?.boolean && - type.types.some(t => isTypeFlagSet(t, ts.TypeFlags.BooleanLiteral)) + type.types.some(t => + tsutils.isTypeFlagSet(t, ts.TypeFlags.BooleanLiteral), + ) ) { return; } if ( ignorePrimitives?.number && - type.types.some(t => isTypeFlagSet(t, ts.TypeFlags.Number)) + type.types.some(t => tsutils.isTypeFlagSet(t, ts.TypeFlags.Number)) ) { return; } From ed7d5250c8145c30220198b744f0d40de1e2e099 Mon Sep 17 00:00:00 2001 From: Omri Luzon Date: Fri, 24 Feb 2023 05:23:30 +0100 Subject: [PATCH 06/22] CR: remove unncessary "as const" --- .../eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts b/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts index 8aee5847f17c..59cba8cf303b 100644 --- a/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts @@ -20,7 +20,7 @@ const ruleTester = new RuleTester({ const types = ['string', 'number', 'boolean', 'object']; const nullishTypes = ['null', 'undefined', 'null | undefined']; -const ignorablePrimitiveTypes = ['string', 'number', 'boolean'] as const; +const ignorablePrimitiveTypes = ['string', 'number', 'boolean']; function typeValidTest( cb: (type: string) => TSESLint.ValidTestCase | string, From 6be03609d3c3d1facafbc969d9271d2ec73a72d2 Mon Sep 17 00:00:00 2001 From: Omri Luzon Date: Sat, 27 May 2023 09:43:43 +0300 Subject: [PATCH 07/22] CR: Fix markdown lint rule --- packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md b/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md index cd1201ac8e7d..f44bb6d4d4f6 100644 --- a/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md +++ b/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md @@ -130,6 +130,7 @@ a ?? (b && c && d); ``` ### `ignorePrimitives` + If you would like to ignore certain primitive types that can be falsy then you may pass an object containing a boolean value for each primitive: - Option entries in the object: From a576095458b2cd4d2703a3141a9e52379a1b48c2 Mon Sep 17 00:00:00 2001 From: Omri Luzon Date: Sat, 27 May 2023 09:52:55 +0300 Subject: [PATCH 08/22] CR: Use binary OR to simplify loop --- .../src/rules/prefer-nullish-coalescing.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts b/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts index 0cbf349f4691..bbd884efb507 100644 --- a/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts +++ b/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts @@ -313,10 +313,14 @@ export default util.createRule({ ) { return; } - if ( - ignorePrimitives?.number && - type.types.some(t => tsutils.isTypeFlagSet(t, ts.TypeFlags.Number)) - ) { + const ignorableFlags = [ + ignorePrimitives?.boolean && ts.TypeFlags.BooleanLiteral, + ignorePrimitives?.number && ts.TypeFlags.Number, + ignorePrimitives?.string && ts.TypeFlags.String, + ] + .filter((flag): flag is number => flag !== undefined) + .reduce((previous, flag) => previous | flag, 0); + if (type.types.some(t => tsutils.isTypeFlagSet(t, ignorableFlags))) { return; } } From 8dd233f5135ce3f1c35c3b339109bb2eae1f078d Mon Sep 17 00:00:00 2001 From: Omri Luzon Date: Sat, 27 May 2023 10:20:51 +0300 Subject: [PATCH 09/22] CR: More positive test cases - Literal types (true, 1, etc.) - Template literals (`hello${string}`) - Bigints (1n) - Combinations of the types (boolean | string | ...), etc. --- .../rules/prefer-nullish-coalescing.test.ts | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts b/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts index 59cba8cf303b..3a5cf150f063 100644 --- a/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts @@ -781,5 +781,50 @@ x || y; }, ], })), + ...[ + // falsy + { ignoreablePrimitive: 'string', literalPrimitive: "''" }, + { ignoreablePrimitive: 'string', literalPrimitive: '``' }, + { ignoreablePrimitive: 'number', literalPrimitive: '0' }, + { ignoreablePrimitive: 'number', literalPrimitive: '0n' }, + { ignoreablePrimitive: 'boolean', literalPrimitive: 'false' }, + // truthy + { ignoreablePrimitive: 'string', literalPrimitive: "'a'" }, + { ignoreablePrimitive: 'string', literalPrimitive: "`hello${'string'}`" }, + { ignoreablePrimitive: 'number', literalPrimitive: '1' }, + { ignoreablePrimitive: 'number', literalPrimitive: '1n' }, + { ignoreablePrimitive: 'boolean', literalPrimitive: 'true' }, + // unions + { ignoreablePrimitive: 'string', literalPrimitive: "'a' | 'b'" }, + { ignoreablePrimitive: 'string', literalPrimitive: "'a' | `b`" }, + { ignoreablePrimitive: 'number', literalPrimitive: '0 | 1' }, + { ignoreablePrimitive: 'number', literalPrimitive: '1 | 2 | 3' }, + { ignoreablePrimitive: 'boolean', literalPrimitive: 'true | false' }, + { + ignoreablePrimitive: 'boolean', + literalPrimitive: 'true | false | null', + }, + ].map>( + ({ ignoreablePrimitive, literalPrimitive }) => ({ + code: ` + declare const x: ${literalPrimitive} | undefined; + x || y; + `, + options: [ + { + ignorePrimitives: Object.fromEntries( + ignorablePrimitiveTypes + .filter(t => t !== ignoreablePrimitive) + .map(t => [t, true]), + ), + }, + ], + errors: [ + { + messageId: 'preferNullishOverOr', + }, + ], + }), + ), ], }); From 25a085279f78455771a90fc0c1ace1b0884440d1 Mon Sep 17 00:00:00 2001 From: Omri Luzon Date: Sat, 27 May 2023 10:26:25 +0300 Subject: [PATCH 10/22] CR: mixed --- .../rules/prefer-nullish-coalescing.test.ts | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts b/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts index 3a5cf150f063..ad3d77befb15 100644 --- a/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts @@ -826,5 +826,43 @@ x || y; ], }), ), + { + code: ` +declare const x: 0 | 'foo' | undefined; +x || y; + `, + options: [ + { + ignorePrimitives: { + number: true, + string: true, + }, + }, + ], + errors: [ + { + messageId: 'preferNullishOverOr', + }, + ], + }, + { + code: ` +declare const x: 0 | 'foo' | undefined; +x || y; + `, + options: [ + { + ignorePrimitives: { + number: true, + string: false, + }, + }, + ], + errors: [ + { + messageId: 'preferNullishOverOr', + }, + ], + } ], }); From e66ee9b4d7c9cfe217cceb5250e5bbc18a8f5322 Mon Sep 17 00:00:00 2001 From: Omri Luzon Date: Sat, 27 May 2023 10:29:38 +0300 Subject: [PATCH 11/22] CR: remove implied line --- packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md b/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md index f44bb6d4d4f6..b1e1159f4f6d 100644 --- a/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md +++ b/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md @@ -133,7 +133,6 @@ a ?? (b && c && d); If you would like to ignore certain primitive types that can be falsy then you may pass an object containing a boolean value for each primitive: -- Option entries in the object: - `string: true`, ignores `null` or `undefined` unions with `string` (default: false). - `number: true`, ignores `null` or `undefined` unions with `number` (default: false). - `boolean: true`, ignores `null` or `undefined` unions with `boolean` (default: false). From 5c4ab38e90f41534bb85f9a1de38fd1a70919166 Mon Sep 17 00:00:00 2001 From: Omri Luzon Date: Sat, 27 May 2023 10:30:53 +0300 Subject: [PATCH 12/22] CR: Move the note up --- .../eslint-plugin/docs/rules/prefer-nullish-coalescing.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md b/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md index b1e1159f4f6d..36cd57b1a0f8 100644 --- a/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md +++ b/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md @@ -129,6 +129,8 @@ a ?? (b && c) ?? d; a ?? (b && c && d); ``` +**_NOTE:_** Errors for this specific case will be presented as suggestions (see below), instead of fixes. This is because it is not always safe to automatically convert `||` to `??` within a mixed logical expression, as we cannot tell the intended precedence of the operator. Note that by design, `??` requires parentheses when used with `&&` or `||` in the same expression. + ### `ignorePrimitives` If you would like to ignore certain primitive types that can be falsy then you may pass an object containing a boolean value for each primitive: @@ -137,8 +139,6 @@ If you would like to ignore certain primitive types that can be falsy then you m - `number: true`, ignores `null` or `undefined` unions with `number` (default: false). - `boolean: true`, ignores `null` or `undefined` unions with `boolean` (default: false). -**_NOTE:_** Errors for this specific case will be presented as suggestions (see below), instead of fixes. This is because it is not always safe to automatically convert `||` to `??` within a mixed logical expression, as we cannot tell the intended precedence of the operator. Note that by design, `??` requires parentheses when used with `&&` or `||` in the same expression. - ## When Not To Use It If you are not using TypeScript 3.7 (or greater), then you will not be able to use this rule, as the operator is not supported. From 3b1e012844f2882f92dddbe58f7bdedc6911c5ca Mon Sep 17 00:00:00 2001 From: Omri Luzon Date: Sat, 27 May 2023 10:37:51 +0300 Subject: [PATCH 13/22] prettier fixz --- .../eslint-plugin/docs/rules/prefer-nullish-coalescing.md | 6 +++--- .../tests/rules/prefer-nullish-coalescing.test.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md b/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md index 36cd57b1a0f8..a6d9b25e8e10 100644 --- a/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md +++ b/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md @@ -135,9 +135,9 @@ a ?? (b && c && d); If you would like to ignore certain primitive types that can be falsy then you may pass an object containing a boolean value for each primitive: - - `string: true`, ignores `null` or `undefined` unions with `string` (default: false). - - `number: true`, ignores `null` or `undefined` unions with `number` (default: false). - - `boolean: true`, ignores `null` or `undefined` unions with `boolean` (default: false). +- `string: true`, ignores `null` or `undefined` unions with `string` (default: false). +- `number: true`, ignores `null` or `undefined` unions with `number` (default: false). +- `boolean: true`, ignores `null` or `undefined` unions with `boolean` (default: false). ## When Not To Use It diff --git a/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts b/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts index ad3d77befb15..bd0a3b03e1aa 100644 --- a/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts @@ -863,6 +863,6 @@ x || y; messageId: 'preferNullishOverOr', }, ], - } + }, ], }); From 2e579a686f491e6ec9f139aee2f87082be47c30a Mon Sep 17 00:00:00 2001 From: Omri Luzon Date: Sat, 27 May 2023 11:49:16 +0300 Subject: [PATCH 14/22] Fix coverage, ts didn't know it has a default so it's not optional --- .../src/rules/prefer-nullish-coalescing.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts b/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts index bbd884efb507..b0929b563c4c 100644 --- a/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts +++ b/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts @@ -300,13 +300,13 @@ export default util.createRule({ if (tsutils.isUnionOrIntersectionType(type)) { if ( - ignorePrimitives?.string && + ignorePrimitives!.string && type.types.some(t => tsutils.isTypeFlagSet(t, ts.TypeFlags.String)) ) { return; } if ( - ignorePrimitives?.boolean && + ignorePrimitives!.boolean && type.types.some(t => tsutils.isTypeFlagSet(t, ts.TypeFlags.BooleanLiteral), ) @@ -314,9 +314,9 @@ export default util.createRule({ return; } const ignorableFlags = [ - ignorePrimitives?.boolean && ts.TypeFlags.BooleanLiteral, - ignorePrimitives?.number && ts.TypeFlags.Number, - ignorePrimitives?.string && ts.TypeFlags.String, + ignorePrimitives!.boolean && ts.TypeFlags.BooleanLiteral, + ignorePrimitives!.number && ts.TypeFlags.Number, + ignorePrimitives!.string && ts.TypeFlags.String, ] .filter((flag): flag is number => flag !== undefined) .reduce((previous, flag) => previous | flag, 0); From 7d30fe515bc3ad481edfc1994505948014ea39c5 Mon Sep 17 00:00:00 2001 From: Omri Luzon Date: Sat, 3 Jun 2023 09:49:48 +0300 Subject: [PATCH 15/22] CR1: Remove redundant check --- .../src/rules/prefer-nullish-coalescing.ts | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts b/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts index b0929b563c4c..3a08cb36fccf 100644 --- a/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts +++ b/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts @@ -299,20 +299,6 @@ export default util.createRule({ } if (tsutils.isUnionOrIntersectionType(type)) { - if ( - ignorePrimitives!.string && - type.types.some(t => tsutils.isTypeFlagSet(t, ts.TypeFlags.String)) - ) { - return; - } - if ( - ignorePrimitives!.boolean && - type.types.some(t => - tsutils.isTypeFlagSet(t, ts.TypeFlags.BooleanLiteral), - ) - ) { - return; - } const ignorableFlags = [ ignorePrimitives!.boolean && ts.TypeFlags.BooleanLiteral, ignorePrimitives!.number && ts.TypeFlags.Number, From 7907cf510dae9235cc2cc5d1dd996c84bbe7bf3d Mon Sep 17 00:00:00 2001 From: Omri Luzon Date: Sat, 3 Jun 2023 09:50:36 +0300 Subject: [PATCH 16/22] CR2: Remove redundant check for the type-system --- .../src/rules/prefer-nullish-coalescing.ts | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts b/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts index 3a08cb36fccf..c079fb6f8352 100644 --- a/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts +++ b/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts @@ -298,17 +298,19 @@ export default util.createRule({ return; } - if (tsutils.isUnionOrIntersectionType(type)) { - const ignorableFlags = [ - ignorePrimitives!.boolean && ts.TypeFlags.BooleanLiteral, - ignorePrimitives!.number && ts.TypeFlags.Number, - ignorePrimitives!.string && ts.TypeFlags.String, - ] - .filter((flag): flag is number => flag !== undefined) - .reduce((previous, flag) => previous | flag, 0); - if (type.types.some(t => tsutils.isTypeFlagSet(t, ignorableFlags))) { - return; - } + const ignorableFlags = [ + ignorePrimitives!.boolean && ts.TypeFlags.BooleanLiteral, + ignorePrimitives!.number && ts.TypeFlags.Number, + ignorePrimitives!.string && ts.TypeFlags.String, + ] + .filter((flag): flag is number => flag !== undefined) + .reduce((previous, flag) => previous | flag, 0); + if ( + (type as ts.UnionOrIntersectionType).types.some(t => + tsutils.isTypeFlagSet(t, ignorableFlags), + ) + ) { + return; } const barBarOperator = util.nullThrows( From 40c1839f7aaf336088759b20ee68887d11865e72 Mon Sep 17 00:00:00 2001 From: Omri Luzon Date: Sat, 3 Jun 2023 10:17:58 +0300 Subject: [PATCH 17/22] Add bigint option --- .../src/rules/prefer-nullish-coalescing.ts | 4 ++++ .../tests/rules/prefer-nullish-coalescing.test.ts | 15 ++++++++++++--- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts b/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts index c079fb6f8352..5a1a33d386fb 100644 --- a/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts +++ b/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts @@ -11,6 +11,7 @@ export type Options = [ ignoreConditionalTests?: boolean; ignoreMixedLogicalExpressions?: boolean; ignorePrimitives?: { + bigint?: boolean; boolean?: boolean; number?: boolean; string?: boolean; @@ -61,6 +62,7 @@ export default util.createRule({ ignorePrimitives: { type: 'object', properties: { + bigint: { type: 'boolean' }, boolean: { type: 'boolean' }, number: { type: 'boolean' }, string: { type: 'boolean' }, @@ -81,6 +83,7 @@ export default util.createRule({ ignoreTernaryTests: true, ignoreMixedLogicalExpressions: true, ignorePrimitives: { + bigint: false, boolean: false, number: false, string: false, @@ -299,6 +302,7 @@ export default util.createRule({ } const ignorableFlags = [ + ignorePrimitives!.bigint && ts.TypeFlags.BigInt, ignorePrimitives!.boolean && ts.TypeFlags.BooleanLiteral, ignorePrimitives!.number && ts.TypeFlags.Number, ignorePrimitives!.string && ts.TypeFlags.String, diff --git a/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts b/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts index bd0a3b03e1aa..9ef28d3e819e 100644 --- a/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts @@ -20,7 +20,7 @@ const ruleTester = new RuleTester({ const types = ['string', 'number', 'boolean', 'object']; const nullishTypes = ['null', 'undefined', 'null | undefined']; -const ignorablePrimitiveTypes = ['string', 'number', 'boolean']; +const ignorablePrimitiveTypes = ['string', 'number', 'boolean', 'bigint']; function typeValidTest( cb: (type: string) => TSESLint.ValidTestCase | string, @@ -786,20 +786,29 @@ x || y; { ignoreablePrimitive: 'string', literalPrimitive: "''" }, { ignoreablePrimitive: 'string', literalPrimitive: '``' }, { ignoreablePrimitive: 'number', literalPrimitive: '0' }, - { ignoreablePrimitive: 'number', literalPrimitive: '0n' }, + { ignoreablePrimitive: 'bigint', literalPrimitive: '0n' }, { ignoreablePrimitive: 'boolean', literalPrimitive: 'false' }, // truthy { ignoreablePrimitive: 'string', literalPrimitive: "'a'" }, { ignoreablePrimitive: 'string', literalPrimitive: "`hello${'string'}`" }, { ignoreablePrimitive: 'number', literalPrimitive: '1' }, - { ignoreablePrimitive: 'number', literalPrimitive: '1n' }, + { ignoreablePrimitive: 'bigint', literalPrimitive: '1n' }, { ignoreablePrimitive: 'boolean', literalPrimitive: 'true' }, // unions { ignoreablePrimitive: 'string', literalPrimitive: "'a' | 'b'" }, { ignoreablePrimitive: 'string', literalPrimitive: "'a' | `b`" }, { ignoreablePrimitive: 'number', literalPrimitive: '0 | 1' }, { ignoreablePrimitive: 'number', literalPrimitive: '1 | 2 | 3' }, + { ignoreablePrimitive: 'bigint', literalPrimitive: '0n | 1n' }, + { ignoreablePrimitive: 'bigint', literalPrimitive: '1n | 2n | 3n' }, { ignoreablePrimitive: 'boolean', literalPrimitive: 'true | false' }, + // Mixed unions + { ignoreablePrimitive: 'number', literalPrimitive: '0 | 1 | 0n | 1n' }, + { ignoreablePrimitive: 'bigint', literalPrimitive: '0 | 1 | 0n | 1n' }, + { + ignoreablePrimitive: 'number | bigint', + literalPrimitive: '0 | 1 | 0n | 1n', + }, { ignoreablePrimitive: 'boolean', literalPrimitive: 'true | false | null', From d085f34305cc5e46de30f6e3a072491fba315847 Mon Sep 17 00:00:00 2001 From: Omri Luzon Date: Sat, 3 Jun 2023 10:19:10 +0300 Subject: [PATCH 18/22] markdown too --- packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md b/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md index a6d9b25e8e10..4d36ef634f3a 100644 --- a/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md +++ b/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md @@ -137,6 +137,7 @@ If you would like to ignore certain primitive types that can be falsy then you m - `string: true`, ignores `null` or `undefined` unions with `string` (default: false). - `number: true`, ignores `null` or `undefined` unions with `number` (default: false). +- `bigint: true`, ignores `null` or `undefined` unions with `bigint` (default: false). - `boolean: true`, ignores `null` or `undefined` unions with `boolean` (default: false). ## When Not To Use It From 2dff212b7bfb2fc31a362a05c656ad3783f41fff Mon Sep 17 00:00:00 2001 From: Omri Luzon Date: Sat, 3 Jun 2023 10:35:50 +0300 Subject: [PATCH 19/22] Fixed union test and JSON.stringify(ignorePrimitives) --- .../rules/prefer-nullish-coalescing.test.ts | 86 +++++++++++-------- 1 file changed, 48 insertions(+), 38 deletions(-) diff --git a/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts b/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts index 9ef28d3e819e..df2a5bbd15f3 100644 --- a/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts @@ -783,57 +783,67 @@ x || y; })), ...[ // falsy - { ignoreablePrimitive: 'string', literalPrimitive: "''" }, - { ignoreablePrimitive: 'string', literalPrimitive: '``' }, - { ignoreablePrimitive: 'number', literalPrimitive: '0' }, - { ignoreablePrimitive: 'bigint', literalPrimitive: '0n' }, - { ignoreablePrimitive: 'boolean', literalPrimitive: 'false' }, + { ignoreablePrimitive: ['string'], literalPrimitive: "''" }, + { ignoreablePrimitive: ['string'], literalPrimitive: '``' }, + { ignoreablePrimitive: ['number'], literalPrimitive: '0' }, + { ignoreablePrimitive: ['bigint'], literalPrimitive: '0n' }, + { ignoreablePrimitive: ['boolean'], literalPrimitive: 'false' }, // truthy - { ignoreablePrimitive: 'string', literalPrimitive: "'a'" }, - { ignoreablePrimitive: 'string', literalPrimitive: "`hello${'string'}`" }, - { ignoreablePrimitive: 'number', literalPrimitive: '1' }, - { ignoreablePrimitive: 'bigint', literalPrimitive: '1n' }, - { ignoreablePrimitive: 'boolean', literalPrimitive: 'true' }, + { ignoreablePrimitive: ['string'], literalPrimitive: "'a'" }, + { + ignoreablePrimitive: ['string'], + literalPrimitive: "`hello${'string'}`", + }, + { ignoreablePrimitive: ['number'], literalPrimitive: '1' }, + { ignoreablePrimitive: ['bigint'], literalPrimitive: '1n' }, + { ignoreablePrimitive: ['boolean'], literalPrimitive: 'true' }, // unions - { ignoreablePrimitive: 'string', literalPrimitive: "'a' | 'b'" }, - { ignoreablePrimitive: 'string', literalPrimitive: "'a' | `b`" }, - { ignoreablePrimitive: 'number', literalPrimitive: '0 | 1' }, - { ignoreablePrimitive: 'number', literalPrimitive: '1 | 2 | 3' }, - { ignoreablePrimitive: 'bigint', literalPrimitive: '0n | 1n' }, - { ignoreablePrimitive: 'bigint', literalPrimitive: '1n | 2n | 3n' }, - { ignoreablePrimitive: 'boolean', literalPrimitive: 'true | false' }, + { ignoreablePrimitive: ['string'], literalPrimitive: "'a' | 'b'" }, + { ignoreablePrimitive: ['string'], literalPrimitive: "'a' | `b`" }, + { ignoreablePrimitive: ['number'], literalPrimitive: '0 | 1' }, + { ignoreablePrimitive: ['number'], literalPrimitive: '1 | 2 | 3' }, + { ignoreablePrimitive: ['bigint'], literalPrimitive: '0n | 1n' }, + { ignoreablePrimitive: ['bigint'], literalPrimitive: '1n | 2n | 3n' }, + { ignoreablePrimitive: ['boolean'], literalPrimitive: 'true | false' }, // Mixed unions - { ignoreablePrimitive: 'number', literalPrimitive: '0 | 1 | 0n | 1n' }, - { ignoreablePrimitive: 'bigint', literalPrimitive: '0 | 1 | 0n | 1n' }, { - ignoreablePrimitive: 'number | bigint', + ignoreablePrimitive: ['number'], + literalPrimitive: '0 | 1 | 0n | 1n', + }, + { + ignoreablePrimitive: ['bigint'], literalPrimitive: '0 | 1 | 0n | 1n', }, { - ignoreablePrimitive: 'boolean', + ignoreablePrimitive: ['number', 'bigint'], + literalPrimitive: '0 | 1 | 0n | 1n', + }, + { + ignoreablePrimitive: ['boolean'], literalPrimitive: 'true | false | null', }, ].map>( - ({ ignoreablePrimitive, literalPrimitive }) => ({ - code: ` + ({ ignoreablePrimitive, literalPrimitive }) => { + const ignorePrimitives = Object.fromEntries( + ignorablePrimitiveTypes.map(t => [ + t, + !ignoreablePrimitive.includes(t), + ]), + ); + return { + code: ` declare const x: ${literalPrimitive} | undefined; x || y; + // ${JSON.stringify(ignorePrimitives)} `, - options: [ - { - ignorePrimitives: Object.fromEntries( - ignorablePrimitiveTypes - .filter(t => t !== ignoreablePrimitive) - .map(t => [t, true]), - ), - }, - ], - errors: [ - { - messageId: 'preferNullishOverOr', - }, - ], - }), + options: [{ ignorePrimitives }], + errors: [ + { + messageId: 'preferNullishOverOr', + }, + ], + }; + }, ), { code: ` From 3747796610b7f2cf072ae1565f8c1d8ee1925c62 Mon Sep 17 00:00:00 2001 From: Omri Luzon Date: Sat, 8 Jul 2023 09:27:30 +0300 Subject: [PATCH 20/22] flattening map --- .../rules/prefer-nullish-coalescing.test.ts | 425 +++++++++++++++--- 1 file changed, 361 insertions(+), 64 deletions(-) diff --git a/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts b/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts index df2a5bbd15f3..d0a0c08ec09b 100644 --- a/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts @@ -781,70 +781,367 @@ x || y; }, ], })), - ...[ - // falsy - { ignoreablePrimitive: ['string'], literalPrimitive: "''" }, - { ignoreablePrimitive: ['string'], literalPrimitive: '``' }, - { ignoreablePrimitive: ['number'], literalPrimitive: '0' }, - { ignoreablePrimitive: ['bigint'], literalPrimitive: '0n' }, - { ignoreablePrimitive: ['boolean'], literalPrimitive: 'false' }, - // truthy - { ignoreablePrimitive: ['string'], literalPrimitive: "'a'" }, - { - ignoreablePrimitive: ['string'], - literalPrimitive: "`hello${'string'}`", - }, - { ignoreablePrimitive: ['number'], literalPrimitive: '1' }, - { ignoreablePrimitive: ['bigint'], literalPrimitive: '1n' }, - { ignoreablePrimitive: ['boolean'], literalPrimitive: 'true' }, - // unions - { ignoreablePrimitive: ['string'], literalPrimitive: "'a' | 'b'" }, - { ignoreablePrimitive: ['string'], literalPrimitive: "'a' | `b`" }, - { ignoreablePrimitive: ['number'], literalPrimitive: '0 | 1' }, - { ignoreablePrimitive: ['number'], literalPrimitive: '1 | 2 | 3' }, - { ignoreablePrimitive: ['bigint'], literalPrimitive: '0n | 1n' }, - { ignoreablePrimitive: ['bigint'], literalPrimitive: '1n | 2n | 3n' }, - { ignoreablePrimitive: ['boolean'], literalPrimitive: 'true | false' }, - // Mixed unions - { - ignoreablePrimitive: ['number'], - literalPrimitive: '0 | 1 | 0n | 1n', - }, - { - ignoreablePrimitive: ['bigint'], - literalPrimitive: '0 | 1 | 0n | 1n', - }, - { - ignoreablePrimitive: ['number', 'bigint'], - literalPrimitive: '0 | 1 | 0n | 1n', - }, - { - ignoreablePrimitive: ['boolean'], - literalPrimitive: 'true | false | null', - }, - ].map>( - ({ ignoreablePrimitive, literalPrimitive }) => { - const ignorePrimitives = Object.fromEntries( - ignorablePrimitiveTypes.map(t => [ - t, - !ignoreablePrimitive.includes(t), - ]), - ); - return { - code: ` - declare const x: ${literalPrimitive} | undefined; - x || y; - // ${JSON.stringify(ignorePrimitives)} - `, - options: [{ ignorePrimitives }], - errors: [ - { - messageId: 'preferNullishOverOr', - }, - ], - }; - }, - ), + // falsy + { + code: ` +declare const x: '' | undefined; +x || y; + `, + options: [ + { + ignorePrimitives: { + string: false, + number: true, + boolean: true, + bigint: true, + }, + }, + ], + errors: [{ messageId: 'preferNullishOverOr' }], + }, + { + code: ` +declare const x: \`\` | undefined; +x || y; + `, + options: [ + { + ignorePrimitives: { + string: false, + number: true, + boolean: true, + bigint: true, + }, + }, + ], + errors: [{ messageId: 'preferNullishOverOr' }], + }, + { + code: ` +declare const x: 0 | undefined; +x || y; + `, + options: [ + { + ignorePrimitives: { + string: true, + number: false, + boolean: true, + bigint: true, + }, + }, + ], + errors: [{ messageId: 'preferNullishOverOr' }], + }, + { + code: ` +declare const x: 0n | undefined; +x || y; + `, + options: [ + { + ignorePrimitives: { + string: true, + number: true, + boolean: true, + bigint: false, + }, + }, + ], + errors: [{ messageId: 'preferNullishOverOr' }], + }, + { + code: ` +declare const x: false | undefined; +x || y; + `, + options: [ + { + ignorePrimitives: { + string: true, + number: true, + boolean: false, + bigint: true, + }, + }, + ], + errors: [{ messageId: 'preferNullishOverOr' }], + }, + // truthy + { + code: ` +declare const x: 'a' | undefined; +x || y; + `, + options: [ + { + ignorePrimitives: { + string: false, + number: true, + boolean: true, + bigint: true, + }, + }, + ], + errors: [{ messageId: 'preferNullishOverOr' }], + }, + { + code: ` +declare const x: \`hello\${'string'}\` | undefined; +x || y; + `, + options: [ + { + ignorePrimitives: { + string: false, + number: true, + boolean: true, + bigint: true, + }, + }, + ], + errors: [{ messageId: 'preferNullishOverOr' }], + }, + { + code: ` +declare const x: 1 | undefined; +x || y; + `, + options: [ + { + ignorePrimitives: { + string: true, + number: false, + boolean: true, + bigint: true, + }, + }, + ], + errors: [{ messageId: 'preferNullishOverOr' }], + }, + { + code: ` +declare const x: 1n | undefined; +x || y; + `, + options: [ + { + ignorePrimitives: { + string: true, + number: true, + boolean: true, + bigint: false, + }, + }, + ], + errors: [{ messageId: 'preferNullishOverOr' }], + }, + { + code: ` +declare const x: true | undefined; +x || y; + `, + options: [ + { + ignorePrimitives: { + string: true, + number: true, + boolean: false, + bigint: true, + }, + }, + ], + errors: [{ messageId: 'preferNullishOverOr' }], + }, + // Unions of same primitive + { + code: ` +declare const x: 'a' | 'b' | undefined; +x || y; + `, + options: [ + { + ignorePrimitives: { + string: false, + number: true, + boolean: true, + bigint: true, + }, + }, + ], + errors: [{ messageId: 'preferNullishOverOr' }], + }, + { + code: ` +declare const x: 'a' | \`b\` | undefined; +x || y; + `, + options: [ + { + ignorePrimitives: { + string: false, + number: true, + boolean: true, + bigint: true, + }, + }, + ], + errors: [{ messageId: 'preferNullishOverOr' }], + }, + { + code: ` +declare const x: 0 | 1 | undefined; +x || y; + `, + options: [ + { + ignorePrimitives: { + string: true, + number: false, + boolean: true, + bigint: true, + }, + }, + ], + errors: [{ messageId: 'preferNullishOverOr' }], + }, + { + code: ` +declare const x: 1 | 2 | 3 | undefined; +x || y; + `, + options: [ + { + ignorePrimitives: { + string: true, + number: false, + boolean: true, + bigint: true, + }, + }, + ], + errors: [{ messageId: 'preferNullishOverOr' }], + }, + { + code: ` +declare const x: 0n | 1n | undefined; +x || y; + `, + options: [ + { + ignorePrimitives: { + string: true, + number: true, + boolean: true, + bigint: false, + }, + }, + ], + errors: [{ messageId: 'preferNullishOverOr' }], + }, + { + code: ` +declare const x: 1n | 2n | 3n | undefined; +x || y; + `, + options: [ + { + ignorePrimitives: { + string: true, + number: true, + boolean: true, + bigint: false, + }, + }, + ], + errors: [{ messageId: 'preferNullishOverOr' }], + }, + { + code: ` +declare const x: true | false | undefined; +x || y; + `, + options: [ + { + ignorePrimitives: { + string: true, + number: true, + boolean: false, + bigint: true, + }, + }, + ], + errors: [{ messageId: 'preferNullishOverOr' }], + }, + // Mixed unions + { + code: ` +declare const x: 0 | 1 | 0n | 1n | undefined; +x || y; + `, + options: [ + { + ignorePrimitives: { + string: true, + number: false, + boolean: true, + bigint: true, + }, + }, + ], + errors: [{ messageId: 'preferNullishOverOr' }], + }, + { + code: ` +declare const x: 0 | 1 | 0n | 1n | undefined; +x || y; + `, + options: [ + { + ignorePrimitives: { + string: true, + number: true, + boolean: true, + bigint: false, + }, + }, + ], + errors: [{ messageId: 'preferNullishOverOr' }], + }, + { + code: ` +declare const x: 0 | 1 | 0n | 1n | undefined; +x || y; + `, + options: [ + { + ignorePrimitives: { + string: true, + number: false, + boolean: true, + bigint: false, + }, + }, + ], + errors: [{ messageId: 'preferNullishOverOr' }], + }, + { + code: ` +declare const x: true | false | null | undefined; +x || y; + `, + options: [ + { + ignorePrimitives: { + string: true, + number: true, + boolean: false, + bigint: true, + }, + }, + ], + errors: [{ messageId: 'preferNullishOverOr' }], + }, { code: ` declare const x: 0 | 'foo' | undefined; From 4b6c03f5c557ae5d279069d720570036dd0ba635 Mon Sep 17 00:00:00 2001 From: Omri Luzon Date: Sat, 8 Jul 2023 09:29:28 +0300 Subject: [PATCH 21/22] flattening map 2 --- .../rules/prefer-nullish-coalescing.test.ts | 51 ++++++++++++++----- 1 file changed, 39 insertions(+), 12 deletions(-) diff --git a/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts b/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts index d0a0c08ec09b..a2f91d436aaa 100644 --- a/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts @@ -759,28 +759,55 @@ declare const c: ${type}; }, ], })), - ...ignorablePrimitiveTypes.map< - TSESLint.InvalidTestCase - >(ignoreablePrimitive => ({ + // default for missing option + { code: ` -declare const x: ${ignoreablePrimitive} | undefined; +declare const x: string | undefined; x || y; `, options: [ { - ignorePrimitives: Object.fromEntries( - ignorablePrimitiveTypes - .filter(t => t !== ignoreablePrimitive) - .map(t => [t, true]), - ), + ignorePrimitives: { number: true, boolean: true, bigint: true }, }, ], - errors: [ + errors: [{ messageId: 'preferNullishOverOr' }], + }, + { + code: ` +declare const x: number | undefined; +x || y; + `, + options: [ { - messageId: 'preferNullishOverOr', + ignorePrimitives: { string: true, boolean: true, bigint: true }, }, ], - })), + errors: [{ messageId: 'preferNullishOverOr' }], + }, + { + code: ` +declare const x: boolean | undefined; +x || y; + `, + options: [ + { + ignorePrimitives: { string: true, number: true, bigint: true }, + }, + ], + errors: [{ messageId: 'preferNullishOverOr' }], + }, + { + code: ` +declare const x: bigint | undefined; +x || y; + `, + options: [ + { + ignorePrimitives: { string: true, number: true, boolean: true }, + }, + ], + errors: [{ messageId: 'preferNullishOverOr' }], + }, // falsy { code: ` From 7207e0533581002c8fe68b5866a1b5e09c1f7e9a Mon Sep 17 00:00:00 2001 From: Omri Luzon Date: Sat, 8 Jul 2023 09:41:04 +0300 Subject: [PATCH 22/22] add examples to docs --- .../docs/rules/prefer-nullish-coalescing.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md b/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md index 4d36ef634f3a..146e96a19fa3 100644 --- a/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md +++ b/packages/eslint-plugin/docs/rules/prefer-nullish-coalescing.md @@ -140,6 +140,20 @@ If you would like to ignore certain primitive types that can be falsy then you m - `bigint: true`, ignores `null` or `undefined` unions with `bigint` (default: false). - `boolean: true`, ignores `null` or `undefined` unions with `boolean` (default: false). +Incorrect code for `ignorePrimitives: { string: true }`, and correct code for `ignorePrimitives: { string: false }`: + +```ts +const foo: string | undefined = 'bar'; +foo || 'a string'; +``` + +Correct code for `ignorePrimitives: { string: true }`: + +```ts +const foo: string | undefined = 'bar'; +foo ?? 'a string'; +``` + ## When Not To Use It If you are not using TypeScript 3.7 (or greater), then you will not be able to use this rule, as the operator is not supported.