From 1e3281a0b64432b813ff943625451d8d6d0e31e3 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Tue, 15 Oct 2024 07:21:47 -0500 Subject: [PATCH 01/10] extract current LogicalExpression logic into function --- .../src/rules/prefer-nullish-coalescing.ts | 169 +++++++++--------- 1 file changed, 89 insertions(+), 80 deletions(-) diff --git a/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts b/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts index 94424989b955..4afcdc7ff7d5 100644 --- a/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts +++ b/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts @@ -157,6 +157,94 @@ export default createRule({ }); } + // todo: rename to something more specific? + function checkAssignmentOrLogicalExpression( + node: TSESTree.AssignmentExpression | TSESTree.LogicalExpression, + description: string, + equals: string, + ): void { + const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); + const type = checker.getTypeAtLocation(tsNode.left); + if (!isTypeFlagSet(type, ts.TypeFlags.Null | ts.TypeFlags.Undefined)) { + return; + } + + if (ignoreConditionalTests === true && isConditionalTest(node)) { + return; + } + + const isMixedLogical = isMixedLogicalExpression(node); + if (ignoreMixedLogicalExpressions === true && isMixedLogical) { + return; + } + + // https://github.com/typescript-eslint/typescript-eslint/issues/5439 + /* eslint-disable @typescript-eslint/no-non-null-assertion */ + const ignorableFlags = [ + (ignorePrimitives === true || ignorePrimitives!.bigint) && + ts.TypeFlags.BigIntLike, + (ignorePrimitives === true || ignorePrimitives!.boolean) && + ts.TypeFlags.BooleanLike, + (ignorePrimitives === true || ignorePrimitives!.number) && + ts.TypeFlags.NumberLike, + (ignorePrimitives === true || ignorePrimitives!.string) && + ts.TypeFlags.StringLike, + ] + .filter((flag): flag is number => typeof flag === 'number') + .reduce((previous, flag) => previous | flag, 0); + if ( + type.flags !== ts.TypeFlags.Null && + type.flags !== ts.TypeFlags.Undefined && + (type as ts.UnionOrIntersectionType).types.some(t => + tsutils + .intersectionTypeParts(t) + .some(t => tsutils.isTypeFlagSet(t, ignorableFlags)), + ) + ) { + return; + } + /* eslint-enable @typescript-eslint/no-non-null-assertion */ + + const barBarOperator = nullThrows( + context.sourceCode.getTokenAfter( + node.left, + token => + token.type === AST_TOKEN_TYPES.Punctuator && + token.value === node.operator, + ), + NullThrowsReasons.MissingToken('operator', node.type), + ); + + function* fix( + fixer: TSESLint.RuleFixer, + ): IterableIterator { + if (isLogicalOrOperator(node.parent)) { + // '&&' and '??' operations cannot be mixed without parentheses (e.g. a && b ?? c) + if ( + node.left.type === AST_NODE_TYPES.LogicalExpression && + !isLogicalOrOperator(node.left.left) + ) { + yield fixer.insertTextBefore(node.left.right, '('); + } else { + yield fixer.insertTextBefore(node.left, '('); + } + yield fixer.insertTextAfter(node.right, ')'); + } + yield fixer.replaceText(barBarOperator, '??'); + } + + context.report({ + node: barBarOperator, + messageId: 'preferNullishOverOr', + suggest: [ + { + messageId: 'suggestNullish', + fix, + }, + ], + }); + } + return { ConditionalExpression(node: TSESTree.ConditionalExpression): void { if (ignoreTernaryTests) { @@ -315,86 +403,7 @@ export default createRule({ 'LogicalExpression[operator = "||"]'( node: TSESTree.LogicalExpression, ): void { - const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); - const type = checker.getTypeAtLocation(tsNode.left); - if (!isTypeFlagSet(type, ts.TypeFlags.Null | ts.TypeFlags.Undefined)) { - return; - } - - if (ignoreConditionalTests === true && isConditionalTest(node)) { - return; - } - - const isMixedLogical = isMixedLogicalExpression(node); - if (ignoreMixedLogicalExpressions === true && isMixedLogical) { - return; - } - - // https://github.com/typescript-eslint/typescript-eslint/issues/5439 - /* eslint-disable @typescript-eslint/no-non-null-assertion */ - const ignorableFlags = [ - (ignorePrimitives === true || ignorePrimitives!.bigint) && - ts.TypeFlags.BigIntLike, - (ignorePrimitives === true || ignorePrimitives!.boolean) && - ts.TypeFlags.BooleanLike, - (ignorePrimitives === true || ignorePrimitives!.number) && - ts.TypeFlags.NumberLike, - (ignorePrimitives === true || ignorePrimitives!.string) && - ts.TypeFlags.StringLike, - ] - .filter((flag): flag is number => typeof flag === 'number') - .reduce((previous, flag) => previous | flag, 0); - if ( - type.flags !== ts.TypeFlags.Null && - type.flags !== ts.TypeFlags.Undefined && - (type as ts.UnionOrIntersectionType).types.some(t => - tsutils - .intersectionTypeParts(t) - .some(t => tsutils.isTypeFlagSet(t, ignorableFlags)), - ) - ) { - return; - } - /* eslint-enable @typescript-eslint/no-non-null-assertion */ - - const barBarOperator = nullThrows( - context.sourceCode.getTokenAfter( - node.left, - token => - token.type === AST_TOKEN_TYPES.Punctuator && - token.value === node.operator, - ), - NullThrowsReasons.MissingToken('operator', node.type), - ); - - function* fix( - fixer: TSESLint.RuleFixer, - ): IterableIterator { - if (isLogicalOrOperator(node.parent)) { - // '&&' and '??' operations cannot be mixed without parentheses (e.g. a && b ?? c) - if ( - node.left.type === AST_NODE_TYPES.LogicalExpression && - !isLogicalOrOperator(node.left.left) - ) { - yield fixer.insertTextBefore(node.left.right, '('); - } else { - yield fixer.insertTextBefore(node.left, '('); - } - yield fixer.insertTextAfter(node.right, ')'); - } - yield fixer.replaceText(barBarOperator, '??'); - } - - context.report({ - node: barBarOperator, - messageId: 'preferNullishOverOr', - suggest: [ - { - messageId: 'suggestNullish', - fix, - }, - ], - }); + checkAssignmentOrLogicalExpression(node, 'or', ''); }, }; }, From 3d6a209f76aa45bc5c9389ba5f9595297227ec7c Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Tue, 15 Oct 2024 07:25:23 -0500 Subject: [PATCH 02/10] pull in other changes to rule file without edits --- .../src/rules/prefer-nullish-coalescing.ts | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts b/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts index 4afcdc7ff7d5..d1dc7b580bd8 100644 --- a/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts +++ b/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts @@ -56,10 +56,10 @@ export default createRule({ noStrictNullCheck: 'This rule requires the `strictNullChecks` compiler option to be turned on to function correctly.', preferNullishOverOr: - 'Prefer using nullish coalescing operator (`??`) instead of a logical or (`||`), as it is a safer operator.', + 'Prefer using nullish coalescing operator (`??{{ equals }}`) instead of a logical {{ description }} (`||{{ equals }}`), as it is a safer operator.', preferNullishOverTernary: - 'Prefer using nullish coalescing operator (`??`) instead of a ternary expression, as it is simpler to read.', - suggestNullish: 'Fix to nullish coalescing operator (`??`).', + 'Prefer using nullish coalescing operator (`??{{ equals }}`) instead of a ternary expression, as it is simpler to read.', + suggestNullish: 'Fix to nullish coalescing operator (`??{{ equals }}`).', }, schema: [ { @@ -274,7 +274,7 @@ export default createRule({ node.test.right.left, node.test.right.right, ]; - if (node.test.operator === '||') { + if (['||', '||='].includes(node.test.operator)) { if ( node.test.left.operator === '===' && node.test.right.operator === '===' @@ -376,10 +376,13 @@ export default createRule({ if (isFixable) { context.report({ + // TODO: also account for = in the ternary clause + data: { equals: '' }, node, messageId: 'preferNullishOverTernary', suggest: [ { + data: { equals: '' }, messageId: 'suggestNullish', fix(fixer: TSESLint.RuleFixer): TSESLint.RuleFix { const [left, right] = @@ -399,7 +402,11 @@ export default createRule({ }); } }, - + 'AssignmentExpression[operator = "||="]'( + node: TSESTree.AssignmentExpression, + ): void { + checkAssignmentOrLogicalExpression(node, 'assignment', '='); + }, 'LogicalExpression[operator = "||"]'( node: TSESTree.LogicalExpression, ): void { @@ -446,7 +453,9 @@ function isConditionalTest(node: TSESTree.Node): boolean { return false; } -function isMixedLogicalExpression(node: TSESTree.LogicalExpression): boolean { +function isMixedLogicalExpression( + node: TSESTree.AssignmentExpression | TSESTree.LogicalExpression, +): boolean { const seen = new Set(); const queue = [node.parent, node.left, node.right]; for (const current of queue) { @@ -458,7 +467,7 @@ function isMixedLogicalExpression(node: TSESTree.LogicalExpression): boolean { if (current.type === AST_NODE_TYPES.LogicalExpression) { if (current.operator === '&&') { return true; - } else if (current.operator === '||') { + } else if (['||', '||='].includes(current.operator)) { // check the pieces of the node to catch cases like `a || b || c && d` queue.push(current.parent, current.left, current.right); } From df78ea37d03ae35f9cc7a511a42ae8b966030005 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Tue, 15 Oct 2024 07:34:47 -0500 Subject: [PATCH 03/10] pull over genericizations for checkAssignmentOrLogicalExpression --- .../src/rules/prefer-nullish-coalescing.ts | 13 ++++++++++--- 1 file changed, 10 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 d1dc7b580bd8..ec651641e19a 100644 --- a/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts +++ b/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts @@ -173,8 +173,10 @@ export default createRule({ return; } - const isMixedLogical = isMixedLogicalExpression(node); - if (ignoreMixedLogicalExpressions === true && isMixedLogical) { + if ( + ignoreMixedLogicalExpressions === true && + isMixedLogicalExpression(node) + ) { return; } @@ -230,14 +232,19 @@ export default createRule({ } yield fixer.insertTextAfter(node.right, ')'); } - yield fixer.replaceText(barBarOperator, '??'); + yield fixer.replaceText( + barBarOperator, + node.operator.replace('||', '??'), + ); } context.report({ + data: { equals, description }, node: barBarOperator, messageId: 'preferNullishOverOr', suggest: [ { + data: { equals }, messageId: 'suggestNullish', fix, }, From ff5db69f57e012c90e09e855324917b78470fc0f Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Tue, 15 Oct 2024 07:35:52 -0500 Subject: [PATCH 04/10] lint autofix --- .../src/rules/prefer-nullish-coalescing.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts b/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts index ec651641e19a..24a8dbc71797 100644 --- a/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts +++ b/packages/eslint-plugin/src/rules/prefer-nullish-coalescing.ts @@ -239,13 +239,13 @@ export default createRule({ } context.report({ - data: { equals, description }, node: barBarOperator, messageId: 'preferNullishOverOr', + data: { description, equals }, suggest: [ { - data: { equals }, messageId: 'suggestNullish', + data: { equals }, fix, }, ], @@ -253,6 +253,11 @@ export default createRule({ } return { + 'AssignmentExpression[operator = "||="]'( + node: TSESTree.AssignmentExpression, + ): void { + checkAssignmentOrLogicalExpression(node, 'assignment', '='); + }, ConditionalExpression(node: TSESTree.ConditionalExpression): void { if (ignoreTernaryTests) { return; @@ -383,14 +388,14 @@ export default createRule({ if (isFixable) { context.report({ - // TODO: also account for = in the ternary clause - data: { equals: '' }, node, messageId: 'preferNullishOverTernary', + // TODO: also account for = in the ternary clause + data: { equals: '' }, suggest: [ { - data: { equals: '' }, messageId: 'suggestNullish', + data: { equals: '' }, fix(fixer: TSESLint.RuleFixer): TSESLint.RuleFix { const [left, right] = operator === '===' || operator === '==' @@ -409,11 +414,6 @@ export default createRule({ }); } }, - 'AssignmentExpression[operator = "||="]'( - node: TSESTree.AssignmentExpression, - ): void { - checkAssignmentOrLogicalExpression(node, 'assignment', '='); - }, 'LogicalExpression[operator = "||"]'( node: TSESTree.LogicalExpression, ): void { From 2a705a3adb7e6463cc3fc30308f3e83fdd45e4cb Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Tue, 15 Oct 2024 07:53:18 -0500 Subject: [PATCH 05/10] port over test changes --- .../rules/prefer-nullish-coalescing.test.ts | 126 +++++++++--------- 1 file changed, 66 insertions(+), 60 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 bbd655fa5d9a..d1dc20916e6a 100644 --- a/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts @@ -30,31 +30,37 @@ const nullishTypes = ['null', 'undefined', 'null | undefined']; const ignorablePrimitiveTypes = ['string', 'number', 'boolean', 'bigint']; function typeValidTest( - cb: (type: string) => ValidTestCase | string, + cb: (type: string, equals: '' | '=') => ValidTestCase | string, ): (ValidTestCase | string)[] { - return types.map(type => cb(type)); + return [ + ...types.map(type => cb(type, '')), + ...types.map(type => cb(type, '=')), + ]; } + function nullishTypeTest< T extends | InvalidTestCase | ValidTestCase | string, ->(cb: (nullish: string, type: string) => T): T[] { - return nullishTypes.flatMap(nullish => types.map(type => cb(nullish, type))); +>(cb: (nullish: string, type: string, equals: string) => T): T[] { + return nullishTypes.flatMap(nullish => + types.flatMap(type => [cb(nullish, type, ''), cb(nullish, type, '=')]), + ); } ruleTester.run('prefer-nullish-coalescing', rule, { valid: [ ...typeValidTest( - type => ` + (type, equals) => ` declare const x: ${type}; -x || 'foo'; +(x ||${equals} 'foo'); `, ), ...nullishTypeTest( - (nullish, type) => ` + (nullish, type, equals) => ` declare const x: ${type} | ${nullish}; -x ?? 'foo'; +x ??${equals} 'foo'; `, ), @@ -120,34 +126,34 @@ x === null ? x : y; })), // ignoreConditionalTests - ...nullishTypeTest((nullish, type) => ({ + ...nullishTypeTest((nullish, type, equals) => ({ code: ` declare const x: ${type} | ${nullish}; -x || 'foo' ? null : null; +(x ||${equals} 'foo') ? null : null; `, })), - ...nullishTypeTest((nullish, type) => ({ + ...nullishTypeTest((nullish, type, equals) => ({ code: ` declare const x: ${type} | ${nullish}; -if (x || 'foo') {} +if ((x ||${equals} 'foo')) {} `, })), - ...nullishTypeTest((nullish, type) => ({ + ...nullishTypeTest((nullish, type, equals) => ({ code: ` declare const x: ${type} | ${nullish}; -do {} while (x || 'foo') +do {} while ((x ||${equals} 'foo')) `, })), - ...nullishTypeTest((nullish, type) => ({ + ...nullishTypeTest((nullish, type, equals) => ({ code: ` declare const x: ${type} | ${nullish}; -for (;x || 'foo';) {} +for (;(x ||${equals} 'foo');) {} `, })), - ...nullishTypeTest((nullish, type) => ({ + ...nullishTypeTest((nullish, type, equals) => ({ code: ` declare const x: ${type} | ${nullish}; -while (x || 'foo') {} +while ((x ||${equals} 'foo')) {} `, })), @@ -358,15 +364,15 @@ x || y; }, ], invalid: [ - ...nullishTypeTest((nullish, type) => ({ + ...nullishTypeTest((nullish, type, equals) => ({ code: ` declare const x: ${type} | ${nullish}; -x || 'foo'; +(x ||${equals} 'foo'); `, errors: [ { - column: 3, - endColumn: 5, + column: 4, + endColumn: 6 + equals.length, endLine: 3, line: 3, messageId: 'preferNullishOverOr', @@ -375,7 +381,7 @@ x || 'foo'; messageId: 'suggestNullish', output: ` declare const x: ${type} | ${nullish}; -x ?? 'foo'; +(x ??${equals} 'foo'); `, }, ], @@ -510,7 +516,7 @@ undefined !== x ? x : y; `, ` declare const x: string | undefined; -x === undefined ? y : x; +undefined === x ? y : x; `, ` declare const x: string | undefined; @@ -526,7 +532,7 @@ null !== x ? x : y; `, ` declare const x: string | null; -x === null ? y : x; +null === x ? y : x; `, ` declare const x: string | null; @@ -579,15 +585,15 @@ if (x) { }, // ignoreConditionalTests - ...nullishTypeTest((nullish, type) => ({ + ...nullishTypeTest((nullish, type, equals) => ({ code: ` declare const x: ${type} | ${nullish}; -x || 'foo' ? null : null; +(x ||${equals} 'foo') ? null : null; `, errors: [ { - column: 3, - endColumn: 5, + column: 4, + endColumn: 6 + equals.length, endLine: 3, line: 3, messageId: 'preferNullishOverOr', @@ -596,7 +602,7 @@ x || 'foo' ? null : null; messageId: 'suggestNullish', output: ` declare const x: ${type} | ${nullish}; -x ?? 'foo' ? null : null; +(x ??${equals} 'foo') ? null : null; `, }, ], @@ -605,15 +611,15 @@ x ?? 'foo' ? null : null; options: [{ ignoreConditionalTests: false }], output: null, })), - ...nullishTypeTest((nullish, type) => ({ + ...nullishTypeTest((nullish, type, equals) => ({ code: ` declare const x: ${type} | ${nullish}; -if (x || 'foo') {} +if ((x ||${equals} 'foo')) {} `, errors: [ { - column: 7, - endColumn: 9, + column: 8, + endColumn: 10 + equals.length, endLine: 3, line: 3, messageId: 'preferNullishOverOr', @@ -622,7 +628,7 @@ if (x || 'foo') {} messageId: 'suggestNullish', output: ` declare const x: ${type} | ${nullish}; -if (x ?? 'foo') {} +if ((x ??${equals} 'foo')) {} `, }, ], @@ -631,15 +637,15 @@ if (x ?? 'foo') {} options: [{ ignoreConditionalTests: false }], output: null, })), - ...nullishTypeTest((nullish, type) => ({ + ...nullishTypeTest((nullish, type, equals) => ({ code: ` declare const x: ${type} | ${nullish}; -do {} while (x || 'foo') +do {} while ((x ||${equals} 'foo')) `, errors: [ { - column: 16, - endColumn: 18, + column: 17, + endColumn: 19 + equals.length, endLine: 3, line: 3, messageId: 'preferNullishOverOr', @@ -648,7 +654,7 @@ do {} while (x || 'foo') messageId: 'suggestNullish', output: ` declare const x: ${type} | ${nullish}; -do {} while (x ?? 'foo') +do {} while ((x ??${equals} 'foo')) `, }, ], @@ -657,15 +663,15 @@ do {} while (x ?? 'foo') options: [{ ignoreConditionalTests: false }], output: null, })), - ...nullishTypeTest((nullish, type) => ({ + ...nullishTypeTest((nullish, type, equals) => ({ code: ` declare const x: ${type} | ${nullish}; -for (;x || 'foo';) {} +for (;(x ||${equals} 'foo');) {} `, errors: [ { - column: 9, - endColumn: 11, + column: 10, + endColumn: 12 + equals.length, endLine: 3, line: 3, messageId: 'preferNullishOverOr', @@ -674,7 +680,7 @@ for (;x || 'foo';) {} messageId: 'suggestNullish', output: ` declare const x: ${type} | ${nullish}; -for (;x ?? 'foo';) {} +for (;(x ??${equals} 'foo');) {} `, }, ], @@ -683,15 +689,15 @@ for (;x ?? 'foo';) {} options: [{ ignoreConditionalTests: false }], output: null, })), - ...nullishTypeTest((nullish, type) => ({ + ...nullishTypeTest((nullish, type, equals) => ({ code: ` declare const x: ${type} | ${nullish}; -while (x || 'foo') {} +while ((x ||${equals} 'foo')) {} `, errors: [ { - column: 10, - endColumn: 12, + column: 11, + endColumn: 13 + equals.length, endLine: 3, line: 3, messageId: 'preferNullishOverOr', @@ -700,7 +706,7 @@ while (x || 'foo') {} messageId: 'suggestNullish', output: ` declare const x: ${type} | ${nullish}; -while (x ?? 'foo') {} +while ((x ??${equals} 'foo')) {} `, }, ], @@ -842,15 +848,15 @@ a && b || c ?? d; })), // should not false positive for functions inside conditional tests - ...nullishTypeTest((nullish, type) => ({ + ...nullishTypeTest((nullish, type, equals) => ({ code: ` declare const x: ${type} | ${nullish}; -if (() => x || 'foo') {} +if (() => (x ||${equals} 'foo')) {} `, errors: [ { - column: 13, - endColumn: 15, + column: 14, + endColumn: 16 + equals.length, endLine: 3, line: 3, messageId: 'preferNullishOverOr', @@ -859,7 +865,7 @@ if (() => x || 'foo') {} messageId: 'suggestNullish', output: ` declare const x: ${type} | ${nullish}; -if (() => x ?? 'foo') {} +if (() => (x ??${equals} 'foo')) {} `, }, ], @@ -867,15 +873,15 @@ if (() => x ?? 'foo') {} ], output: null, })), - ...nullishTypeTest((nullish, type) => ({ + ...nullishTypeTest((nullish, type, equals) => ({ code: ` declare const x: ${type} | ${nullish}; -if (function werid() { return x || 'foo' }) {} +if (function weird() { return (x ||${equals} 'foo') }) {} `, errors: [ { - column: 33, - endColumn: 35, + column: 34, + endColumn: 36 + equals.length, endLine: 3, line: 3, messageId: 'preferNullishOverOr', @@ -884,7 +890,7 @@ if (function werid() { return x || 'foo' }) {} messageId: 'suggestNullish', output: ` declare const x: ${type} | ${nullish}; -if (function werid() { return x ?? 'foo' }) {} +if (function weird() { return (x ??${equals} 'foo') }) {} `, }, ], From 4c3fb244984299f8af0e7ecccadfd05065c2615d Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Tue, 15 Oct 2024 07:53:47 -0500 Subject: [PATCH 06/10] change all declare consts to declare lets --- .../rules/prefer-nullish-coalescing.test.ts | 320 +++++++++--------- 1 file changed, 160 insertions(+), 160 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 d1dc20916e6a..b6b1fc37583a 100644 --- a/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts @@ -53,13 +53,13 @@ ruleTester.run('prefer-nullish-coalescing', rule, { valid: [ ...typeValidTest( (type, equals) => ` -declare const x: ${type}; +declare let x: ${type}; (x ||${equals} 'foo'); `, ), ...nullishTypeTest( (nullish, type, equals) => ` -declare const x: ${type} | ${nullish}; +declare let x: ${type} | ${nullish}; x ??${equals} 'foo'; `, ), @@ -93,31 +93,31 @@ x ??${equals} 'foo'; 'null != x ? y : x;', 'undefined != x ? y : x;', ` -declare const x: string; +declare let x: string; x === null ? x : y; `, ` -declare const x: string | undefined; +declare let x: string | undefined; x === null ? x : y; `, ` -declare const x: string | null; +declare let x: string | null; x === undefined ? x : y; `, ` -declare const x: string | undefined | null; +declare let x: string | undefined | null; x !== undefined ? x : y; `, ` -declare const x: string | undefined | null; +declare let x: string | undefined | null; x !== null ? x : y; `, ` -declare const x: string | null | any; +declare let x: string | null | any; x === null ? x : y; `, ` -declare const x: string | null | unknown; +declare let x: string | null | unknown; x === null ? x : y; `, ].map(code => ({ @@ -128,31 +128,31 @@ x === null ? x : y; // ignoreConditionalTests ...nullishTypeTest((nullish, type, equals) => ({ code: ` -declare const x: ${type} | ${nullish}; +declare let x: ${type} | ${nullish}; (x ||${equals} 'foo') ? null : null; `, })), ...nullishTypeTest((nullish, type, equals) => ({ code: ` -declare const x: ${type} | ${nullish}; +declare let x: ${type} | ${nullish}; if ((x ||${equals} 'foo')) {} `, })), ...nullishTypeTest((nullish, type, equals) => ({ code: ` -declare const x: ${type} | ${nullish}; +declare let x: ${type} | ${nullish}; do {} while ((x ||${equals} 'foo')) `, })), ...nullishTypeTest((nullish, type, equals) => ({ code: ` -declare const x: ${type} | ${nullish}; +declare let x: ${type} | ${nullish}; for (;(x ||${equals} 'foo');) {} `, })), ...nullishTypeTest((nullish, type, equals) => ({ code: ` -declare const x: ${type} | ${nullish}; +declare let x: ${type} | ${nullish}; while ((x ||${equals} 'foo')) {} `, })), @@ -160,79 +160,79 @@ while ((x ||${equals} 'foo')) {} // ignoreMixedLogicalExpressions ...nullishTypeTest((nullish, type) => ({ code: ` -declare const a: ${type} | ${nullish}; -declare const b: ${type} | ${nullish}; -declare const c: ${type} | ${nullish}; +declare let a: ${type} | ${nullish}; +declare let b: ${type} | ${nullish}; +declare let c: ${type} | ${nullish}; a || b && c; `, options: [{ ignoreMixedLogicalExpressions: true }], })), ...nullishTypeTest((nullish, type) => ({ code: ` -declare const a: ${type} | ${nullish}; -declare const b: ${type} | ${nullish}; -declare const c: ${type} | ${nullish}; -declare const d: ${type} | ${nullish}; +declare let a: ${type} | ${nullish}; +declare let b: ${type} | ${nullish}; +declare let c: ${type} | ${nullish}; +declare let d: ${type} | ${nullish}; a || b || c && d; `, options: [{ ignoreMixedLogicalExpressions: true }], })), ...nullishTypeTest((nullish, type) => ({ code: ` -declare const a: ${type} | ${nullish}; -declare const b: ${type} | ${nullish}; -declare const c: ${type} | ${nullish}; -declare const d: ${type} | ${nullish}; +declare let a: ${type} | ${nullish}; +declare let b: ${type} | ${nullish}; +declare let c: ${type} | ${nullish}; +declare let d: ${type} | ${nullish}; a && b || c || d; `, options: [{ ignoreMixedLogicalExpressions: true }], })), ...ignorablePrimitiveTypes.map>(type => ({ code: ` -declare const x: ${type} | undefined; +declare let x: ${type} | undefined; x || y; `, options: [{ ignorePrimitives: { [type]: true } }], })), ...ignorablePrimitiveTypes.map>(type => ({ code: ` -declare const x: ${type} | undefined; +declare let x: ${type} | undefined; x || y; `, options: [{ ignorePrimitives: true }], })), ...ignorablePrimitiveTypes.map>(type => ({ code: ` -declare const x: (${type} & { __brand?: any }) | undefined; +declare let x: (${type} & { __brand?: any }) | undefined; x || y; `, options: [{ ignorePrimitives: { [type]: true } }], })), ...ignorablePrimitiveTypes.map>(type => ({ code: ` -declare const x: (${type} & { __brand?: any }) | undefined; +declare let x: (${type} & { __brand?: any }) | undefined; x || y; `, options: [{ ignorePrimitives: true }], })), ` - declare const x: any; - declare const y: number; + declare let x: any; + declare let y: number; x || y; `, ` - declare const x: unknown; - declare const y: number; + declare let x: unknown; + declare let y: number; x || y; `, ` - declare const x: never; - declare const y: number; + declare let x: never; + declare let y: number; x || y; `, { code: ` -declare const x: 0 | 1 | 0n | 1n | undefined; +declare let x: 0 | 1 | 0n | 1n | undefined; x || y; `, options: [ @@ -248,7 +248,7 @@ x || y; }, { code: ` -declare const x: 0 | 1 | 0n | 1n | undefined; +declare let x: 0 | 1 | 0n | 1n | undefined; x || y; `, options: [ @@ -264,7 +264,7 @@ x || y; }, { code: ` -declare const x: 0 | 'foo' | undefined; +declare let x: 0 | 'foo' | undefined; x || y; `, options: [ @@ -278,7 +278,7 @@ x || y; }, { code: ` -declare const x: 0 | 'foo' | undefined; +declare let x: 0 | 'foo' | undefined; x || y; `, options: [ @@ -297,7 +297,7 @@ enum Enum { B = 1, C = 2, } -declare const x: Enum | undefined; +declare let x: Enum | undefined; x || y; `, options: [ @@ -315,7 +315,7 @@ enum Enum { B = 1, C = 2, } -declare const x: Enum.A | Enum.B | undefined; +declare let x: Enum.A | Enum.B | undefined; x || y; `, options: [ @@ -333,7 +333,7 @@ enum Enum { B = 'b', C = 'c', } -declare const x: Enum | undefined; +declare let x: Enum | undefined; x || y; `, options: [ @@ -351,7 +351,7 @@ enum Enum { B = 'b', C = 'c', } -declare const x: Enum.A | Enum.B | undefined; +declare let x: Enum.A | Enum.B | undefined; x || y; `, options: [ @@ -366,7 +366,7 @@ x || y; invalid: [ ...nullishTypeTest((nullish, type, equals) => ({ code: ` -declare const x: ${type} | ${nullish}; +declare let x: ${type} | ${nullish}; (x ||${equals} 'foo'); `, errors: [ @@ -380,7 +380,7 @@ declare const x: ${type} | ${nullish}; { messageId: 'suggestNullish', output: ` -declare const x: ${type} | ${nullish}; +declare let x: ${type} | ${nullish}; (x ??${equals} 'foo'); `, }, @@ -507,35 +507,35 @@ declare const x: ${type} | ${nullish}; ...[ ` -declare const x: string | undefined; +declare let x: string | undefined; x !== undefined ? x : y; `, ` -declare const x: string | undefined; +declare let x: string | undefined; undefined !== x ? x : y; `, ` -declare const x: string | undefined; +declare let x: string | undefined; undefined === x ? y : x; `, ` -declare const x: string | undefined; +declare let x: string | undefined; undefined === x ? y : x; `, ` -declare const x: string | null; +declare let x: string | null; x !== null ? x : y; `, ` -declare const x: string | null; +declare let x: string | null; null !== x ? x : y; `, ` -declare const x: string | null; +declare let x: string | null; null === x ? y : x; `, ` -declare const x: string | null; +declare let x: string | null; null === x ? y : x; `, ].map(code => ({ @@ -565,7 +565,7 @@ x ?? y; // noStrictNullCheck { code: ` -declare const x: string[] | null; +declare let x: string[] | null; if (x) { } `, @@ -587,7 +587,7 @@ if (x) { // ignoreConditionalTests ...nullishTypeTest((nullish, type, equals) => ({ code: ` -declare const x: ${type} | ${nullish}; +declare let x: ${type} | ${nullish}; (x ||${equals} 'foo') ? null : null; `, errors: [ @@ -601,7 +601,7 @@ declare const x: ${type} | ${nullish}; { messageId: 'suggestNullish', output: ` -declare const x: ${type} | ${nullish}; +declare let x: ${type} | ${nullish}; (x ??${equals} 'foo') ? null : null; `, }, @@ -613,7 +613,7 @@ declare const x: ${type} | ${nullish}; })), ...nullishTypeTest((nullish, type, equals) => ({ code: ` -declare const x: ${type} | ${nullish}; +declare let x: ${type} | ${nullish}; if ((x ||${equals} 'foo')) {} `, errors: [ @@ -627,7 +627,7 @@ if ((x ||${equals} 'foo')) {} { messageId: 'suggestNullish', output: ` -declare const x: ${type} | ${nullish}; +declare let x: ${type} | ${nullish}; if ((x ??${equals} 'foo')) {} `, }, @@ -639,7 +639,7 @@ if ((x ??${equals} 'foo')) {} })), ...nullishTypeTest((nullish, type, equals) => ({ code: ` -declare const x: ${type} | ${nullish}; +declare let x: ${type} | ${nullish}; do {} while ((x ||${equals} 'foo')) `, errors: [ @@ -653,7 +653,7 @@ do {} while ((x ||${equals} 'foo')) { messageId: 'suggestNullish', output: ` -declare const x: ${type} | ${nullish}; +declare let x: ${type} | ${nullish}; do {} while ((x ??${equals} 'foo')) `, }, @@ -665,7 +665,7 @@ do {} while ((x ??${equals} 'foo')) })), ...nullishTypeTest((nullish, type, equals) => ({ code: ` -declare const x: ${type} | ${nullish}; +declare let x: ${type} | ${nullish}; for (;(x ||${equals} 'foo');) {} `, errors: [ @@ -679,7 +679,7 @@ for (;(x ||${equals} 'foo');) {} { messageId: 'suggestNullish', output: ` -declare const x: ${type} | ${nullish}; +declare let x: ${type} | ${nullish}; for (;(x ??${equals} 'foo');) {} `, }, @@ -691,7 +691,7 @@ for (;(x ??${equals} 'foo');) {} })), ...nullishTypeTest((nullish, type, equals) => ({ code: ` -declare const x: ${type} | ${nullish}; +declare let x: ${type} | ${nullish}; while ((x ||${equals} 'foo')) {} `, errors: [ @@ -705,7 +705,7 @@ while ((x ||${equals} 'foo')) {} { messageId: 'suggestNullish', output: ` -declare const x: ${type} | ${nullish}; +declare let x: ${type} | ${nullish}; while ((x ??${equals} 'foo')) {} `, }, @@ -719,9 +719,9 @@ while ((x ??${equals} 'foo')) {} // ignoreMixedLogicalExpressions ...nullishTypeTest((nullish, type) => ({ code: ` -declare const a: ${type} | ${nullish}; -declare const b: ${type} | ${nullish}; -declare const c: ${type} | ${nullish}; +declare let a: ${type} | ${nullish}; +declare let b: ${type} | ${nullish}; +declare let c: ${type} | ${nullish}; a || b && c; `, errors: [ @@ -735,9 +735,9 @@ a || b && c; { messageId: 'suggestNullish', output: ` -declare const a: ${type} | ${nullish}; -declare const b: ${type} | ${nullish}; -declare const c: ${type} | ${nullish}; +declare let a: ${type} | ${nullish}; +declare let b: ${type} | ${nullish}; +declare let c: ${type} | ${nullish}; a ?? b && c; `, }, @@ -748,10 +748,10 @@ a ?? b && c; })), ...nullishTypeTest((nullish, type) => ({ code: ` -declare const a: ${type} | ${nullish}; -declare const b: ${type} | ${nullish}; -declare const c: ${type} | ${nullish}; -declare const d: ${type} | ${nullish}; +declare let a: ${type} | ${nullish}; +declare let b: ${type} | ${nullish}; +declare let c: ${type} | ${nullish}; +declare let d: ${type} | ${nullish}; a || b || c && d; `, errors: [ @@ -765,10 +765,10 @@ a || b || c && d; { messageId: 'suggestNullish', output: ` -declare const a: ${type} | ${nullish}; -declare const b: ${type} | ${nullish}; -declare const c: ${type} | ${nullish}; -declare const d: ${type} | ${nullish}; +declare let a: ${type} | ${nullish}; +declare let b: ${type} | ${nullish}; +declare let c: ${type} | ${nullish}; +declare let d: ${type} | ${nullish}; (a ?? b) || c && d; `, }, @@ -784,10 +784,10 @@ declare const d: ${type} | ${nullish}; { messageId: 'suggestNullish', output: ` -declare const a: ${type} | ${nullish}; -declare const b: ${type} | ${nullish}; -declare const c: ${type} | ${nullish}; -declare const d: ${type} | ${nullish}; +declare let a: ${type} | ${nullish}; +declare let b: ${type} | ${nullish}; +declare let c: ${type} | ${nullish}; +declare let d: ${type} | ${nullish}; a || b ?? c && d; `, }, @@ -798,10 +798,10 @@ a || b ?? c && d; })), ...nullishTypeTest((nullish, type) => ({ code: ` -declare const a: ${type} | ${nullish}; -declare const b: ${type} | ${nullish}; -declare const c: ${type} | ${nullish}; -declare const d: ${type} | ${nullish}; +declare let a: ${type} | ${nullish}; +declare let b: ${type} | ${nullish}; +declare let c: ${type} | ${nullish}; +declare let d: ${type} | ${nullish}; a && b || c || d; `, errors: [ @@ -815,10 +815,10 @@ a && b || c || d; { messageId: 'suggestNullish', output: ` -declare const a: ${type} | ${nullish}; -declare const b: ${type} | ${nullish}; -declare const c: ${type} | ${nullish}; -declare const d: ${type} | ${nullish}; +declare let a: ${type} | ${nullish}; +declare let b: ${type} | ${nullish}; +declare let c: ${type} | ${nullish}; +declare let d: ${type} | ${nullish}; a && (b ?? c) || d; `, }, @@ -834,10 +834,10 @@ a && (b ?? c) || d; { messageId: 'suggestNullish', output: ` -declare const a: ${type} | ${nullish}; -declare const b: ${type} | ${nullish}; -declare const c: ${type} | ${nullish}; -declare const d: ${type} | ${nullish}; +declare let a: ${type} | ${nullish}; +declare let b: ${type} | ${nullish}; +declare let c: ${type} | ${nullish}; +declare let d: ${type} | ${nullish}; a && b || c ?? d; `, }, @@ -850,7 +850,7 @@ a && b || c ?? d; // should not false positive for functions inside conditional tests ...nullishTypeTest((nullish, type, equals) => ({ code: ` -declare const x: ${type} | ${nullish}; +declare let x: ${type} | ${nullish}; if (() => (x ||${equals} 'foo')) {} `, errors: [ @@ -864,7 +864,7 @@ if (() => (x ||${equals} 'foo')) {} { messageId: 'suggestNullish', output: ` -declare const x: ${type} | ${nullish}; +declare let x: ${type} | ${nullish}; if (() => (x ??${equals} 'foo')) {} `, }, @@ -875,7 +875,7 @@ if (() => (x ??${equals} 'foo')) {} })), ...nullishTypeTest((nullish, type, equals) => ({ code: ` -declare const x: ${type} | ${nullish}; +declare let x: ${type} | ${nullish}; if (function weird() { return (x ||${equals} 'foo') }) {} `, errors: [ @@ -889,7 +889,7 @@ if (function weird() { return (x ||${equals} 'foo') }) {} { messageId: 'suggestNullish', output: ` -declare const x: ${type} | ${nullish}; +declare let x: ${type} | ${nullish}; if (function weird() { return (x ??${equals} 'foo') }) {} `, }, @@ -901,9 +901,9 @@ if (function weird() { return (x ??${equals} 'foo') }) {} // https://github.com/typescript-eslint/typescript-eslint/issues/1290 ...nullishTypeTest((nullish, type) => ({ code: ` -declare const a: ${type} | ${nullish}; -declare const b: ${type}; -declare const c: ${type}; +declare let a: ${type} | ${nullish}; +declare let b: ${type}; +declare let c: ${type}; a || b || c; `, errors: [ @@ -917,9 +917,9 @@ a || b || c; { messageId: 'suggestNullish', output: ` -declare const a: ${type} | ${nullish}; -declare const b: ${type}; -declare const c: ${type}; +declare let a: ${type} | ${nullish}; +declare let b: ${type}; +declare let c: ${type}; (a ?? b) || c; `, }, @@ -931,7 +931,7 @@ declare const c: ${type}; // default for missing option { code: ` -declare const x: string | undefined; +declare let x: string | undefined; x || y; `, errors: [ @@ -941,7 +941,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: string | undefined; +declare let x: string | undefined; x ?? y; `, }, @@ -957,7 +957,7 @@ x ?? y; }, { code: ` -declare const x: number | undefined; +declare let x: number | undefined; x || y; `, errors: [ @@ -967,7 +967,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: number | undefined; +declare let x: number | undefined; x ?? y; `, }, @@ -983,7 +983,7 @@ x ?? y; }, { code: ` -declare const x: boolean | undefined; +declare let x: boolean | undefined; x || y; `, errors: [ @@ -993,7 +993,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: boolean | undefined; +declare let x: boolean | undefined; x ?? y; `, }, @@ -1009,7 +1009,7 @@ x ?? y; }, { code: ` -declare const x: bigint | undefined; +declare let x: bigint | undefined; x || y; `, errors: [ @@ -1019,7 +1019,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: bigint | undefined; +declare let x: bigint | undefined; x ?? y; `, }, @@ -1036,7 +1036,7 @@ x ?? y; // falsy { code: ` -declare const x: '' | undefined; +declare let x: '' | undefined; x || y; `, errors: [ @@ -1046,7 +1046,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: '' | undefined; +declare let x: '' | undefined; x ?? y; `, }, @@ -1067,7 +1067,7 @@ x ?? y; }, { code: ` -declare const x: \`\` | undefined; +declare let x: \`\` | undefined; x || y; `, errors: [ @@ -1077,7 +1077,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: \`\` | undefined; +declare let x: \`\` | undefined; x ?? y; `, }, @@ -1098,7 +1098,7 @@ x ?? y; }, { code: ` -declare const x: 0 | undefined; +declare let x: 0 | undefined; x || y; `, errors: [ @@ -1108,7 +1108,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: 0 | undefined; +declare let x: 0 | undefined; x ?? y; `, }, @@ -1129,7 +1129,7 @@ x ?? y; }, { code: ` -declare const x: 0n | undefined; +declare let x: 0n | undefined; x || y; `, errors: [ @@ -1139,7 +1139,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: 0n | undefined; +declare let x: 0n | undefined; x ?? y; `, }, @@ -1160,7 +1160,7 @@ x ?? y; }, { code: ` -declare const x: false | undefined; +declare let x: false | undefined; x || y; `, errors: [ @@ -1170,7 +1170,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: false | undefined; +declare let x: false | undefined; x ?? y; `, }, @@ -1192,7 +1192,7 @@ x ?? y; // truthy { code: ` -declare const x: 'a' | undefined; +declare let x: 'a' | undefined; x || y; `, errors: [ @@ -1202,7 +1202,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: 'a' | undefined; +declare let x: 'a' | undefined; x ?? y; `, }, @@ -1223,7 +1223,7 @@ x ?? y; }, { code: ` -declare const x: \`hello\${'string'}\` | undefined; +declare let x: \`hello\${'string'}\` | undefined; x || y; `, errors: [ @@ -1233,7 +1233,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: \`hello\${'string'}\` | undefined; +declare let x: \`hello\${'string'}\` | undefined; x ?? y; `, }, @@ -1254,7 +1254,7 @@ x ?? y; }, { code: ` -declare const x: 1 | undefined; +declare let x: 1 | undefined; x || y; `, errors: [ @@ -1264,7 +1264,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: 1 | undefined; +declare let x: 1 | undefined; x ?? y; `, }, @@ -1285,7 +1285,7 @@ x ?? y; }, { code: ` -declare const x: 1n | undefined; +declare let x: 1n | undefined; x || y; `, errors: [ @@ -1295,7 +1295,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: 1n | undefined; +declare let x: 1n | undefined; x ?? y; `, }, @@ -1316,7 +1316,7 @@ x ?? y; }, { code: ` -declare const x: true | undefined; +declare let x: true | undefined; x || y; `, errors: [ @@ -1326,7 +1326,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: true | undefined; +declare let x: true | undefined; x ?? y; `, }, @@ -1348,7 +1348,7 @@ x ?? y; // Unions of same primitive { code: ` -declare const x: 'a' | 'b' | undefined; +declare let x: 'a' | 'b' | undefined; x || y; `, errors: [ @@ -1358,7 +1358,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: 'a' | 'b' | undefined; +declare let x: 'a' | 'b' | undefined; x ?? y; `, }, @@ -1379,7 +1379,7 @@ x ?? y; }, { code: ` -declare const x: 'a' | \`b\` | undefined; +declare let x: 'a' | \`b\` | undefined; x || y; `, errors: [ @@ -1389,7 +1389,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: 'a' | \`b\` | undefined; +declare let x: 'a' | \`b\` | undefined; x ?? y; `, }, @@ -1410,7 +1410,7 @@ x ?? y; }, { code: ` -declare const x: 0 | 1 | undefined; +declare let x: 0 | 1 | undefined; x || y; `, errors: [ @@ -1420,7 +1420,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: 0 | 1 | undefined; +declare let x: 0 | 1 | undefined; x ?? y; `, }, @@ -1441,7 +1441,7 @@ x ?? y; }, { code: ` -declare const x: 1 | 2 | 3 | undefined; +declare let x: 1 | 2 | 3 | undefined; x || y; `, errors: [ @@ -1451,7 +1451,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: 1 | 2 | 3 | undefined; +declare let x: 1 | 2 | 3 | undefined; x ?? y; `, }, @@ -1472,7 +1472,7 @@ x ?? y; }, { code: ` -declare const x: 0n | 1n | undefined; +declare let x: 0n | 1n | undefined; x || y; `, errors: [ @@ -1482,7 +1482,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: 0n | 1n | undefined; +declare let x: 0n | 1n | undefined; x ?? y; `, }, @@ -1503,7 +1503,7 @@ x ?? y; }, { code: ` -declare const x: 1n | 2n | 3n | undefined; +declare let x: 1n | 2n | 3n | undefined; x || y; `, errors: [ @@ -1513,7 +1513,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: 1n | 2n | 3n | undefined; +declare let x: 1n | 2n | 3n | undefined; x ?? y; `, }, @@ -1534,7 +1534,7 @@ x ?? y; }, { code: ` -declare const x: true | false | undefined; +declare let x: true | false | undefined; x || y; `, errors: [ @@ -1544,7 +1544,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: true | false | undefined; +declare let x: true | false | undefined; x ?? y; `, }, @@ -1566,7 +1566,7 @@ x ?? y; // Mixed unions { code: ` -declare const x: 0 | 1 | 0n | 1n | undefined; +declare let x: 0 | 1 | 0n | 1n | undefined; x || y; `, errors: [ @@ -1576,7 +1576,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: 0 | 1 | 0n | 1n | undefined; +declare let x: 0 | 1 | 0n | 1n | undefined; x ?? y; `, }, @@ -1597,7 +1597,7 @@ x ?? y; }, { code: ` -declare const x: true | false | null | undefined; +declare let x: true | false | null | undefined; x || y; `, errors: [ @@ -1607,7 +1607,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: true | false | null | undefined; +declare let x: true | false | null | undefined; x ?? y; `, }, @@ -1628,7 +1628,7 @@ x ?? y; }, { code: ` -declare const x: null; +declare let x: null; x || y; `, errors: [ @@ -1638,7 +1638,7 @@ x || y; { messageId: 'suggestNullish', output: ` -declare const x: null; +declare let x: null; x ?? y; `, }, @@ -1713,7 +1713,7 @@ enum Enum { B = 1, C = 2, } -declare const x: Enum | undefined; +declare let x: Enum | undefined; x || y; `, errors: [ @@ -1728,7 +1728,7 @@ enum Enum { B = 1, C = 2, } -declare const x: Enum | undefined; +declare let x: Enum | undefined; x ?? y; `, }, @@ -1744,7 +1744,7 @@ enum Enum { B = 1, C = 2, } -declare const x: Enum.A | Enum.B | undefined; +declare let x: Enum.A | Enum.B | undefined; x || y; `, errors: [ @@ -1759,7 +1759,7 @@ enum Enum { B = 1, C = 2, } -declare const x: Enum.A | Enum.B | undefined; +declare let x: Enum.A | Enum.B | undefined; x ?? y; `, }, @@ -1775,7 +1775,7 @@ enum Enum { B = 'b', C = 'c', } -declare const x: Enum | undefined; +declare let x: Enum | undefined; x || y; `, errors: [ @@ -1790,7 +1790,7 @@ enum Enum { B = 'b', C = 'c', } -declare const x: Enum | undefined; +declare let x: Enum | undefined; x ?? y; `, }, @@ -1806,7 +1806,7 @@ enum Enum { B = 'b', C = 'c', } -declare const x: Enum.A | Enum.B | undefined; +declare let x: Enum.A | Enum.B | undefined; x || y; `, errors: [ @@ -1821,7 +1821,7 @@ enum Enum { B = 'b', C = 'c', } -declare const x: Enum.A | Enum.B | undefined; +declare let x: Enum.A | Enum.B | undefined; x ?? y; `, }, From 52066e08fe9fb149e132b043aba7a27e35a0c562 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Tue, 15 Oct 2024 22:27:11 -0500 Subject: [PATCH 07/10] apply lint rule --- packages/rule-tester/src/utils/config-validator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/rule-tester/src/utils/config-validator.ts b/packages/rule-tester/src/utils/config-validator.ts index b8b740915aba..bc5e09e7646c 100644 --- a/packages/rule-tester/src/utils/config-validator.ts +++ b/packages/rule-tester/src/utils/config-validator.ts @@ -197,7 +197,7 @@ function validateConfigSchema( config: TesterConfigWithDefaults, source: string, ): void { - validateSchema ||= ajv.compile(flatConfigSchema); + validateSchema ??= ajv.compile(flatConfigSchema); if (!validateSchema(config)) { throw new Error( From da2aeb8efe8375655065fb01b9720eeee16983e8 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Tue, 15 Oct 2024 22:43:21 -0500 Subject: [PATCH 08/10] reduce the number of duplicate tests --- .../tests/rules/prefer-nullish-coalescing.test.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 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 b6b1fc37583a..60f42b38061c 100644 --- a/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts @@ -38,16 +38,21 @@ function typeValidTest( ]; } -function nullishTypeTest< +const nullishTypeTest = < T extends | InvalidTestCase | ValidTestCase | string, ->(cb: (nullish: string, type: string, equals: string) => T): T[] { - return nullishTypes.flatMap(nullish => - types.flatMap(type => [cb(nullish, type, ''), cb(nullish, type, '=')]), +>( + cb: (nullish: string, type: string, equals: string) => T, +): T[] => + nullishTypes.flatMap(nullish => + types.flatMap(type => + ['', ...(cb.length === 3 ? ['='] : [])].map(equals => + cb(nullish, type, equals), + ), + ), ); -} ruleTester.run('prefer-nullish-coalescing', rule, { valid: [ From de4fb7075f3dab5e621d843ced6b3b9cf1be0f00 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Tue, 15 Oct 2024 22:46:44 -0500 Subject: [PATCH 09/10] revert incorrect unrelated changes --- .../tests/rules/prefer-nullish-coalescing.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 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 60f42b38061c..b75949a55e86 100644 --- a/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-nullish-coalescing.test.ts @@ -521,7 +521,7 @@ undefined !== x ? x : y; `, ` declare let x: string | undefined; -undefined === x ? y : x; +x === undefined ? y : x; `, ` declare let x: string | undefined; @@ -537,7 +537,7 @@ null !== x ? x : y; `, ` declare let x: string | null; -null === x ? y : x; +x === null ? y : x; `, ` declare let x: string | null; From 22841dac7a89680ddb4d4fc679205f3a2543495d Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 21 Oct 2024 14:09:21 -0400 Subject: [PATCH 10/10] fix lint complaints --- packages/type-utils/src/TypeOrValueSpecifier.ts | 10 +++++----- packages/type-utils/src/builtinSymbolLikes.ts | 2 +- .../src/typeOrValueSpecifiers/specifierNameMatches.ts | 2 +- .../tests/getConstrainedTypeAtLocation.test.ts | 8 ++++---- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/type-utils/src/TypeOrValueSpecifier.ts b/packages/type-utils/src/TypeOrValueSpecifier.ts index 0d476b38a7d0..21c1871cba65 100644 --- a/packages/type-utils/src/TypeOrValueSpecifier.ts +++ b/packages/type-utils/src/TypeOrValueSpecifier.ts @@ -18,7 +18,7 @@ export interface FileSpecifier { /** * Type or value name(s) to match on. */ - name: string[] | string; + name: string | string[]; /** * A specific file the types or values must be declared in. @@ -36,7 +36,7 @@ export interface LibSpecifier { /** * Type or value name(s) to match on. */ - name: string[] | string; + name: string | string[]; } /** @@ -49,7 +49,7 @@ export interface PackageSpecifier { /** * Type or value name(s) to match on. */ - name: string[] | string; + name: string | string[]; /** * Package name the type or value must be declared in. @@ -62,10 +62,10 @@ export interface PackageSpecifier { * See [TypeOrValueSpecifier](/packages/type-utils/type-or-value-specifier). */ export type TypeOrValueSpecifier = + | string | FileSpecifier | LibSpecifier - | PackageSpecifier - | string; + | PackageSpecifier; export const typeOrValueSpecifiersSchema = { items: { diff --git a/packages/type-utils/src/builtinSymbolLikes.ts b/packages/type-utils/src/builtinSymbolLikes.ts index f8c27524c365..d1833e3ada84 100644 --- a/packages/type-utils/src/builtinSymbolLikes.ts +++ b/packages/type-utils/src/builtinSymbolLikes.ts @@ -121,7 +121,7 @@ export function isBuiltinTypeAliasLike( export function isBuiltinSymbolLike( program: ts.Program, type: ts.Type, - symbolName: string[] | string, + symbolName: string | string[], ): boolean { return isBuiltinSymbolLikeRecurser(program, type, subType => { const symbol = subType.getSymbol(); diff --git a/packages/type-utils/src/typeOrValueSpecifiers/specifierNameMatches.ts b/packages/type-utils/src/typeOrValueSpecifiers/specifierNameMatches.ts index bd26509c1e12..278ade8b8f17 100644 --- a/packages/type-utils/src/typeOrValueSpecifiers/specifierNameMatches.ts +++ b/packages/type-utils/src/typeOrValueSpecifiers/specifierNameMatches.ts @@ -4,7 +4,7 @@ import * as tsutils from 'ts-api-utils'; export function specifierNameMatches( type: ts.Type, - names: string[] | string, + names: string | string[], ): boolean { if (typeof names === 'string') { names = [names]; diff --git a/packages/type-utils/tests/getConstrainedTypeAtLocation.test.ts b/packages/type-utils/tests/getConstrainedTypeAtLocation.test.ts index f10c2b9635f2..592c45b1520b 100644 --- a/packages/type-utils/tests/getConstrainedTypeAtLocation.test.ts +++ b/packages/type-utils/tests/getConstrainedTypeAtLocation.test.ts @@ -11,11 +11,11 @@ const mockType = (): ts.Type => { }; const mockServices = ({ - typeAtLocation, baseConstraintOfType, + typeAtLocation, }: { - typeAtLocation: ts.Type; baseConstraintOfType?: ts.Type; + typeAtLocation: ts.Type; }): ParserServicesWithTypeInformation => { const typeChecker = { getBaseConstraintOfType: (_: ts.Type) => baseConstraintOfType, @@ -25,8 +25,8 @@ const mockServices = ({ } as ts.Program; return { - program, getTypeAtLocation: (_: TSESTree.Node) => typeAtLocation, + program, } as ParserServicesWithTypeInformation; }; @@ -36,8 +36,8 @@ describe('getConstrainedTypeAtLocation', () => { const typeAtLocation = mockType(); const baseConstraintOfType = mockType(); const services = mockServices({ - typeAtLocation, baseConstraintOfType, + typeAtLocation, }); expect(getConstrainedTypeAtLocation(services, node)).toBe(