From c035774ee63307015e522742a8cd4e4e0ae4ea93 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Thu, 28 Sep 2023 21:52:25 -0500 Subject: [PATCH 01/10] WIP --- .../rules/restrict-template-expressions.ts | 59 ++++++------------- 1 file changed, 17 insertions(+), 42 deletions(-) diff --git a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts index 5da963a8219..83cc441e85f 100644 --- a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts +++ b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts @@ -4,16 +4,20 @@ import * as ts from 'typescript'; import * as util from '../util'; -type Options = [ - { - allowAny?: boolean; - allowBoolean?: boolean; - allowNullish?: boolean; - allowNumber?: boolean; - allowRegExp?: boolean; - allowNever?: boolean; - }, -]; +const optionEntries = ( + ['Any', 'Array', 'Boolean', 'Nullish', 'Number', 'RegExp', 'Never'] as const +).map( + type => + [ + `allow${type}`, + { + description: `Whether to allow \`${type.toLowerCase()}\` typed values in template expressions.`, + type: 'boolean', + }, + ] as const, +); + +type Options = [{ [Type in (typeof optionEntries)[number][0]]?: boolean }]; type MessageId = 'invalidType'; @@ -34,38 +38,7 @@ export default util.createRule({ { type: 'object', additionalProperties: false, - properties: { - allowAny: { - description: - 'Whether to allow `any` typed values in template expressions.', - type: 'boolean', - }, - allowBoolean: { - description: - 'Whether to allow `boolean` typed values in template expressions.', - type: 'boolean', - }, - allowNullish: { - description: - 'Whether to allow `nullish` typed values in template expressions.', - type: 'boolean', - }, - allowNumber: { - description: - 'Whether to allow `number` typed values in template expressions.', - type: 'boolean', - }, - allowRegExp: { - description: - 'Whether to allow `regexp` typed values in template expressions.', - type: 'boolean', - }, - allowNever: { - description: - 'Whether to allow `never` typed values in template expressions.', - type: 'boolean', - }, - }, + properties: Object.fromEntries(optionEntries), }, ], }, @@ -119,6 +92,8 @@ export default util.createRule({ return true; } + /*if (options.allowArray)*/ console.log(type); + if (options.allowNever && util.isTypeNeverType(type)) { return true; } From f51c879576973786ce0b0d0b55df183ec97e899c Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Tue, 6 Feb 2024 07:49:20 -0600 Subject: [PATCH 02/10] finish logic --- .../rules/restrict-template-expressions.ts | 115 +++++++----------- 1 file changed, 46 insertions(+), 69 deletions(-) diff --git a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts index 48d7379a4b9..fb577e34560 100644 --- a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts +++ b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts @@ -12,20 +12,35 @@ import { isTypeNeverType, } from '../util'; -const optionEntries = ( - ['Any', 'Array', 'Boolean', 'Nullish', 'Number', 'RegExp', 'Never'] as const -).map( - type => - [ - `allow${type}`, - { - description: `Whether to allow \`${type.toLowerCase()}\` typed values in template expressions.`, - type: 'boolean', - }, - ] as const, -); - -type Options = [{ [Type in (typeof optionEntries)[number][0]]?: boolean }]; +const optionsObj = { + Any: isTypeAnyType, + Array: (type, checker, rec): boolean => { + if (!checker.isArrayType(type)) { + return false; + } + const numberIndexType = type.getNumberIndexType(); + return !numberIndexType || rec(numberIndexType); + }, + Boolean: (type): boolean => isTypeFlagSet(type, ts.TypeFlags.BooleanLike), + Nullish: (type): boolean => + isTypeFlagSet(type, ts.TypeFlags.Null | ts.TypeFlags.Undefined), + Number: (type): boolean => + isTypeFlagSet(type, ts.TypeFlags.NumberLike | ts.TypeFlags.BigIntLike), + RegExp: (type, checker): boolean => getTypeName(checker, type) === 'RegExp', + Never: isTypeNeverType, +} satisfies Record< + string, + ( + type: ts.Type, + checker: ts.TypeChecker, + rec: (type: ts.Type) => boolean, + ) => boolean +>; +const optionsArr = Object.entries(optionsObj).map(([_type, fn]) => { + const type = _type as keyof typeof optionsObj; + return { type, option: `allow${type}` as const, fn }; +}); +type Options = [{ [Type in (typeof optionsArr)[number]['option']]?: boolean }]; type MessageId = 'invalidType'; @@ -46,7 +61,15 @@ export default createRule({ { type: 'object', additionalProperties: false, - properties: Object.fromEntries(optionEntries), + properties: Object.fromEntries( + optionsArr.map(({ option, type }) => [ + option, + { + description: `Whether to allow \`${type.toLowerCase()}\` typed values in template expressions.`, + type: 'boolean', + }, + ]), + ), }, ], }, @@ -63,49 +86,6 @@ export default createRule({ const services = getParserServices(context); const checker = services.program.getTypeChecker(); - function isUnderlyingTypePrimitive(type: ts.Type): boolean { - if (isTypeFlagSet(type, ts.TypeFlags.StringLike)) { - return true; - } - - if ( - options.allowNumber && - isTypeFlagSet(type, ts.TypeFlags.NumberLike | ts.TypeFlags.BigIntLike) - ) { - return true; - } - - if ( - options.allowBoolean && - isTypeFlagSet(type, ts.TypeFlags.BooleanLike) - ) { - return true; - } - - if (options.allowAny && isTypeAnyType(type)) { - return true; - } - - if (options.allowRegExp && getTypeName(checker, type) === 'RegExp') { - return true; - } - - if ( - options.allowNullish && - isTypeFlagSet(type, ts.TypeFlags.Null | ts.TypeFlags.Undefined) - ) { - return true; - } - - /*if (options.allowArray)*/ console.log(type); - - if (options.allowNever && isTypeNeverType(type)) { - return true; - } - - return false; - } - return { TemplateLiteral(node: TSESTree.TemplateLiteral): void { // don't check tagged template literals @@ -119,12 +99,7 @@ export default createRule({ expression, ); - if ( - !isInnerUnionOrIntersectionConformingTo( - expressionType, - isUnderlyingTypePrimitive, - ) - ) { + if (!isInnerUnionOrIntersectionConformingTo(expressionType)) { context.report({ node: expression, messageId: 'invalidType', @@ -135,10 +110,7 @@ export default createRule({ }, }; - function isInnerUnionOrIntersectionConformingTo( - type: ts.Type, - predicate: (underlyingType: ts.Type) => boolean, - ): boolean { + function isInnerUnionOrIntersectionConformingTo(type: ts.Type): boolean { return rec(type); function rec(innerType: ts.Type): boolean { @@ -150,7 +122,12 @@ export default createRule({ return innerType.types.some(rec); } - return predicate(innerType); + return ( + isTypeFlagSet(innerType, ts.TypeFlags.StringLike) || + optionsArr.some( + ({ option, fn }) => options[option] && fn(innerType, checker, rec), + ) + ); } } }, From fbd2f3060a3b5307db80670fcf9e992d73dfc3ef Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Mon, 19 Feb 2024 06:45:59 -0600 Subject: [PATCH 03/10] add doc section --- .../docs/rules/restrict-template-expressions.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/restrict-template-expressions.md b/packages/eslint-plugin/docs/rules/restrict-template-expressions.md index e7e295c443a..d73449fc4ce 100644 --- a/packages/eslint-plugin/docs/rules/restrict-template-expressions.md +++ b/packages/eslint-plugin/docs/rules/restrict-template-expressions.md @@ -12,10 +12,10 @@ This rule reports on values used in a template literal string that aren't string :::note -This rule intentionally does not allow objects with a custom `toString()` method to be used in template literals, because the stringification result may not be user-friendly. +The default settings of this rule intentionally do not allow objects with a custom `toString()` method to be used in template literals, because the stringification result may not be user-friendly. For example, arrays have a custom [`toString()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/toString) method, which only calls `join()` internally, which joins the array elements with commas. This means that (1) array elements are not necessarily stringified to useful results (2) the commas don't have spaces after them, making the result not user-friendly. The best way to format arrays is to use [`Intl.ListFormat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/ListFormat), which even supports adding the "and" conjunction where necessary. -You must explicitly call `object.toString()` if you want to use this object in a template literal. +You must explicitly call `object.toString()` if you want to use this object in a template literal, or turn on the `allowArray` option to specifically allow arrays. The [`no-base-to-string`](./no-base-to-string.md) rule can be used to guard this case against producing `"[object Object]"` by accident. ::: @@ -111,6 +111,15 @@ const arg = 'something'; const msg1 = typeof arg === 'string' ? arg : `arg = ${arg}`; ``` +### `allowArray` + +Examples of additional **correct** code for this rule with `{ allowArray: true }`: + +```ts option='{ "allowArray": true }' showPlaygroundButton +const arg = ['foo', 'bar']; +const msg1 = `arg = ${arg}`; +``` + ## When Not To Use It If you're not worried about incorrectly stringifying non-string values in template literals, then you likely don't need this rule. From 4ee70873f3e6ad163902822a513fade67e0a68d0 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Mon, 19 Feb 2024 06:59:34 -0600 Subject: [PATCH 04/10] update snapshot --- .../schema-snapshots/restrict-template-expressions.shot | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/eslint-plugin/tests/schema-snapshots/restrict-template-expressions.shot b/packages/eslint-plugin/tests/schema-snapshots/restrict-template-expressions.shot index 194ddc4260c..1a4d828e764 100644 --- a/packages/eslint-plugin/tests/schema-snapshots/restrict-template-expressions.shot +++ b/packages/eslint-plugin/tests/schema-snapshots/restrict-template-expressions.shot @@ -12,6 +12,10 @@ exports[`Rule schemas should be convertible to TS types for documentation purpos "description": "Whether to allow \`any\` typed values in template expressions.", "type": "boolean" }, + "allowArray": { + "description": "Whether to allow \`array\` typed values in template expressions.", + "type": "boolean" + }, "allowBoolean": { "description": "Whether to allow \`boolean\` typed values in template expressions.", "type": "boolean" @@ -44,6 +48,8 @@ type Options = [ { /** Whether to allow \`any\` typed values in template expressions. */ allowAny?: boolean; + /** Whether to allow \`array\` typed values in template expressions. */ + allowArray?: boolean; /** Whether to allow \`boolean\` typed values in template expressions. */ allowBoolean?: boolean; /** Whether to allow \`never\` typed values in template expressions. */ From bf925e5e55636218b8ef769bbc3d99d9d6e5be97 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Thu, 22 Feb 2024 21:38:23 -0600 Subject: [PATCH 05/10] refactors and renames --- .../rules/restrict-template-expressions.ts | 113 ++++++++++-------- 1 file changed, 65 insertions(+), 48 deletions(-) diff --git a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts index fb577e34560..e4b0c21399c 100644 --- a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts +++ b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts @@ -12,35 +12,53 @@ import { isTypeNeverType, } from '../util'; -const optionsObj = { - Any: isTypeAnyType, - Array: (type, checker, rec): boolean => { - if (!checker.isArrayType(type)) { - return false; - } - const numberIndexType = type.getNumberIndexType(); - return !numberIndexType || rec(numberIndexType); - }, - Boolean: (type): boolean => isTypeFlagSet(type, ts.TypeFlags.BooleanLike), - Nullish: (type): boolean => - isTypeFlagSet(type, ts.TypeFlags.Null | ts.TypeFlags.Undefined), - Number: (type): boolean => - isTypeFlagSet(type, ts.TypeFlags.NumberLike | ts.TypeFlags.BigIntLike), - RegExp: (type, checker): boolean => getTypeName(checker, type) === 'RegExp', - Never: isTypeNeverType, -} satisfies Record< - string, - ( - type: ts.Type, - checker: ts.TypeChecker, - rec: (type: ts.Type) => boolean, - ) => boolean ->; -const optionsArr = Object.entries(optionsObj).map(([_type, fn]) => { - const type = _type as keyof typeof optionsObj; - return { type, option: `allow${type}` as const, fn }; -}); -type Options = [{ [Type in (typeof optionsArr)[number]['option']]?: boolean }]; +type OptionTester = ( + type: ts.Type, + checker: ts.TypeChecker, + recursivelyCheckType: (type: ts.Type) => boolean, +) => boolean; + +const optionTesters = ( + [ + ['Any', isTypeAnyType], + [ + 'Array', + (type, checker, recursivelyCheckType): boolean => { + const maybeNumberIndexType = + checker.isArrayType(type) && type.getNumberIndexType(); + return ( + !maybeNumberIndexType || recursivelyCheckType(maybeNumberIndexType) + ); + }, + ], + [ + 'Boolean', // eslint-disable-line @typescript-eslint/internal/prefer-ast-types-enum + (type): boolean => isTypeFlagSet(type, ts.TypeFlags.BooleanLike), + ], + [ + 'Nullish', + (type): boolean => + isTypeFlagSet(type, ts.TypeFlags.Null | ts.TypeFlags.Undefined), + ], + [ + 'Number', + (type): boolean => + isTypeFlagSet(type, ts.TypeFlags.NumberLike | ts.TypeFlags.BigIntLike), + ], + [ + 'RegExp', + (type, checker): boolean => getTypeName(checker, type) === 'RegExp', + ], + ['Never', isTypeNeverType], + ] as const satisfies [string, OptionTester][] +).map(([type, tester]) => ({ + type, + option: `allow${type}` as const, + tester, +})); +type Options = [ + { [Type in (typeof optionTesters)[number]['option']]?: boolean }, +]; type MessageId = 'invalidType'; @@ -62,7 +80,7 @@ export default createRule({ type: 'object', additionalProperties: false, properties: Object.fromEntries( - optionsArr.map(({ option, type }) => [ + optionTesters.map(({ option, type }) => [ option, { description: `Whether to allow \`${type.toLowerCase()}\` typed values in template expressions.`, @@ -85,6 +103,9 @@ export default createRule({ create(context, [options]) { const services = getParserServices(context); const checker = services.program.getTypeChecker(); + const enabledOptionTesters = optionTesters.filter( + ({ option }) => options[option], + ); return { TemplateLiteral(node: TSESTree.TemplateLiteral): void { @@ -99,7 +120,7 @@ export default createRule({ expression, ); - if (!isInnerUnionOrIntersectionConformingTo(expressionType)) { + if (!recursivelyCheckType(expressionType)) { context.report({ node: expression, messageId: 'invalidType', @@ -110,25 +131,21 @@ export default createRule({ }, }; - function isInnerUnionOrIntersectionConformingTo(type: ts.Type): boolean { - return rec(type); - - function rec(innerType: ts.Type): boolean { - if (innerType.isUnion()) { - return innerType.types.every(rec); - } - - if (innerType.isIntersection()) { - return innerType.types.some(rec); - } + function recursivelyCheckType(innerType: ts.Type): boolean { + if (innerType.isUnion()) { + return innerType.types.every(recursivelyCheckType); + } - return ( - isTypeFlagSet(innerType, ts.TypeFlags.StringLike) || - optionsArr.some( - ({ option, fn }) => options[option] && fn(innerType, checker, rec), - ) - ); + if (innerType.isIntersection()) { + return innerType.types.some(recursivelyCheckType); } + + return ( + isTypeFlagSet(innerType, ts.TypeFlags.StringLike) || + enabledOptionTesters.some(({ tester }) => + tester(innerType, checker, recursivelyCheckType), + ) + ); } }, }); From f56d8e9d3fe1f01b3933163902edade208dfa87d Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Thu, 22 Feb 2024 21:48:32 -0600 Subject: [PATCH 06/10] simplify type flag testers --- .../rules/restrict-template-expressions.ts | 38 +++++++++---------- 1 file changed, 17 insertions(+), 21 deletions(-) diff --git a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts index e4b0c21399c..5b455bee21a 100644 --- a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts +++ b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts @@ -1,6 +1,7 @@ import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; -import * as ts from 'typescript'; +import type { Type, TypeChecker } from 'typescript'; +import { TypeFlags } from 'typescript'; import { createRule, @@ -13,11 +14,16 @@ import { } from '../util'; type OptionTester = ( - type: ts.Type, - checker: ts.TypeChecker, - recursivelyCheckType: (type: ts.Type) => boolean, + type: Type, + checker: TypeChecker, + recursivelyCheckType: (type: Type) => boolean, ) => boolean; +const makeTypeFlagTester = + (flagsToCheck: TypeFlags): OptionTester => + type => + isTypeFlagSet(type, flagsToCheck); + const optionTesters = ( [ ['Any', isTypeAnyType], @@ -31,26 +37,16 @@ const optionTesters = ( ); }, ], - [ - 'Boolean', // eslint-disable-line @typescript-eslint/internal/prefer-ast-types-enum - (type): boolean => isTypeFlagSet(type, ts.TypeFlags.BooleanLike), - ], - [ - 'Nullish', - (type): boolean => - isTypeFlagSet(type, ts.TypeFlags.Null | ts.TypeFlags.Undefined), - ], - [ - 'Number', - (type): boolean => - isTypeFlagSet(type, ts.TypeFlags.NumberLike | ts.TypeFlags.BigIntLike), - ], + // eslint-disable-next-line @typescript-eslint/internal/prefer-ast-types-enum + ['Boolean', makeTypeFlagTester(TypeFlags.BooleanLike)], + ['Nullish', makeTypeFlagTester(TypeFlags.Null | TypeFlags.Undefined)], + ['Number', makeTypeFlagTester(TypeFlags.NumberLike | TypeFlags.BigIntLike)], [ 'RegExp', (type, checker): boolean => getTypeName(checker, type) === 'RegExp', ], ['Never', isTypeNeverType], - ] as const satisfies [string, OptionTester][] + ] satisfies [string, OptionTester][] ).map(([type, tester]) => ({ type, option: `allow${type}` as const, @@ -131,7 +127,7 @@ export default createRule({ }, }; - function recursivelyCheckType(innerType: ts.Type): boolean { + function recursivelyCheckType(innerType: Type): boolean { if (innerType.isUnion()) { return innerType.types.every(recursivelyCheckType); } @@ -141,7 +137,7 @@ export default createRule({ } return ( - isTypeFlagSet(innerType, ts.TypeFlags.StringLike) || + isTypeFlagSet(innerType, TypeFlags.StringLike) || enabledOptionTesters.some(({ tester }) => tester(innerType, checker, recursivelyCheckType), ) From 824514665b105e3be6ed5fef75528d30cf122c87 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Thu, 22 Feb 2024 21:57:13 -0600 Subject: [PATCH 07/10] rename --- .../src/rules/restrict-template-expressions.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts index 5b455bee21a..4479059e614 100644 --- a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts +++ b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts @@ -19,7 +19,7 @@ type OptionTester = ( recursivelyCheckType: (type: Type) => boolean, ) => boolean; -const makeTypeFlagTester = +const testTypeFlag = (flagsToCheck: TypeFlags): OptionTester => type => isTypeFlagSet(type, flagsToCheck); @@ -38,9 +38,9 @@ const optionTesters = ( }, ], // eslint-disable-next-line @typescript-eslint/internal/prefer-ast-types-enum - ['Boolean', makeTypeFlagTester(TypeFlags.BooleanLike)], - ['Nullish', makeTypeFlagTester(TypeFlags.Null | TypeFlags.Undefined)], - ['Number', makeTypeFlagTester(TypeFlags.NumberLike | TypeFlags.BigIntLike)], + ['Boolean', testTypeFlag(TypeFlags.BooleanLike)], + ['Nullish', testTypeFlag(TypeFlags.Null | TypeFlags.Undefined)], + ['Number', testTypeFlag(TypeFlags.NumberLike | TypeFlags.BigIntLike)], [ 'RegExp', (type, checker): boolean => getTypeName(checker, type) === 'RegExp', From f4bb1e8dc9d370d38cb4b176ced6c01bb299f324 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Mon, 26 Feb 2024 07:50:36 -0600 Subject: [PATCH 08/10] write tests --- .../rules/restrict-template-expressions.ts | 10 ++--- .../restrict-template-expressions.test.ts | 44 ++++++++++++++++--- 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts index 4479059e614..6fcb6579393 100644 --- a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts +++ b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts @@ -29,13 +29,9 @@ const optionTesters = ( ['Any', isTypeAnyType], [ 'Array', - (type, checker, recursivelyCheckType): boolean => { - const maybeNumberIndexType = - checker.isArrayType(type) && type.getNumberIndexType(); - return ( - !maybeNumberIndexType || recursivelyCheckType(maybeNumberIndexType) - ); - }, + (type, checker, recursivelyCheckType): boolean => + checker.isArrayType(type) && + recursivelyCheckType(type.getNumberIndexType()!), ], // eslint-disable-next-line @typescript-eslint/internal/prefer-ast-types-enum ['Boolean', testTypeFlag(TypeFlags.BooleanLike)], diff --git a/packages/eslint-plugin/tests/rules/restrict-template-expressions.test.ts b/packages/eslint-plugin/tests/rules/restrict-template-expressions.test.ts index 1701f9a4496..2bdbc115053 100644 --- a/packages/eslint-plugin/tests/rules/restrict-template-expressions.test.ts +++ b/packages/eslint-plugin/tests/rules/restrict-template-expressions.test.ts @@ -131,6 +131,30 @@ ruleTester.run('restrict-template-expressions', rule, { } `, }, + // allowArray + { + options: [{ allowArray: true }], + code: ` + const arg = []; + const msg = \`arg = \${arg}\`; + `, + }, + { + options: [{ allowArray: true }], + code: ` + const arg = []; + const msg = \`arg = \${arg || 'default'}\`; + `, + }, + { + options: [{ allowArray: true }], + code: ` + const arg = []; + function test(arg: T) { + return \`arg = \${arg}\`; + } + `, + }, // allowAny { options: [{ allowAny: true }], @@ -341,6 +365,20 @@ ruleTester.run('restrict-template-expressions', rule, { ], options: [{ allowNullish: false }], }, + { + code: ` + const msg = \`arg = \${[null, 2]}\`; + `, + errors: [ + { + messageId: 'invalidType', + data: { type: '(number | null)[]' }, + line: 2, + column: 30, + }, + ], + options: [{ allowNullish: false, allowArray: true }], + }, { code: ` declare const arg: number; @@ -369,11 +407,7 @@ ruleTester.run('restrict-template-expressions', rule, { column: 30, }, ], - options: [ - { - allowBoolean: false, - }, - ], + options: [{ allowBoolean: false }], }, { options: [{ allowNumber: true, allowBoolean: true, allowNullish: true }], From 0a60320ec31e1840612a6019919e487774c44e03 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Mon, 26 Feb 2024 08:04:42 -0600 Subject: [PATCH 09/10] simplify test --- .../tests/rules/restrict-template-expressions.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin/tests/rules/restrict-template-expressions.test.ts b/packages/eslint-plugin/tests/rules/restrict-template-expressions.test.ts index 2bdbc115053..698471ae00a 100644 --- a/packages/eslint-plugin/tests/rules/restrict-template-expressions.test.ts +++ b/packages/eslint-plugin/tests/rules/restrict-template-expressions.test.ts @@ -367,12 +367,12 @@ ruleTester.run('restrict-template-expressions', rule, { }, { code: ` - const msg = \`arg = \${[null, 2]}\`; + const msg = \`arg = \${[, 2]}\`; `, errors: [ { messageId: 'invalidType', - data: { type: '(number | null)[]' }, + data: { type: '(number | undefined)[]' }, line: 2, column: 30, }, From 4c375acbe0ca529e18071cb7d7a15f7fdd45af9e Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Fri, 8 Mar 2024 07:05:31 -0600 Subject: [PATCH 10/10] suppress lint error --- .../eslint-plugin/src/rules/restrict-template-expressions.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts index 6fcb6579393..960617ab044 100644 --- a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts +++ b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts @@ -31,6 +31,7 @@ const optionTesters = ( 'Array', (type, checker, recursivelyCheckType): boolean => checker.isArrayType(type) && + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion recursivelyCheckType(type.getNumberIndexType()!), ], // eslint-disable-next-line @typescript-eslint/internal/prefer-ast-types-enum