From c035774ee63307015e522742a8cd4e4e0ae4ea93 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Thu, 28 Sep 2023 21:52:25 -0500 Subject: [PATCH 01/53] 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 5da963a8219e..83cc441e85f7 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/53] 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 48d7379a4b9e..fb577e345606 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/53] 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 e7e295c443a9..d73449fc4cec 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/53] 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 194ddc4260cb..1a4d828e764e 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/53] 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 fb577e345606..e4b0c21399c4 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/53] 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 e4b0c21399c4..5b455bee21a3 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/53] 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 5b455bee21a3..4479059e614f 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/53] 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 4479059e614f..6fcb65793938 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 1701f9a44960..2bdbc1150537 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/53] 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 2bdbc1150537..698471ae00a3 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 cb1f33eda3474246629d27a14620c5fd7e3a3e4b Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Mon, 26 Feb 2024 18:08:13 -0600 Subject: [PATCH 10/53] ignoredtypenames --- .../rules/restrict-template-expressions.ts | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts index 6fcb65793938..0f50b2b5359e 100644 --- a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts +++ b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts @@ -49,7 +49,9 @@ const optionTesters = ( tester, })); type Options = [ - { [Type in (typeof optionTesters)[number]['option']]?: boolean }, + { [Type in (typeof optionTesters)[number]['option']]?: boolean } & { + ignoredTypeNames?: string[]; + }, ]; type MessageId = 'invalidType'; @@ -71,15 +73,23 @@ export default createRule({ { type: 'object', additionalProperties: false, - properties: Object.fromEntries( - optionTesters.map(({ option, type }) => [ - option, - { - description: `Whether to allow \`${type.toLowerCase()}\` typed values in template expressions.`, - type: 'boolean', + properties: { + ...Object.fromEntries( + optionTesters.map(({ option, type }) => [ + option, + { + description: `Whether to allow \`${type.toLowerCase()}\` typed values in template expressions.`, + type: 'boolean', + }, + ]), + ), + ignoredTypeNames: { + type: 'array', + items: { + type: 'string', }, - ]), - ), + }, + }, }, ], }, @@ -90,9 +100,10 @@ export default createRule({ allowNullish: true, allowNumber: true, allowRegExp: true, + ignoredTypeNames: ['Error', 'RegExp', 'URL', 'URLSearchParams'], }, ], - create(context, [options]) { + create(context, [{ ignoredTypeNames = [], ...options }]) { const services = getParserServices(context); const checker = services.program.getTypeChecker(); const enabledOptionTesters = optionTesters.filter( @@ -134,6 +145,7 @@ export default createRule({ return ( isTypeFlagSet(innerType, TypeFlags.StringLike) || + ignoredTypeNames.includes(getTypeName(checker, innerType)) || enabledOptionTesters.some(({ tester }) => tester(innerType, checker, recursivelyCheckType), ) From f5396c671477b5464207ef7f567b2b2b66afc747 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Sat, 16 Mar 2024 08:50:23 -0500 Subject: [PATCH 11/53] restore suppression --- .../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 0f50b2b5359e..0368f44a4e32 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 From c41785b67da27b62e7a007734f455b903d34f89a Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Sat, 8 Jun 2024 17:20:28 -0500 Subject: [PATCH 12/53] rename option --- .../src/rules/restrict-template-expressions.ts | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts index 899ca00a8d80..081f9c1dec3e 100644 --- a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts +++ b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts @@ -43,7 +43,7 @@ const optionTesters = ( (type, checker): boolean => getTypeName(checker, type) === 'RegExp', ], ['Never', isTypeNeverType], - ] satisfies [string, OptionTester][] + ] as const satisfies [string, OptionTester][] ).map(([type, tester]) => ({ type, option: `allow${type}` as const, @@ -51,7 +51,7 @@ const optionTesters = ( })); type Options = [ { [Type in (typeof optionTesters)[number]['option']]?: boolean } & { - ignoredTypeNames?: string[]; + allow?: string[]; }, ]; @@ -96,12 +96,7 @@ export default createRule({ }, ]), ), - ignoredTypeNames: { - type: 'array', - items: { - type: 'string', - }, - }, + allow: { type: 'array', items: { type: 'string' } }, }, }, ], @@ -113,10 +108,10 @@ export default createRule({ allowNullish: true, allowNumber: true, allowRegExp: true, - ignoredTypeNames: ['Error', 'RegExp', 'URL', 'URLSearchParams'], + allow: ['Error', 'RegExp', 'URL', 'URLSearchParams'], }, ], - create(context, [{ ignoredTypeNames = [], ...options }]) { + create(context, [{ allow = [], ...options }]) { const services = getParserServices(context); const checker = services.program.getTypeChecker(); const enabledOptionTesters = optionTesters.filter( @@ -158,7 +153,7 @@ export default createRule({ return ( isTypeFlagSet(innerType, TypeFlags.StringLike) || - ignoredTypeNames.includes(getTypeName(checker, innerType)) || + allow.includes(getTypeName(checker, innerType)) || enabledOptionTesters.some(({ tester }) => tester(innerType, checker, recursivelyCheckType), ) From ca0bb6e82f78e6f514ff3fee7712ca1bbee17b13 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Sat, 8 Jun 2024 17:24:31 -0500 Subject: [PATCH 13/53] change option type --- .../src/rules/restrict-template-expressions.ts | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts index 081f9c1dec3e..4bc6c2e4a49d 100644 --- a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts +++ b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts @@ -1,8 +1,10 @@ +import { typeOrValueSpecifierSchema } from '@typescript-eslint/type-utils'; import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import type { Type, TypeChecker } from 'typescript'; import { TypeFlags } from 'typescript'; +import type { TypeOrValueSpecifier } from '../util'; import { createRule, getConstrainedTypeAtLocation, @@ -44,14 +46,10 @@ const optionTesters = ( ], ['Never', isTypeNeverType], ] as const satisfies [string, OptionTester][] -).map(([type, tester]) => ({ - type, - option: `allow${type}` as const, - tester, -})); +).map(([type, tester]) => ({ type, option: `allow${type}` as const, tester })); type Options = [ { [Type in (typeof optionTesters)[number]['option']]?: boolean } & { - allow?: string[]; + allow?: TypeOrValueSpecifier[]; }, ]; @@ -96,7 +94,7 @@ export default createRule({ }, ]), ), - allow: { type: 'array', items: { type: 'string' } }, + allow: { type: 'array', items: typeOrValueSpecifierSchema }, }, }, ], From aba1edb3d9a84a4874b72274a0e443d16ffc5e67 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Sat, 8 Jun 2024 17:34:56 -0500 Subject: [PATCH 14/53] change to typematchesspecifier --- .../rules/restrict-template-expressions.ts | 12 +++- .../type-utils/src/TypeOrValueSpecifier.ts | 55 +++++-------------- 2 files changed, 23 insertions(+), 44 deletions(-) diff --git a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts index 4bc6c2e4a49d..01cba96bc536 100644 --- a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts +++ b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts @@ -1,4 +1,7 @@ -import { typeOrValueSpecifierSchema } from '@typescript-eslint/type-utils'; +import { + typeMatchesSpecifier, + typeOrValueSpecifierSchema, +} from '@typescript-eslint/type-utils'; import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; import type { Type, TypeChecker } from 'typescript'; @@ -111,7 +114,8 @@ export default createRule({ ], create(context, [{ allow = [], ...options }]) { const services = getParserServices(context); - const checker = services.program.getTypeChecker(); + const { program } = services; + const checker = program.getTypeChecker(); const enabledOptionTesters = optionTesters.filter( ({ option }) => options[option], ); @@ -151,7 +155,9 @@ export default createRule({ return ( isTypeFlagSet(innerType, TypeFlags.StringLike) || - allow.includes(getTypeName(checker, innerType)) || + allow.some(specifier => + typeMatchesSpecifier(innerType, specifier, program), + ) || enabledOptionTesters.some(({ tester }) => tester(innerType, checker, recursivelyCheckType), ) diff --git a/packages/type-utils/src/TypeOrValueSpecifier.ts b/packages/type-utils/src/TypeOrValueSpecifier.ts index cf23b059c802..d2bdef397c65 100644 --- a/packages/type-utils/src/TypeOrValueSpecifier.ts +++ b/packages/type-utils/src/TypeOrValueSpecifier.ts @@ -27,37 +27,26 @@ export type TypeOrValueSpecifier = | PackageSpecifier | string; -export const typeOrValueSpecifierSchema: JSONSchema4 = { +export const typeOrValueSpecifierSchema = { oneOf: [ - { - type: 'string', - }, + { type: 'string' }, { type: 'object', additionalProperties: false, properties: { - from: { - type: 'string', - enum: ['file'], - }, + from: { type: 'string', enum: ['file'] }, name: { oneOf: [ - { - type: 'string', - }, + { type: 'string' }, { type: 'array', minItems: 1, uniqueItems: true, - items: { - type: 'string', - }, + items: { type: 'string' }, }, ], }, - path: { - type: 'string', - }, + path: { type: 'string' }, }, required: ['from', 'name'], }, @@ -65,22 +54,15 @@ export const typeOrValueSpecifierSchema: JSONSchema4 = { type: 'object', additionalProperties: false, properties: { - from: { - type: 'string', - enum: ['lib'], - }, + from: { type: 'string', enum: ['lib'] }, name: { oneOf: [ - { - type: 'string', - }, + { type: 'string' }, { type: 'array', minItems: 1, uniqueItems: true, - items: { - type: 'string', - }, + items: { type: 'string' }, }, ], }, @@ -91,33 +73,24 @@ export const typeOrValueSpecifierSchema: JSONSchema4 = { type: 'object', additionalProperties: false, properties: { - from: { - type: 'string', - enum: ['package'], - }, + from: { type: 'string', enum: ['package'] }, name: { oneOf: [ - { - type: 'string', - }, + { type: 'string' }, { type: 'array', minItems: 1, uniqueItems: true, - items: { - type: 'string', - }, + items: { type: 'string' }, }, ], }, - package: { - type: 'string', - }, + package: { type: 'string' }, }, required: ['from', 'name', 'package'], }, ], -}; +} as const satisfies JSONSchema4; function specifierNameMatches(type: ts.Type, name: string[] | string): boolean { if (typeof name === 'string') { From b6f9be9486c0225077fde39055d5baf0d8d73230 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Sat, 8 Jun 2024 17:58:00 -0500 Subject: [PATCH 15/53] finish --- packages/eslint-plugin/src/rules/no-floating-promises.ts | 8 +++++--- .../src/rules/restrict-template-expressions.ts | 8 +++----- packages/type-utils/src/TypeOrValueSpecifier.ts | 7 +++++++ packages/type-utils/src/isTypeReadonly.ts | 8 ++------ 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-floating-promises.ts b/packages/eslint-plugin/src/rules/no-floating-promises.ts index 1ae5e602ae0e..0881211e53f2 100644 --- a/packages/eslint-plugin/src/rules/no-floating-promises.ts +++ b/packages/eslint-plugin/src/rules/no-floating-promises.ts @@ -11,7 +11,7 @@ import { OperatorPrecedence, readonlynessOptionsDefaults, readonlynessOptionsSchema, - typeMatchesSpecifier, + typeMatchesSomeSpecifier, } from '../util'; type Options = [ @@ -365,8 +365,10 @@ export default createRule({ // Ignore anything specified by `allowForKnownSafePromises` option. if ( - allowForKnownSafePromises.some(allowedType => - typeMatchesSpecifier(type, allowedType, services.program), + typeMatchesSomeSpecifier( + type, + allowForKnownSafePromises, + services.program, ) ) { return false; diff --git a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts index 01cba96bc536..7985eb9c7b66 100644 --- a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts +++ b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts @@ -1,5 +1,5 @@ import { - typeMatchesSpecifier, + typeMatchesSomeSpecifier, typeOrValueSpecifierSchema, } from '@typescript-eslint/type-utils'; import type { TSESTree } from '@typescript-eslint/utils'; @@ -112,7 +112,7 @@ export default createRule({ allow: ['Error', 'RegExp', 'URL', 'URLSearchParams'], }, ], - create(context, [{ allow = [], ...options }]) { + create(context, [{ allow, ...options }]) { const services = getParserServices(context); const { program } = services; const checker = program.getTypeChecker(); @@ -155,9 +155,7 @@ export default createRule({ return ( isTypeFlagSet(innerType, TypeFlags.StringLike) || - allow.some(specifier => - typeMatchesSpecifier(innerType, specifier, program), - ) || + typeMatchesSomeSpecifier(innerType, allow, program) || enabledOptionTesters.some(({ tester }) => tester(innerType, checker, recursivelyCheckType), ) diff --git a/packages/type-utils/src/TypeOrValueSpecifier.ts b/packages/type-utils/src/TypeOrValueSpecifier.ts index d2bdef397c65..85040040917e 100644 --- a/packages/type-utils/src/TypeOrValueSpecifier.ts +++ b/packages/type-utils/src/TypeOrValueSpecifier.ts @@ -191,3 +191,10 @@ export function typeMatchesSpecifier( ); } } + +export const typeMatchesSomeSpecifier = ( + type: ts.Type, + specifiers: TypeOrValueSpecifier[] = [], + program: ts.Program, +): boolean => + specifiers.some(specifier => typeMatchesSpecifier(type, specifier, program)); diff --git a/packages/type-utils/src/isTypeReadonly.ts b/packages/type-utils/src/isTypeReadonly.ts index 6362adc500b4..75eafcb42866 100644 --- a/packages/type-utils/src/isTypeReadonly.ts +++ b/packages/type-utils/src/isTypeReadonly.ts @@ -6,7 +6,7 @@ import * as ts from 'typescript'; import { getTypeOfPropertyOfType } from './propertyTypes'; import type { TypeOrValueSpecifier } from './TypeOrValueSpecifier'; import { - typeMatchesSpecifier, + typeMatchesSomeSpecifier, typeOrValueSpecifierSchema, } from './TypeOrValueSpecifier'; @@ -232,11 +232,7 @@ function isTypeReadonlyRecurser( const checker = program.getTypeChecker(); seenTypes.add(type); - if ( - options.allow?.some(specifier => - typeMatchesSpecifier(type, specifier, program), - ) - ) { + if (typeMatchesSomeSpecifier(type, options.allow, program)) { return Readonlyness.Readonly; } From d6a81ef4e571b9d439774ea227a4b4efa3ede4bd Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Sun, 9 Jun 2024 12:08:53 -0500 Subject: [PATCH 16/53] change defaults --- .../src/rules/restrict-template-expressions.ts | 2 +- .../rules/restrict-template-expressions.test.ts | 14 +++----------- 2 files changed, 4 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 7985eb9c7b66..03fd32010057 100644 --- a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts +++ b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts @@ -109,7 +109,7 @@ export default createRule({ allowNullish: true, allowNumber: true, allowRegExp: true, - allow: ['Error', 'RegExp', 'URL', 'URLSearchParams'], + allow: ['Error', 'URL', 'URLSearchParams'], }, ], create(context, [{ allow, ...options }]) { 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 698471ae00a3..883ff044d257 100644 --- a/packages/eslint-plugin/tests/rules/restrict-template-expressions.test.ts +++ b/packages/eslint-plugin/tests/rules/restrict-template-expressions.test.ts @@ -7,10 +7,7 @@ const rootPath = getFixturesRootDir(); const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser', - parserOptions: { - tsconfigRootDir: rootPath, - project: './tsconfig.json', - }, + parserOptions: { tsconfigRootDir: rootPath, project: './tsconfig.json' }, }); ruleTester.run('restrict-template-expressions', rule, { @@ -288,7 +285,7 @@ ruleTester.run('restrict-template-expressions', rule, { code: ` // more variants may be added to Foo in the future type Foo = { type: 'a'; value: number }; - + function checkFoosAreMatching(foo1: Foo, foo2: Foo) { if (foo1.type !== foo2.type) { // since Foo currently only has one variant, this code is never run, and \`foo1.type\` has type \`never\`. @@ -459,12 +456,7 @@ ruleTester.run('restrict-template-expressions', rule, { } `, errors: [ - { - messageId: 'invalidType', - data: { type: 'T' }, - line: 3, - column: 27, - }, + { messageId: 'invalidType', data: { type: 'T' }, line: 3, column: 27 }, ], }, { From 6a0a384731522180405dd9f69d95713eaca24d51 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Sun, 9 Jun 2024 13:59:39 -0500 Subject: [PATCH 17/53] add tests --- .../rules/restrict-template-expressions.test.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) 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 883ff044d257..3d6d0cd57d2a 100644 --- a/packages/eslint-plugin/tests/rules/restrict-template-expressions.test.ts +++ b/packages/eslint-plugin/tests/rules/restrict-template-expressions.test.ts @@ -312,6 +312,14 @@ ruleTester.run('restrict-template-expressions', rule, { } `, }, + // allow + { + options: [{ allow: [`Promise`] }], + code: 'const msg = `arg = ${Promise.resolve()}`;', + }, + 'const msg = `arg = ${new URL()}`;', + 'const msg = `arg = ${new URLSearchParams()}`;', + 'const msg = `arg = ${new Error()}`;', 'const msg = `arg = ${false}`;', 'const msg = `arg = ${null}`;', 'const msg = `arg = ${undefined}`;', @@ -376,6 +384,15 @@ ruleTester.run('restrict-template-expressions', rule, { ], options: [{ allowNullish: false, allowArray: true }], }, + { + code: 'const msg = `arg = ${Promise.resolve()}`;', + errors: [{ messageId: 'invalidType' }], + }, + { + code: 'const msg = `arg = ${new URL()}`;', + options: [{ allow: [] }], + errors: [{ messageId: 'invalidType' }], + }, { code: ` declare const arg: number; From ea164f5bafe1006bf64771fd220ed9404391d9bd Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Sun, 9 Jun 2024 14:08:37 -0500 Subject: [PATCH 18/53] change defaults --- .../eslint-plugin/src/rules/restrict-template-expressions.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts index 03fd32010057..7bf7a7fa7b86 100644 --- a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts +++ b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts @@ -109,7 +109,10 @@ export default createRule({ allowNullish: true, allowNumber: true, allowRegExp: true, - allow: ['Error', 'URL', 'URLSearchParams'], + allow: ['Error', 'URL', 'URLSearchParams'].map(name => ({ + from: 'lib', + name, + })), }, ], create(context, [{ allow, ...options }]) { From ecb9e27c1297b0b3fabc0a843fcaf6f8f9bed964 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Sun, 9 Jun 2024 14:24:02 -0500 Subject: [PATCH 19/53] add documentation --- .../rules/restrict-template-expressions.mdx | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/packages/eslint-plugin/docs/rules/restrict-template-expressions.mdx b/packages/eslint-plugin/docs/rules/restrict-template-expressions.mdx index f99cb0ab8e60..1e02f7486197 100644 --- a/packages/eslint-plugin/docs/rules/restrict-template-expressions.mdx +++ b/packages/eslint-plugin/docs/rules/restrict-template-expressions.mdx @@ -126,6 +126,23 @@ const arg = ['foo', 'bar']; const msg1 = `arg = ${arg}`; ``` +### `allow` + +This option takes an array of type specifiers to allow. Each item in the array must have one of the following forms: + +- A type defined in a file (`{ from: "file", name: "Foo", path: "src/foo-file.ts" }` with `path` being an optional path relative to the project root directory) +- A type from the default library (`{ from: "lib", name: "PromiseLike" }`) +- A type from a package (`{ from: "package", name: "Foo", package: "foo-lib" }`, this also works for types defined in a typings package). + +Examples of additional **correct** code for this rule with the default option `{ allow: [{ from: 'lib', name: 'Error' }, { from: 'lib', name: 'URL' }, { from: 'lib', name: 'URLSearchParams' }] }``: + +``` +const error = new Error(); +const url = new URL(); +const urlSearchParams = new URLSearchParams(); +const msg1 = `args = ${error}, ${url}, ${urlSearchParams}`; +``` + ## 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 c03c335bb84d978500b046dfab2dff7f5d5f5728 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Sun, 9 Jun 2024 14:32:38 -0500 Subject: [PATCH 20/53] removed extra quote --- .../eslint-plugin/docs/rules/restrict-template-expressions.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/docs/rules/restrict-template-expressions.mdx b/packages/eslint-plugin/docs/rules/restrict-template-expressions.mdx index 1e02f7486197..0bbadff00ca2 100644 --- a/packages/eslint-plugin/docs/rules/restrict-template-expressions.mdx +++ b/packages/eslint-plugin/docs/rules/restrict-template-expressions.mdx @@ -134,7 +134,7 @@ This option takes an array of type specifiers to allow. Each item in the array m - A type from the default library (`{ from: "lib", name: "PromiseLike" }`) - A type from a package (`{ from: "package", name: "Foo", package: "foo-lib" }`, this also works for types defined in a typings package). -Examples of additional **correct** code for this rule with the default option `{ allow: [{ from: 'lib', name: 'Error' }, { from: 'lib', name: 'URL' }, { from: 'lib', name: 'URLSearchParams' }] }``: +Examples of additional **correct** code for this rule with the default option `{ allow: [{ from: 'lib', name: 'Error' }, { from: 'lib', name: 'URL' }, { from: 'lib', name: 'URLSearchParams' }] }`: ``` const error = new Error(); From 1b16bcb0d1aa6c44dea04daeff28a777c1520f4a Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Sun, 9 Jun 2024 14:45:57 -0500 Subject: [PATCH 21/53] add description --- .../src/rules/restrict-template-expressions.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts index 7bf7a7fa7b86..4369b85e64ad 100644 --- a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts +++ b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts @@ -97,7 +97,11 @@ export default createRule({ }, ]), ), - allow: { type: 'array', items: typeOrValueSpecifierSchema }, + allow: { + description: `Types to allow in template expressions.`, + type: 'array', + items: typeOrValueSpecifierSchema, + }, }, }, ], From 1a3b93408fa24321df46c456d588f1a97105b1e7 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Sun, 9 Jun 2024 14:47:23 -0500 Subject: [PATCH 22/53] add language --- .../eslint-plugin/docs/rules/restrict-template-expressions.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/docs/rules/restrict-template-expressions.mdx b/packages/eslint-plugin/docs/rules/restrict-template-expressions.mdx index 0bbadff00ca2..daa4caaa8db2 100644 --- a/packages/eslint-plugin/docs/rules/restrict-template-expressions.mdx +++ b/packages/eslint-plugin/docs/rules/restrict-template-expressions.mdx @@ -136,7 +136,7 @@ This option takes an array of type specifiers to allow. Each item in the array m Examples of additional **correct** code for this rule with the default option `{ allow: [{ from: 'lib', name: 'Error' }, { from: 'lib', name: 'URL' }, { from: 'lib', name: 'URLSearchParams' }] }`: -``` +```ts showPlaygroundButton const error = new Error(); const url = new URL(); const urlSearchParams = new URLSearchParams(); From 2cb8963cd4f439942c7894856a3c0e153047188f Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Sun, 9 Jun 2024 14:48:09 -0500 Subject: [PATCH 23/53] fix JS error --- .../eslint-plugin/docs/rules/restrict-template-expressions.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/docs/rules/restrict-template-expressions.mdx b/packages/eslint-plugin/docs/rules/restrict-template-expressions.mdx index daa4caaa8db2..5bb76395f2a0 100644 --- a/packages/eslint-plugin/docs/rules/restrict-template-expressions.mdx +++ b/packages/eslint-plugin/docs/rules/restrict-template-expressions.mdx @@ -138,7 +138,7 @@ Examples of additional **correct** code for this rule with the default option `{ ```ts showPlaygroundButton const error = new Error(); -const url = new URL(); +const url = new URL('https://typescript-eslint.io'); const urlSearchParams = new URLSearchParams(); const msg1 = `args = ${error}, ${url}, ${urlSearchParams}`; ``` From a0dd1e7a451fa72bbc4c97f85c478035112043a7 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Sun, 9 Jun 2024 15:24:15 -0500 Subject: [PATCH 24/53] fix types from node --- .../src/rules/restrict-template-expressions.ts | 5 +---- packages/type-utils/src/TypeOrValueSpecifier.ts | 13 +++++++++++-- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts index 4369b85e64ad..703ade9c2bb3 100644 --- a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts +++ b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts @@ -113,10 +113,7 @@ export default createRule({ allowNullish: true, allowNumber: true, allowRegExp: true, - allow: ['Error', 'URL', 'URLSearchParams'].map(name => ({ - from: 'lib', - name, - })), + allow: [{ from: 'lib', name: ['Error', 'URL', 'URLSearchParams'] }], }, ], create(context, [{ allow, ...options }]) { diff --git a/packages/type-utils/src/TypeOrValueSpecifier.ts b/packages/type-utils/src/TypeOrValueSpecifier.ts index 85040040917e..a20a576601c5 100644 --- a/packages/type-utils/src/TypeOrValueSpecifier.ts +++ b/packages/type-utils/src/TypeOrValueSpecifier.ts @@ -154,8 +154,17 @@ function typeDeclaredInLib( if (declarationFiles.length === 0) { return true; } - return declarationFiles.some(declaration => - program.isSourceFileDefaultLibrary(declaration), + return ( + declarationFiles.some( + declaration => + program.isSourceFileDefaultLibrary(declaration) || + (program.sourceFileToPackageName.get(declaration.path) == null && + /\/node_modules\/@types\/(bun|node)\//.test(declaration.path)), + ) || + // Declared in types of runtime - Treat it as if it's from lib. + ['bun', 'node'].some(runtime => + typeDeclaredInPackage(runtime, declarationFiles, program), + ) ); } From dab25fe6eb9088529a3631678e98ff00576648ce Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Sun, 9 Jun 2024 16:11:58 -0500 Subject: [PATCH 25/53] extract repeated documentation --- packages/eslint-plugin/docs/TypeOrValueSpecifier.md | 7 +++++++ .../eslint-plugin/docs/rules/no-floating-promises.mdx | 8 ++------ .../docs/rules/prefer-readonly-parameter-types.mdx | 10 ++-------- .../docs/rules/restrict-template-expressions.mdx | 7 ++----- 4 files changed, 13 insertions(+), 19 deletions(-) create mode 100644 packages/eslint-plugin/docs/TypeOrValueSpecifier.md diff --git a/packages/eslint-plugin/docs/TypeOrValueSpecifier.md b/packages/eslint-plugin/docs/TypeOrValueSpecifier.md new file mode 100644 index 000000000000..6874582fac49 --- /dev/null +++ b/packages/eslint-plugin/docs/TypeOrValueSpecifier.md @@ -0,0 +1,7 @@ +This option takes an array of type specifiers to {props.verb}. Each item in the array must have one of the following forms: + +- A type defined in a file (`{ from: "file", name: "Foo", path: "src/foo-file.ts" }` with `path` being an optional path relative to the project root directory) +- A type from the default library (`{ from: "lib", name: "PromiseLike" }`) +- A type from a package (`{ from: "package", name: "Foo", package: "foo-lib" }`, this also works for types defined in a typings package). + +Additionally, a type may be defined just as a simple string, which then matches the type independently of its origin. diff --git a/packages/eslint-plugin/docs/rules/no-floating-promises.mdx b/packages/eslint-plugin/docs/rules/no-floating-promises.mdx index e3f047e6e4f0..f8307e9fed05 100644 --- a/packages/eslint-plugin/docs/rules/no-floating-promises.mdx +++ b/packages/eslint-plugin/docs/rules/no-floating-promises.mdx @@ -4,6 +4,7 @@ description: 'Require Promise-like statements to be handled appropriately.' import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; +import TypeOrValueSpecifier from '../TypeOrValueSpecifier.md'; > 🛑 This file is source code, not the primary documentation location! 🛑 > @@ -123,12 +124,7 @@ await (async function () { This option allows marking specific types as "safe" to be floating. For example, you may need to do this in the case of libraries whose APIs return Promises whose rejections are safely handled by the library. -This option takes an array of type specifiers to consider safe. -Each item in the array must have one of the following forms: - -- A type defined in a file (`{ from: "file", name: "Foo", path: "src/foo-file.ts" }` with `path` being an optional path relative to the project root directory) -- A type from the default library (`{ from: "lib", name: "PromiseLike" }`) -- A type from a package (`{ from: "package", name: "Foo", package: "foo-lib" }`, this also works for types defined in a typings package). + Examples of code for this rule with: diff --git a/packages/eslint-plugin/docs/rules/prefer-readonly-parameter-types.mdx b/packages/eslint-plugin/docs/rules/prefer-readonly-parameter-types.mdx index e2ddcef23048..57c51e2d7e3a 100644 --- a/packages/eslint-plugin/docs/rules/prefer-readonly-parameter-types.mdx +++ b/packages/eslint-plugin/docs/rules/prefer-readonly-parameter-types.mdx @@ -4,6 +4,7 @@ description: 'Require function parameters to be typed as `readonly` to prevent a import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; +import TypeOrValueSpecifier from '../TypeOrValueSpecifier.md'; > 🛑 This file is source code, not the primary documentation location! 🛑 > @@ -142,14 +143,7 @@ interface Foo { Some complex types cannot easily be made readonly, for example the `HTMLElement` type or the `JQueryStatic` type from `@types/jquery`. This option allows you to globally disable reporting of such types. -This option takes an array of type specifiers to ignore. -Each item in the array must have one of the following forms: - -- A type defined in a file (`{ from: "file", name: "Foo", path: "src/foo-file.ts" }` with `path` being an optional path relative to the project root directory) -- A type from the default library (`{ from: "lib", name: "Foo" }`) -- A type from a package (`{ from: "package", name: "Foo", package: "foo-lib" }`, this also works for types defined in a typings package). - -Additionally, a type may be defined just as a simple string, which then matches the type independently of its origin. + Examples of code for this rule with: diff --git a/packages/eslint-plugin/docs/rules/restrict-template-expressions.mdx b/packages/eslint-plugin/docs/rules/restrict-template-expressions.mdx index 5bb76395f2a0..c04164c5bd8c 100644 --- a/packages/eslint-plugin/docs/rules/restrict-template-expressions.mdx +++ b/packages/eslint-plugin/docs/rules/restrict-template-expressions.mdx @@ -4,6 +4,7 @@ description: 'Enforce template literal expressions to be of `string` type.' import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; +import TypeOrValueSpecifier from '../TypeOrValueSpecifier.md'; > 🛑 This file is source code, not the primary documentation location! 🛑 > @@ -128,11 +129,7 @@ const msg1 = `arg = ${arg}`; ### `allow` -This option takes an array of type specifiers to allow. Each item in the array must have one of the following forms: - -- A type defined in a file (`{ from: "file", name: "Foo", path: "src/foo-file.ts" }` with `path` being an optional path relative to the project root directory) -- A type from the default library (`{ from: "lib", name: "PromiseLike" }`) -- A type from a package (`{ from: "package", name: "Foo", package: "foo-lib" }`, this also works for types defined in a typings package). + Examples of additional **correct** code for this rule with the default option `{ allow: [{ from: 'lib', name: 'Error' }, { from: 'lib', name: 'URL' }, { from: 'lib', name: 'URLSearchParams' }] }`: From 6551b47a0135f65b1f1d8b0187c453f1a1a9ea7c Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Sun, 9 Jun 2024 16:14:30 -0500 Subject: [PATCH 26/53] update comment clarifying string array --- packages/eslint-plugin/docs/TypeOrValueSpecifier.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/eslint-plugin/docs/TypeOrValueSpecifier.md b/packages/eslint-plugin/docs/TypeOrValueSpecifier.md index 6874582fac49..e89299295055 100644 --- a/packages/eslint-plugin/docs/TypeOrValueSpecifier.md +++ b/packages/eslint-plugin/docs/TypeOrValueSpecifier.md @@ -1,7 +1,9 @@ This option takes an array of type specifiers to {props.verb}. Each item in the array must have one of the following forms: -- A type defined in a file (`{ from: "file", name: "Foo", path: "src/foo-file.ts" }` with `path` being an optional path relative to the project root directory) -- A type from the default library (`{ from: "lib", name: "PromiseLike" }`) -- A type from a package (`{ from: "package", name: "Foo", package: "foo-lib" }`, this also works for types defined in a typings package). +- A type (or multiple types) defined in a file (`{ from: "file", name: "Foo", path: "src/foo-file.ts" }` with `path` being an optional path relative to the project root directory) +- A type (or multiple types) from the default library (`{ from: "lib", name: "Foo" }`) +- A type (or multiple types) from a package (`{ from: "package", name: "Foo", package: "foo-lib" }`, this also works for types defined in a typings package). + +In any of the three forms, `name` can also be an array of strings representing multiple types, rather than just one string. Additionally, a type may be defined just as a simple string, which then matches the type independently of its origin. From e25e84223c5935b4caff6d6d31cac8b701a3a333 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Mon, 10 Jun 2024 06:41:54 -0500 Subject: [PATCH 27/53] update import path --- packages/type-utils/tests/TypeOrValueSpecifier.test.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/type-utils/tests/TypeOrValueSpecifier.test.ts b/packages/type-utils/tests/TypeOrValueSpecifier.test.ts index 33db003f4758..59dd847d91fc 100644 --- a/packages/type-utils/tests/TypeOrValueSpecifier.test.ts +++ b/packages/type-utils/tests/TypeOrValueSpecifier.test.ts @@ -3,11 +3,8 @@ import type { TSESTree } from '@typescript-eslint/utils'; import Ajv from 'ajv'; import path from 'path'; -import type { TypeOrValueSpecifier } from '../src/TypeOrValueSpecifier'; -import { - typeMatchesSpecifier, - typeOrValueSpecifierSchema, -} from '../src/TypeOrValueSpecifier'; +import type { TypeOrValueSpecifier } from '../src'; +import { typeMatchesSpecifier, typeOrValueSpecifierSchema } from '../src'; describe('TypeOrValueSpecifier', () => { describe('Schema', () => { From d964a121cb6552cb0a692a152149b0b7ec2f3457 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Mon, 10 Jun 2024 06:42:54 -0500 Subject: [PATCH 28/53] update snapshot --- .../restrict-template-expressions.shot | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/restrict-template-expressions.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/restrict-template-expressions.shot index 06f982288ebc..29d9783c8c99 100644 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/restrict-template-expressions.shot +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/restrict-template-expressions.shot @@ -91,3 +91,13 @@ const arg = ['foo', 'bar']; const msg1 = \`arg = \${arg}\`; " `; + +exports[`Validating rule docs restrict-template-expressions.mdx code examples ESLint output 11`] = ` +" + +const error = new Error(); +const url = new URL('https://typescript-eslint.io'); +const urlSearchParams = new URLSearchParams(); +const msg1 = \`args = \${error}, \${url}, \${urlSearchParams}\`; +" +`; From aba6dcdcb655a5b14ed5a58d6cb8d54ed9eb4ef4 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Mon, 10 Jun 2024 06:44:16 -0500 Subject: [PATCH 29/53] revert unnecessary formatting change --- .../tests/rules/restrict-template-expressions.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 3d6d0cd57d2a..26839ba6be51 100644 --- a/packages/eslint-plugin/tests/rules/restrict-template-expressions.test.ts +++ b/packages/eslint-plugin/tests/rules/restrict-template-expressions.test.ts @@ -285,7 +285,7 @@ ruleTester.run('restrict-template-expressions', rule, { code: ` // more variants may be added to Foo in the future type Foo = { type: 'a'; value: number }; - + function checkFoosAreMatching(foo1: Foo, foo2: Foo) { if (foo1.type !== foo2.type) { // since Foo currently only has one variant, this code is never run, and \`foo1.type\` has type \`never\`. From 48fd9912217b138c9f0339c9c7411c6534106958 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Mon, 10 Jun 2024 06:59:35 -0500 Subject: [PATCH 30/53] add test for type from Node --- .../type-utils/src/TypeOrValueSpecifier.ts | 16 +++----- .../tests/TypeOrValueSpecifier.test.ts | 38 ++++--------------- 2 files changed, 13 insertions(+), 41 deletions(-) diff --git a/packages/type-utils/src/TypeOrValueSpecifier.ts b/packages/type-utils/src/TypeOrValueSpecifier.ts index a20a576601c5..2de39259a93a 100644 --- a/packages/type-utils/src/TypeOrValueSpecifier.ts +++ b/packages/type-utils/src/TypeOrValueSpecifier.ts @@ -154,17 +154,11 @@ function typeDeclaredInLib( if (declarationFiles.length === 0) { return true; } - return ( - declarationFiles.some( - declaration => - program.isSourceFileDefaultLibrary(declaration) || - (program.sourceFileToPackageName.get(declaration.path) == null && - /\/node_modules\/@types\/(bun|node)\//.test(declaration.path)), - ) || - // Declared in types of runtime - Treat it as if it's from lib. - ['bun', 'node'].some(runtime => - typeDeclaredInPackage(runtime, declarationFiles, program), - ) + return declarationFiles.some( + declaration => + program.isSourceFileDefaultLibrary(declaration) || + (program.sourceFileToPackageName.get(declaration.path) == null && + /\/node_modules\/@types\/(bun|node)\//.test(declaration.path)), ); } diff --git a/packages/type-utils/tests/TypeOrValueSpecifier.test.ts b/packages/type-utils/tests/TypeOrValueSpecifier.test.ts index 59dd847d91fc..d9ac61364ca7 100644 --- a/packages/type-utils/tests/TypeOrValueSpecifier.test.ts +++ b/packages/type-utils/tests/TypeOrValueSpecifier.test.ts @@ -75,25 +75,13 @@ describe('TypeOrValueSpecifier', () => { it.each([ [{ from: 'package', name: 'MyType', package: 'jquery' }], - [ - { - from: 'package', - name: ['MyType', 'myValue'], - package: 'jquery', - }, - ], + [{ from: 'package', name: ['MyType', 'myValue'], package: 'jquery' }], ])('matches a package specifier: %s', runTestPositive); it.each([ [{ from: 'package', name: 42, package: 'jquery' }], [{ from: 'package', name: ['MyType', 42], package: 'jquery' }], - [ - { - from: 'package', - name: ['MyType', 'MyType'], - package: 'jquery', - }, - ], + [{ from: 'package', name: ['MyType', 'MyType'], package: 'jquery' }], [{ from: 'package', name: [], package: 'jquery' }], [{ from: 'package', name: 'MyType' }], [{ from: 'package', package: 'jquery' }], @@ -221,19 +209,11 @@ describe('TypeOrValueSpecifier', () => { ], [ 'interface Foo {prop: string}; type Test = Foo;', - { - from: 'file', - name: ['Foo', 'Bar'], - path: 'tests/fixtures/file.ts', - }, + { from: 'file', name: ['Foo', 'Bar'], path: 'tests/fixtures/file.ts' }, ], [ 'type Foo = {prop: string}; type Test = Foo;', - { - from: 'file', - name: ['Foo', 'Bar'], - path: 'tests/fixtures/file.ts', - }, + { from: 'file', name: ['Foo', 'Bar'], path: 'tests/fixtures/file.ts' }, ], ])('matches a matching file specifier: %s', runTestPositive); @@ -263,11 +243,13 @@ describe('TypeOrValueSpecifier', () => { it.each<[string, TypeOrValueSpecifier]>([ ['type Test = RegExp;', { from: 'lib', name: 'RegExp' }], ['type Test = RegExp;', { from: 'lib', name: ['RegExp', 'BigInt'] }], + ['type Test = URL;', { from: 'lib', name: 'URL' }], ])('matches a matching lib specifier: %s', runTestPositive); it.each<[string, TypeOrValueSpecifier]>([ ['type Test = RegExp;', { from: 'lib', name: 'BigInt' }], ['type Test = RegExp;', { from: 'lib', name: ['BigInt', 'Date'] }], + ['type Test = URL;', { from: 'lib', name: 'BigInt' }], ])("doesn't match a mismatched lib specifier: %s", runTestNegative); it.each<[string, TypeOrValueSpecifier]>([ @@ -365,7 +347,7 @@ describe('TypeOrValueSpecifier', () => { 'import type {Node as TsNode} from "typescript"; type Test = TsNode;', { from: 'package', name: 'TsNode', package: 'typescript' }, ], - ])("doesn't match a mismatched lib specifier: %s", runTestNegative); + ])("doesn't match a mismatched package specifier: %s", runTestNegative); it.each<[string, TypeOrValueSpecifier]>([ [ @@ -390,11 +372,7 @@ describe('TypeOrValueSpecifier', () => { ], [ 'interface Foo {prop: string}; type Test = Foo;', - { - from: 'package', - name: ['Foo', 'Bar'], - package: 'foo-package', - }, + { from: 'package', name: ['Foo', 'Bar'], package: 'foo-package' }, ], ['type Test = RegExp;', { from: 'file', name: 'RegExp' }], ['type Test = RegExp;', { from: 'file', name: ['RegExp', 'BigInt'] }], From 3072622f9994705b45d87d72a973ec8861c76ac9 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Mon, 10 Jun 2024 07:06:28 -0500 Subject: [PATCH 31/53] restore comment --- packages/type-utils/src/TypeOrValueSpecifier.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/packages/type-utils/src/TypeOrValueSpecifier.ts b/packages/type-utils/src/TypeOrValueSpecifier.ts index 2de39259a93a..383b5cc4840c 100644 --- a/packages/type-utils/src/TypeOrValueSpecifier.ts +++ b/packages/type-utils/src/TypeOrValueSpecifier.ts @@ -154,12 +154,17 @@ function typeDeclaredInLib( if (declarationFiles.length === 0) { return true; } - return declarationFiles.some( - declaration => - program.isSourceFileDefaultLibrary(declaration) || - (program.sourceFileToPackageName.get(declaration.path) == null && - /\/node_modules\/@types\/(bun|node)\//.test(declaration.path)), - ); + return declarationFiles.some(declaration => { + if (program.isSourceFileDefaultLibrary(declaration)) { + return true; + } + const { path } = declaration; + return ( + // Declared in types of runtime - Treat it as if it's from lib. + program.sourceFileToPackageName.get(path) == null && + /\/node_modules\/@types\/(bun|node)\//.test(path) + ); + }); } export function typeMatchesSpecifier( From f9e18ad1dcfe516db0ed6f118e298b67257451c6 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Mon, 10 Jun 2024 08:11:50 -0500 Subject: [PATCH 32/53] push more into var --- .../rules/restrict-template-expressions.ts | 3 +- .../type-utils/src/TypeOrValueSpecifier.ts | 115 +++++++++--------- packages/type-utils/src/isTypeReadonly.ts | 5 +- 3 files changed, 61 insertions(+), 62 deletions(-) diff --git a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts index 703ade9c2bb3..d9a0da6a53f6 100644 --- a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts +++ b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts @@ -99,8 +99,7 @@ export default createRule({ ), allow: { description: `Types to allow in template expressions.`, - type: 'array', - items: typeOrValueSpecifierSchema, + ...typeOrValueSpecifierSchema, }, }, }, diff --git a/packages/type-utils/src/TypeOrValueSpecifier.ts b/packages/type-utils/src/TypeOrValueSpecifier.ts index 383b5cc4840c..d9fe6fd8ad46 100644 --- a/packages/type-utils/src/TypeOrValueSpecifier.ts +++ b/packages/type-utils/src/TypeOrValueSpecifier.ts @@ -28,68 +28,71 @@ export type TypeOrValueSpecifier = | string; export const typeOrValueSpecifierSchema = { - oneOf: [ - { type: 'string' }, - { - type: 'object', - additionalProperties: false, - properties: { - from: { type: 'string', enum: ['file'] }, - name: { - oneOf: [ - { type: 'string' }, - { - type: 'array', - minItems: 1, - uniqueItems: true, - items: { type: 'string' }, - }, - ], + type: 'array', + items: { + oneOf: [ + { type: 'string' }, + { + type: 'object', + additionalProperties: false, + properties: { + from: { type: 'string', enum: ['file'] }, + name: { + oneOf: [ + { type: 'string' }, + { + type: 'array', + minItems: 1, + uniqueItems: true, + items: { type: 'string' }, + }, + ], + }, + path: { type: 'string' }, }, - path: { type: 'string' }, + required: ['from', 'name'], }, - required: ['from', 'name'], - }, - { - type: 'object', - additionalProperties: false, - properties: { - from: { type: 'string', enum: ['lib'] }, - name: { - oneOf: [ - { type: 'string' }, - { - type: 'array', - minItems: 1, - uniqueItems: true, - items: { type: 'string' }, - }, - ], + { + type: 'object', + additionalProperties: false, + properties: { + from: { type: 'string', enum: ['lib'] }, + name: { + oneOf: [ + { type: 'string' }, + { + type: 'array', + minItems: 1, + uniqueItems: true, + items: { type: 'string' }, + }, + ], + }, }, + required: ['from', 'name'], }, - required: ['from', 'name'], - }, - { - type: 'object', - additionalProperties: false, - properties: { - from: { type: 'string', enum: ['package'] }, - name: { - oneOf: [ - { type: 'string' }, - { - type: 'array', - minItems: 1, - uniqueItems: true, - items: { type: 'string' }, - }, - ], + { + type: 'object', + additionalProperties: false, + properties: { + from: { type: 'string', enum: ['package'] }, + name: { + oneOf: [ + { type: 'string' }, + { + type: 'array', + minItems: 1, + uniqueItems: true, + items: { type: 'string' }, + }, + ], + }, + package: { type: 'string' }, }, - package: { type: 'string' }, + required: ['from', 'name', 'package'], }, - required: ['from', 'name', 'package'], - }, - ], + ], + }, } as const satisfies JSONSchema4; function specifierNameMatches(type: ts.Type, name: string[] | string): boolean { diff --git a/packages/type-utils/src/isTypeReadonly.ts b/packages/type-utils/src/isTypeReadonly.ts index 75eafcb42866..12dd3ac56fc1 100644 --- a/packages/type-utils/src/isTypeReadonly.ts +++ b/packages/type-utils/src/isTypeReadonly.ts @@ -31,10 +31,7 @@ export const readonlynessOptionsSchema = { treatMethodsAsReadonly: { type: 'boolean', }, - allow: { - type: 'array', - items: typeOrValueSpecifierSchema, - }, + allow: typeOrValueSpecifierSchema, }, } satisfies JSONSchema4; From aa2faf83b4210baad509cbbb2c573bb910e8cf3f Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Tue, 11 Jun 2024 07:20:08 -0500 Subject: [PATCH 33/53] sync tests --- .../tests/TypeOrValueSpecifier.test.ts | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/packages/type-utils/tests/TypeOrValueSpecifier.test.ts b/packages/type-utils/tests/TypeOrValueSpecifier.test.ts index d9ac61364ca7..9bd965d40a7b 100644 --- a/packages/type-utils/tests/TypeOrValueSpecifier.test.ts +++ b/packages/type-utils/tests/TypeOrValueSpecifier.test.ts @@ -11,13 +11,11 @@ describe('TypeOrValueSpecifier', () => { const ajv = new Ajv(); const validate = ajv.compile(typeOrValueSpecifierSchema); - function runTestPositive(data: unknown): void { + const runTestPositive = (...data: unknown[]): void => expect(validate(data)).toBe(true); - } - function runTestNegative(data: unknown): void { + const runTestNegative = (...data: unknown[]): void => expect(validate(data)).toBe(false); - } it.each([['MyType'], ['myValue'], ['any'], ['void'], ['never']])( 'matches a simple string specifier %s', @@ -133,19 +131,15 @@ describe('TypeOrValueSpecifier', () => { ); } - function runTestPositive( + const runTestPositive = ( code: string, specifier: TypeOrValueSpecifier, - ): void { - runTests(code, specifier, true); - } + ): void => runTests(code, specifier, true); - function runTestNegative( + const runTestNegative = ( code: string, specifier: TypeOrValueSpecifier, - ): void { - runTests(code, specifier, false); - } + ): void => runTests(code, specifier, false); it.each<[string, TypeOrValueSpecifier]>([ ['interface Foo {prop: string}; type Test = Foo;', 'Foo'], From 98577d2229f91253396165570cc6816f55140030 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Tue, 11 Jun 2024 07:23:09 -0500 Subject: [PATCH 34/53] move to shared folder --- packages/eslint-plugin/docs/rules/no-floating-promises.mdx | 2 +- .../docs/rules/prefer-readonly-parameter-types.mdx | 2 +- .../eslint-plugin/docs/rules/restrict-template-expressions.mdx | 2 +- .../docs/{ => rules/shared}/TypeOrValueSpecifier.md | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename packages/eslint-plugin/docs/{ => rules/shared}/TypeOrValueSpecifier.md (100%) diff --git a/packages/eslint-plugin/docs/rules/no-floating-promises.mdx b/packages/eslint-plugin/docs/rules/no-floating-promises.mdx index f8307e9fed05..99efc54659aa 100644 --- a/packages/eslint-plugin/docs/rules/no-floating-promises.mdx +++ b/packages/eslint-plugin/docs/rules/no-floating-promises.mdx @@ -4,7 +4,7 @@ description: 'Require Promise-like statements to be handled appropriately.' import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -import TypeOrValueSpecifier from '../TypeOrValueSpecifier.md'; +import TypeOrValueSpecifier from './shared/TypeOrValueSpecifier.md'; > 🛑 This file is source code, not the primary documentation location! 🛑 > diff --git a/packages/eslint-plugin/docs/rules/prefer-readonly-parameter-types.mdx b/packages/eslint-plugin/docs/rules/prefer-readonly-parameter-types.mdx index 57c51e2d7e3a..10c1b31f434a 100644 --- a/packages/eslint-plugin/docs/rules/prefer-readonly-parameter-types.mdx +++ b/packages/eslint-plugin/docs/rules/prefer-readonly-parameter-types.mdx @@ -4,7 +4,7 @@ description: 'Require function parameters to be typed as `readonly` to prevent a import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -import TypeOrValueSpecifier from '../TypeOrValueSpecifier.md'; +import TypeOrValueSpecifier from './shared/TypeOrValueSpecifier.md'; > 🛑 This file is source code, not the primary documentation location! 🛑 > diff --git a/packages/eslint-plugin/docs/rules/restrict-template-expressions.mdx b/packages/eslint-plugin/docs/rules/restrict-template-expressions.mdx index c04164c5bd8c..8a3ad556c9ce 100644 --- a/packages/eslint-plugin/docs/rules/restrict-template-expressions.mdx +++ b/packages/eslint-plugin/docs/rules/restrict-template-expressions.mdx @@ -4,7 +4,7 @@ description: 'Enforce template literal expressions to be of `string` type.' import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -import TypeOrValueSpecifier from '../TypeOrValueSpecifier.md'; +import TypeOrValueSpecifier from './shared/TypeOrValueSpecifier.md'; > 🛑 This file is source code, not the primary documentation location! 🛑 > diff --git a/packages/eslint-plugin/docs/TypeOrValueSpecifier.md b/packages/eslint-plugin/docs/rules/shared/TypeOrValueSpecifier.md similarity index 100% rename from packages/eslint-plugin/docs/TypeOrValueSpecifier.md rename to packages/eslint-plugin/docs/rules/shared/TypeOrValueSpecifier.md From 899f1464df4535863490cb06122aedf0512a88b0 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Tue, 11 Jun 2024 07:57:44 -0500 Subject: [PATCH 35/53] add shared folder to test --- packages/eslint-plugin/tests/docs.test.ts | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/packages/eslint-plugin/tests/docs.test.ts b/packages/eslint-plugin/tests/docs.test.ts index daae32f5e17a..5788683e53f9 100644 --- a/packages/eslint-plugin/tests/docs.test.ts +++ b/packages/eslint-plugin/tests/docs.test.ts @@ -30,10 +30,7 @@ interface ParsedMarkdownFile { function parseMarkdownFile(filePath: string): ParsedMarkdownFile { const fullText = fs.readFileSync(filePath, 'utf-8'); - const tokens = marked.lexer(fullText, { - gfm: true, - silent: false, - }); + const tokens = marked.lexer(fullText, { gfm: true, silent: false }); return { fullText, tokens }; } @@ -128,6 +125,7 @@ describe('Validating rule docs', () => { const ignoredFiles = new Set([ 'README.md', 'TEMPLATE.md', + 'shared', // These rule docs were left behind on purpose for legacy reasons. See the // comments in the files for more information. 'camelcase.md', @@ -191,10 +189,7 @@ describe('Validating rule docs', () => { const { fullText, tokens } = parseMarkdownFile(filePath); test(`${ruleName}.mdx must start with frontmatter description`, () => { - expect(tokens[0]).toMatchObject({ - raw: '---\n', - type: 'hr', - }); + expect(tokens[0]).toMatchObject({ raw: '---\n', type: 'hr' }); expect(tokens[1]).toMatchObject({ text: description.includes("'") ? `description: "${description}."` @@ -341,9 +336,7 @@ describe('Validating rule docs', () => { try { parseForESLint(token.text, { - ecmaFeatures: { - jsx: /^tsx\b/i.test(lang), - }, + ecmaFeatures: { jsx: /^tsx\b/i.test(lang) }, ecmaVersion: 'latest', sourceType: 'module', range: true, @@ -438,9 +431,7 @@ describe('Validating rule docs', () => { tsconfigRootDir: rootPath, project: './tsconfig.json', }, - rules: { - [ruleName]: ruleConfig, - }, + rules: { [ruleName]: ruleConfig }, }, /^tsx\b/i.test(lang) ? 'react.tsx' : 'file.ts', ); From 9049fe775adfeb26369c94bd90bc69b3cb8d3506 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Thu, 13 Jun 2024 07:10:05 -0500 Subject: [PATCH 36/53] update snapshot --- .../restrict-template-expressions.shot | 113 ++++++++++++++++++ 1 file changed, 113 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 1a4d828e764e..9a2625cd73c3 100644 --- a/packages/eslint-plugin/tests/schema-snapshots/restrict-template-expressions.shot +++ b/packages/eslint-plugin/tests/schema-snapshots/restrict-template-expressions.shot @@ -8,6 +8,101 @@ exports[`Rule schemas should be convertible to TS types for documentation purpos { "additionalProperties": false, "properties": { + "allow": { + "description": "Types to allow in template expressions.", + "items": { + "oneOf": [ + { + "type": "string" + }, + { + "additionalProperties": false, + "properties": { + "from": { + "enum": ["file"], + "type": "string" + }, + "name": { + "oneOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array", + "uniqueItems": true + } + ] + }, + "path": { + "type": "string" + } + }, + "required": ["from", "name"], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "from": { + "enum": ["lib"], + "type": "string" + }, + "name": { + "oneOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array", + "uniqueItems": true + } + ] + } + }, + "required": ["from", "name"], + "type": "object" + }, + { + "additionalProperties": false, + "properties": { + "from": { + "enum": ["package"], + "type": "string" + }, + "name": { + "oneOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "minItems": 1, + "type": "array", + "uniqueItems": true + } + ] + }, + "package": { + "type": "string" + } + }, + "required": ["from", "name", "package"], + "type": "object" + } + ] + }, + "type": "array" + }, "allowAny": { "description": "Whether to allow \`any\` typed values in template expressions.", "type": "boolean" @@ -46,6 +141,24 @@ exports[`Rule schemas should be convertible to TS types for documentation purpos type Options = [ { + /** Types to allow in template expressions. */ + allow?: ( + | { + from: 'file'; + name: [string, ...string[]] | string; + path?: string; + } + | { + from: 'lib'; + name: [string, ...string[]] | string; + } + | { + from: 'package'; + name: [string, ...string[]] | string; + package: string; + } + | string + )[]; /** Whether to allow \`any\` typed values in template expressions. */ allowAny?: boolean; /** Whether to allow \`array\` typed values in template expressions. */ From e7e88bc158a7d29433e2795f2cfc6b39567cc440 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Thu, 13 Jun 2024 07:47:43 -0500 Subject: [PATCH 37/53] change check for consistency --- packages/type-utils/src/TypeOrValueSpecifier.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/type-utils/src/TypeOrValueSpecifier.ts b/packages/type-utils/src/TypeOrValueSpecifier.ts index d9fe6fd8ad46..526c64e19997 100644 --- a/packages/type-utils/src/TypeOrValueSpecifier.ts +++ b/packages/type-utils/src/TypeOrValueSpecifier.ts @@ -164,7 +164,7 @@ function typeDeclaredInLib( const { path } = declaration; return ( // Declared in types of runtime - Treat it as if it's from lib. - program.sourceFileToPackageName.get(path) == null && + program.sourceFileToPackageName.get(path) === undefined && /\/node_modules\/@types\/(bun|node)\//.test(path) ); }); From b3d052a10aab31caf602a5df107da3f45ab83aa3 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Thu, 13 Jun 2024 08:05:26 -0500 Subject: [PATCH 38/53] clarify documentation for string form --- .../eslint-plugin/docs/rules/shared/TypeOrValueSpecifier.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/shared/TypeOrValueSpecifier.md b/packages/eslint-plugin/docs/rules/shared/TypeOrValueSpecifier.md index e89299295055..43fd078795c0 100644 --- a/packages/eslint-plugin/docs/rules/shared/TypeOrValueSpecifier.md +++ b/packages/eslint-plugin/docs/rules/shared/TypeOrValueSpecifier.md @@ -1,9 +1,8 @@ This option takes an array of type specifiers to {props.verb}. Each item in the array must have one of the following forms: +- A type matched independently of its origin, as a simple string (`"Foo"`) - A type (or multiple types) defined in a file (`{ from: "file", name: "Foo", path: "src/foo-file.ts" }` with `path` being an optional path relative to the project root directory) - A type (or multiple types) from the default library (`{ from: "lib", name: "Foo" }`) - A type (or multiple types) from a package (`{ from: "package", name: "Foo", package: "foo-lib" }`, this also works for types defined in a typings package). -In any of the three forms, `name` can also be an array of strings representing multiple types, rather than just one string. - -Additionally, a type may be defined just as a simple string, which then matches the type independently of its origin. +In any of the three object forms, `name` can also be an array of strings representing multiple types, rather than just one string. From e056af8a7f1cf6ded27c2063b14405866d7e8c2b Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Fri, 5 Jul 2024 06:52:04 -0500 Subject: [PATCH 39/53] clean up --- .../rules/restrict-template-expressions.ts | 6 ++- packages/eslint-plugin/tests/docs.test.ts | 18 +++++-- .../restrict-template-expressions.test.ts | 12 ++++- .../type-utils/src/TypeOrValueSpecifier.ts | 51 ++++++++++++++----- .../tests/TypeOrValueSpecifier.test.ts | 34 +++++++++++-- 5 files changed, 97 insertions(+), 24 deletions(-) diff --git a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts index d9a0da6a53f6..97fb69bad3d7 100644 --- a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts +++ b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts @@ -49,7 +49,11 @@ const optionTesters = ( ], ['Never', isTypeNeverType], ] as const satisfies [string, OptionTester][] -).map(([type, tester]) => ({ type, option: `allow${type}` as const, tester })); +).map(([type, tester]) => ({ + type, + option: `allow${type}` as const, + tester, +})); type Options = [ { [Type in (typeof optionTesters)[number]['option']]?: boolean } & { allow?: TypeOrValueSpecifier[]; diff --git a/packages/eslint-plugin/tests/docs.test.ts b/packages/eslint-plugin/tests/docs.test.ts index 5788683e53f9..1214dfd4b80b 100644 --- a/packages/eslint-plugin/tests/docs.test.ts +++ b/packages/eslint-plugin/tests/docs.test.ts @@ -30,7 +30,10 @@ interface ParsedMarkdownFile { function parseMarkdownFile(filePath: string): ParsedMarkdownFile { const fullText = fs.readFileSync(filePath, 'utf-8'); - const tokens = marked.lexer(fullText, { gfm: true, silent: false }); + const tokens = marked.lexer(fullText, { + gfm: true, + silent: false, + }); return { fullText, tokens }; } @@ -189,7 +192,10 @@ describe('Validating rule docs', () => { const { fullText, tokens } = parseMarkdownFile(filePath); test(`${ruleName}.mdx must start with frontmatter description`, () => { - expect(tokens[0]).toMatchObject({ raw: '---\n', type: 'hr' }); + expect(tokens[0]).toMatchObject({ + raw: '---\n', + type: 'hr', + }); expect(tokens[1]).toMatchObject({ text: description.includes("'") ? `description: "${description}."` @@ -336,7 +342,9 @@ describe('Validating rule docs', () => { try { parseForESLint(token.text, { - ecmaFeatures: { jsx: /^tsx\b/i.test(lang) }, + ecmaFeatures: { + jsx: /^tsx\b/i.test(lang), + }, ecmaVersion: 'latest', sourceType: 'module', range: true, @@ -431,7 +439,9 @@ describe('Validating rule docs', () => { tsconfigRootDir: rootPath, project: './tsconfig.json', }, - rules: { [ruleName]: ruleConfig }, + rules: { + [ruleName]: ruleConfig, + }, }, /^tsx\b/i.test(lang) ? 'react.tsx' : 'file.ts', ); 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 26839ba6be51..80964e3b4d64 100644 --- a/packages/eslint-plugin/tests/rules/restrict-template-expressions.test.ts +++ b/packages/eslint-plugin/tests/rules/restrict-template-expressions.test.ts @@ -7,7 +7,10 @@ const rootPath = getFixturesRootDir(); const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser', - parserOptions: { tsconfigRootDir: rootPath, project: './tsconfig.json' }, + parserOptions: { + tsconfigRootDir: rootPath, + project: './tsconfig.json', + }, }); ruleTester.run('restrict-template-expressions', rule, { @@ -473,7 +476,12 @@ ruleTester.run('restrict-template-expressions', rule, { } `, errors: [ - { messageId: 'invalidType', data: { type: 'T' }, line: 3, column: 27 }, + { + messageId: 'invalidType', + data: { type: 'T' }, + line: 3, + column: 27, + }, ], }, { diff --git a/packages/type-utils/src/TypeOrValueSpecifier.ts b/packages/type-utils/src/TypeOrValueSpecifier.ts index 526c64e19997..5cb13c5f2c2a 100644 --- a/packages/type-utils/src/TypeOrValueSpecifier.ts +++ b/packages/type-utils/src/TypeOrValueSpecifier.ts @@ -31,24 +31,35 @@ export const typeOrValueSpecifierSchema = { type: 'array', items: { oneOf: [ - { type: 'string' }, + { + type: 'string', + }, { type: 'object', additionalProperties: false, properties: { - from: { type: 'string', enum: ['file'] }, + from: { + type: 'string', + enum: ['file'], + }, name: { oneOf: [ - { type: 'string' }, + { + type: 'string', + }, { type: 'array', minItems: 1, uniqueItems: true, - items: { type: 'string' }, + items: { + type: 'string', + }, }, ], }, - path: { type: 'string' }, + path: { + type: 'string', + }, }, required: ['from', 'name'], }, @@ -56,15 +67,22 @@ export const typeOrValueSpecifierSchema = { type: 'object', additionalProperties: false, properties: { - from: { type: 'string', enum: ['lib'] }, + from: { + type: 'string', + enum: ['lib'], + }, name: { oneOf: [ - { type: 'string' }, + { + type: 'string', + }, { type: 'array', minItems: 1, uniqueItems: true, - items: { type: 'string' }, + items: { + type: 'string', + }, }, ], }, @@ -75,19 +93,28 @@ export const typeOrValueSpecifierSchema = { type: 'object', additionalProperties: false, properties: { - from: { type: 'string', enum: ['package'] }, + from: { + type: 'string', + enum: ['package'], + }, name: { oneOf: [ - { type: 'string' }, + { + type: 'string', + }, { type: 'array', minItems: 1, uniqueItems: true, - items: { type: 'string' }, + items: { + type: 'string', + }, }, ], }, - package: { type: 'string' }, + package: { + type: 'string', + }, }, required: ['from', 'name', 'package'], }, diff --git a/packages/type-utils/tests/TypeOrValueSpecifier.test.ts b/packages/type-utils/tests/TypeOrValueSpecifier.test.ts index 9bd965d40a7b..8d8465fda162 100644 --- a/packages/type-utils/tests/TypeOrValueSpecifier.test.ts +++ b/packages/type-utils/tests/TypeOrValueSpecifier.test.ts @@ -73,13 +73,25 @@ describe('TypeOrValueSpecifier', () => { it.each([ [{ from: 'package', name: 'MyType', package: 'jquery' }], - [{ from: 'package', name: ['MyType', 'myValue'], package: 'jquery' }], + [ + { + from: 'package', + name: ['MyType', 'myValue'], + package: 'jquery', + }, + ], ])('matches a package specifier: %s', runTestPositive); it.each([ [{ from: 'package', name: 42, package: 'jquery' }], [{ from: 'package', name: ['MyType', 42], package: 'jquery' }], - [{ from: 'package', name: ['MyType', 'MyType'], package: 'jquery' }], + [ + { + from: 'package', + name: ['MyType', 'MyType'], + package: 'jquery', + }, + ], [{ from: 'package', name: [], package: 'jquery' }], [{ from: 'package', name: 'MyType' }], [{ from: 'package', package: 'jquery' }], @@ -203,11 +215,19 @@ describe('TypeOrValueSpecifier', () => { ], [ 'interface Foo {prop: string}; type Test = Foo;', - { from: 'file', name: ['Foo', 'Bar'], path: 'tests/fixtures/file.ts' }, + { + from: 'file', + name: ['Foo', 'Bar'], + path: 'tests/fixtures/file.ts', + }, ], [ 'type Foo = {prop: string}; type Test = Foo;', - { from: 'file', name: ['Foo', 'Bar'], path: 'tests/fixtures/file.ts' }, + { + from: 'file', + name: ['Foo', 'Bar'], + path: 'tests/fixtures/file.ts', + }, ], ])('matches a matching file specifier: %s', runTestPositive); @@ -366,7 +386,11 @@ describe('TypeOrValueSpecifier', () => { ], [ 'interface Foo {prop: string}; type Test = Foo;', - { from: 'package', name: ['Foo', 'Bar'], package: 'foo-package' }, + { + from: 'package', + name: ['Foo', 'Bar'], + package: 'foo-package', + }, ], ['type Test = RegExp;', { from: 'file', name: 'RegExp' }], ['type Test = RegExp;', { from: 'file', name: ['RegExp', 'BigInt'] }], From 3a9c50b545f85e8930d4df6d76b1d05b0ac7b74a Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Mon, 8 Jul 2024 06:43:16 -0500 Subject: [PATCH 40/53] make docs secret, tweak tests --- .../eslint-plugin/docs/rules/shared/TypeOrValueSpecifier.md | 1 - .../tests/rules/restrict-template-expressions.test.ts | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/shared/TypeOrValueSpecifier.md b/packages/eslint-plugin/docs/rules/shared/TypeOrValueSpecifier.md index 43fd078795c0..34671dd567b0 100644 --- a/packages/eslint-plugin/docs/rules/shared/TypeOrValueSpecifier.md +++ b/packages/eslint-plugin/docs/rules/shared/TypeOrValueSpecifier.md @@ -1,6 +1,5 @@ This option takes an array of type specifiers to {props.verb}. Each item in the array must have one of the following forms: -- A type matched independently of its origin, as a simple string (`"Foo"`) - A type (or multiple types) defined in a file (`{ from: "file", name: "Foo", path: "src/foo-file.ts" }` with `path` being an optional path relative to the project root directory) - A type (or multiple types) from the default library (`{ from: "lib", name: "Foo" }`) - A type (or multiple types) from a package (`{ from: "package", name: "Foo", package: "foo-lib" }`, this also works for types defined in a typings package). 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 80964e3b4d64..524a44006f18 100644 --- a/packages/eslint-plugin/tests/rules/restrict-template-expressions.test.ts +++ b/packages/eslint-plugin/tests/rules/restrict-template-expressions.test.ts @@ -317,7 +317,7 @@ ruleTester.run('restrict-template-expressions', rule, { }, // allow { - options: [{ allow: [`Promise`] }], + options: [{ allow: [{ from: 'lib', name: 'Promise' }] }], code: 'const msg = `arg = ${Promise.resolve()}`;', }, 'const msg = `arg = ${new URL()}`;', From 8b54864e1ddd8a015909fa450c2e7717564fa591 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Thu, 25 Jul 2024 16:39:16 -0500 Subject: [PATCH 41/53] revert --- packages/type-utils/src/TypeOrValueSpecifier.ts | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/packages/type-utils/src/TypeOrValueSpecifier.ts b/packages/type-utils/src/TypeOrValueSpecifier.ts index 5cb13c5f2c2a..8f68b3629a02 100644 --- a/packages/type-utils/src/TypeOrValueSpecifier.ts +++ b/packages/type-utils/src/TypeOrValueSpecifier.ts @@ -184,17 +184,9 @@ function typeDeclaredInLib( if (declarationFiles.length === 0) { return true; } - return declarationFiles.some(declaration => { - if (program.isSourceFileDefaultLibrary(declaration)) { - return true; - } - const { path } = declaration; - return ( - // Declared in types of runtime - Treat it as if it's from lib. - program.sourceFileToPackageName.get(path) === undefined && - /\/node_modules\/@types\/(bun|node)\//.test(path) - ); - }); + return declarationFiles.some(declaration => + program.isSourceFileDefaultLibrary(declaration), + ); } export function typeMatchesSpecifier( From 42d4afa0d6fc21a3319873197c1c398d05501718 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Fri, 26 Jul 2024 09:38:56 -0500 Subject: [PATCH 42/53] cleanup --- .../type-utils/src/TypeOrValueSpecifier.ts | 2 +- packages/type-utils/src/isTypeReadonly.ts | 4 +-- .../tests/TypeOrValueSpecifier.test.ts | 28 +++++++++++-------- 3 files changed, 19 insertions(+), 15 deletions(-) diff --git a/packages/type-utils/src/TypeOrValueSpecifier.ts b/packages/type-utils/src/TypeOrValueSpecifier.ts index 8f68b3629a02..16e4a1339dd6 100644 --- a/packages/type-utils/src/TypeOrValueSpecifier.ts +++ b/packages/type-utils/src/TypeOrValueSpecifier.ts @@ -27,7 +27,7 @@ export type TypeOrValueSpecifier = | PackageSpecifier | string; -export const typeOrValueSpecifierSchema = { +export const typeOrValueSpecifiersSchema = { type: 'array', items: { oneOf: [ diff --git a/packages/type-utils/src/isTypeReadonly.ts b/packages/type-utils/src/isTypeReadonly.ts index 12dd3ac56fc1..3b8cfb5d1917 100644 --- a/packages/type-utils/src/isTypeReadonly.ts +++ b/packages/type-utils/src/isTypeReadonly.ts @@ -7,7 +7,7 @@ import { getTypeOfPropertyOfType } from './propertyTypes'; import type { TypeOrValueSpecifier } from './TypeOrValueSpecifier'; import { typeMatchesSomeSpecifier, - typeOrValueSpecifierSchema, + typeOrValueSpecifiersSchema, } from './TypeOrValueSpecifier'; const enum Readonlyness { @@ -31,7 +31,7 @@ export const readonlynessOptionsSchema = { treatMethodsAsReadonly: { type: 'boolean', }, - allow: typeOrValueSpecifierSchema, + allow: typeOrValueSpecifiersSchema, }, } satisfies JSONSchema4; diff --git a/packages/type-utils/tests/TypeOrValueSpecifier.test.ts b/packages/type-utils/tests/TypeOrValueSpecifier.test.ts index 8d8465fda162..34bb6e35fd5c 100644 --- a/packages/type-utils/tests/TypeOrValueSpecifier.test.ts +++ b/packages/type-utils/tests/TypeOrValueSpecifier.test.ts @@ -4,18 +4,20 @@ import Ajv from 'ajv'; import path from 'path'; import type { TypeOrValueSpecifier } from '../src'; -import { typeMatchesSpecifier, typeOrValueSpecifierSchema } from '../src'; +import { typeMatchesSpecifier, typeOrValueSpecifiersSchema } from '../src'; describe('TypeOrValueSpecifier', () => { describe('Schema', () => { const ajv = new Ajv(); - const validate = ajv.compile(typeOrValueSpecifierSchema); + const validate = ajv.compile(typeOrValueSpecifiersSchema); - const runTestPositive = (...data: unknown[]): void => - expect(validate(data)).toBe(true); + function runTestPositive(typeOrValueSpecifier: unknown): void { + expect(validate([typeOrValueSpecifier])).toBe(true); + } - const runTestNegative = (...data: unknown[]): void => - expect(validate(data)).toBe(false); + function runTestNegative(typeOrValueSpecifier: unknown): void { + expect(validate([typeOrValueSpecifier])).toBe(false); + } it.each([['MyType'], ['myValue'], ['any'], ['void'], ['never']])( 'matches a simple string specifier %s', @@ -143,15 +145,19 @@ describe('TypeOrValueSpecifier', () => { ); } - const runTestPositive = ( + function runTestPositive( code: string, specifier: TypeOrValueSpecifier, - ): void => runTests(code, specifier, true); + ): void { + runTests(code, specifier, true); + } - const runTestNegative = ( + function runTestNegative( code: string, specifier: TypeOrValueSpecifier, - ): void => runTests(code, specifier, false); + ): void { + runTests(code, specifier, false); + } it.each<[string, TypeOrValueSpecifier]>([ ['interface Foo {prop: string}; type Test = Foo;', 'Foo'], @@ -257,13 +263,11 @@ describe('TypeOrValueSpecifier', () => { it.each<[string, TypeOrValueSpecifier]>([ ['type Test = RegExp;', { from: 'lib', name: 'RegExp' }], ['type Test = RegExp;', { from: 'lib', name: ['RegExp', 'BigInt'] }], - ['type Test = URL;', { from: 'lib', name: 'URL' }], ])('matches a matching lib specifier: %s', runTestPositive); it.each<[string, TypeOrValueSpecifier]>([ ['type Test = RegExp;', { from: 'lib', name: 'BigInt' }], ['type Test = RegExp;', { from: 'lib', name: ['BigInt', 'Date'] }], - ['type Test = URL;', { from: 'lib', name: 'BigInt' }], ])("doesn't match a mismatched lib specifier: %s", runTestNegative); it.each<[string, TypeOrValueSpecifier]>([ From 33904bf4f839254700cbb8b0035515b4cb32a150 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Fri, 26 Jul 2024 09:45:19 -0500 Subject: [PATCH 43/53] finish rename --- .../eslint-plugin/src/rules/restrict-template-expressions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts index e7dbd28e3376..54b94aa0b193 100644 --- a/packages/eslint-plugin/src/rules/restrict-template-expressions.ts +++ b/packages/eslint-plugin/src/rules/restrict-template-expressions.ts @@ -1,6 +1,6 @@ import { typeMatchesSomeSpecifier, - typeOrValueSpecifierSchema, + typeOrValueSpecifiersSchema, } from '@typescript-eslint/type-utils'; import type { TSESTree } from '@typescript-eslint/utils'; import { AST_NODE_TYPES } from '@typescript-eslint/utils'; @@ -103,7 +103,7 @@ export default createRule({ ), allow: { description: `Types to allow in template expressions.`, - ...typeOrValueSpecifierSchema, + ...typeOrValueSpecifiersSchema, }, }, }, From f45a9795bc89194c9af7b783225de4fc2e874d73 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Fri, 26 Jul 2024 09:51:56 -0500 Subject: [PATCH 44/53] simplify tests --- .../tests/rules/restrict-template-expressions.test.ts | 2 -- 1 file changed, 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 c1cfad79120f..b6dfb56ef6e3 100644 --- a/packages/eslint-plugin/tests/rules/restrict-template-expressions.test.ts +++ b/packages/eslint-plugin/tests/rules/restrict-template-expressions.test.ts @@ -348,8 +348,6 @@ ruleTester.run('restrict-template-expressions', rule, { options: [{ allow: [{ from: 'lib', name: 'Promise' }] }], code: 'const msg = `arg = ${Promise.resolve()}`;', }, - 'const msg = `arg = ${new URL()}`;', - 'const msg = `arg = ${new URLSearchParams()}`;', 'const msg = `arg = ${new Error()}`;', 'const msg = `arg = ${false}`;', 'const msg = `arg = ${null}`;', From 34c24a582ad481093c9b5f790516cd06be80fc83 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Fri, 26 Jul 2024 09:54:31 -0500 Subject: [PATCH 45/53] change URL to Error --- .../tests/rules/restrict-template-expressions.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 b6dfb56ef6e3..07ff0fc2571c 100644 --- a/packages/eslint-plugin/tests/rules/restrict-template-expressions.test.ts +++ b/packages/eslint-plugin/tests/rules/restrict-template-expressions.test.ts @@ -432,7 +432,7 @@ ruleTester.run('restrict-template-expressions', rule, { errors: [{ messageId: 'invalidType' }], }, { - code: 'const msg = `arg = ${new URL()}`;', + code: 'const msg = `arg = ${new Error()}`;', options: [{ allow: [] }], errors: [{ messageId: 'invalidType' }], }, From 53a069a5b4692ac6d42ac0c4dc36034184cbdca9 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Fri, 26 Jul 2024 10:12:31 -0500 Subject: [PATCH 46/53] add tsconfig withdom --- packages/eslint-plugin/tests/docs.test.ts | 2 +- packages/eslint-plugin/tests/fixtures/tsconfig-withdom.json | 6 ++++++ .../tests/rules/restrict-template-expressions.test.ts | 6 ++++-- 3 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 packages/eslint-plugin/tests/fixtures/tsconfig-withdom.json diff --git a/packages/eslint-plugin/tests/docs.test.ts b/packages/eslint-plugin/tests/docs.test.ts index 1214dfd4b80b..3430c9012c19 100644 --- a/packages/eslint-plugin/tests/docs.test.ts +++ b/packages/eslint-plugin/tests/docs.test.ts @@ -437,7 +437,7 @@ describe('Validating rule docs', () => { parser: '@typescript-eslint/parser', parserOptions: { tsconfigRootDir: rootPath, - project: './tsconfig.json', + project: './tsconfig-withdom.json', }, rules: { [ruleName]: ruleConfig, diff --git a/packages/eslint-plugin/tests/fixtures/tsconfig-withdom.json b/packages/eslint-plugin/tests/fixtures/tsconfig-withdom.json new file mode 100644 index 000000000000..92629ea04cc6 --- /dev/null +++ b/packages/eslint-plugin/tests/fixtures/tsconfig-withdom.json @@ -0,0 +1,6 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "lib": ["dom"] + } +} 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 07ff0fc2571c..e3f33afd6baf 100644 --- a/packages/eslint-plugin/tests/rules/restrict-template-expressions.test.ts +++ b/packages/eslint-plugin/tests/rules/restrict-template-expressions.test.ts @@ -9,7 +9,7 @@ const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser', parserOptions: { tsconfigRootDir: rootPath, - project: './tsconfig.json', + project: './tsconfig-withdom.json', }, }); @@ -348,6 +348,8 @@ ruleTester.run('restrict-template-expressions', rule, { options: [{ allow: [{ from: 'lib', name: 'Promise' }] }], code: 'const msg = `arg = ${Promise.resolve()}`;', }, + 'const msg = `arg = ${new URL()}`;', + 'const msg = `arg = ${new URLSearchParams()}`;', 'const msg = `arg = ${new Error()}`;', 'const msg = `arg = ${false}`;', 'const msg = `arg = ${null}`;', @@ -432,7 +434,7 @@ ruleTester.run('restrict-template-expressions', rule, { errors: [{ messageId: 'invalidType' }], }, { - code: 'const msg = `arg = ${new Error()}`;', + code: 'const msg = `arg = ${new URL()}`;', options: [{ allow: [] }], errors: [{ messageId: 'invalidType' }], }, From 089809cc8ff09507ae6dc0381102bdb1c4042462 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Sat, 27 Jul 2024 13:49:12 -0500 Subject: [PATCH 47/53] revert docs tsconfig change --- packages/eslint-plugin/tests/docs.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/tests/docs.test.ts b/packages/eslint-plugin/tests/docs.test.ts index 3430c9012c19..1214dfd4b80b 100644 --- a/packages/eslint-plugin/tests/docs.test.ts +++ b/packages/eslint-plugin/tests/docs.test.ts @@ -437,7 +437,7 @@ describe('Validating rule docs', () => { parser: '@typescript-eslint/parser', parserOptions: { tsconfigRootDir: rootPath, - project: './tsconfig-withdom.json', + project: './tsconfig.json', }, rules: { [ruleName]: ruleConfig, From 346f9f4cf0107dd702ef3d943294a8d6a0e63aa8 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Wed, 14 Aug 2024 16:44:18 -0500 Subject: [PATCH 48/53] fix TS error --- packages/eslint-plugin/src/rules/no-floating-promises.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-floating-promises.ts b/packages/eslint-plugin/src/rules/no-floating-promises.ts index 3db0265ead06..503050bbfbf7 100644 --- a/packages/eslint-plugin/src/rules/no-floating-promises.ts +++ b/packages/eslint-plugin/src/rules/no-floating-promises.ts @@ -231,8 +231,10 @@ export default createRule({ const type = services.getTypeAtLocation(node.callee); - return allowForKnownSafeCalls.some(allowedType => - typeMatchesSpecifier(type, allowedType, services.program), + return typeMatchesSomeSpecifier( + type, + allowForKnownSafeCalls, + services.program, ); } From f9e528a3c50a870254be0249e47ca8273dc033ca Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Thu, 15 Aug 2024 07:34:24 -0500 Subject: [PATCH 49/53] simplify docs --- .../docs/rules/restrict-template-expressions.mdx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/restrict-template-expressions.mdx b/packages/eslint-plugin/docs/rules/restrict-template-expressions.mdx index 8a3ad556c9ce..1464e9ebdfdd 100644 --- a/packages/eslint-plugin/docs/rules/restrict-template-expressions.mdx +++ b/packages/eslint-plugin/docs/rules/restrict-template-expressions.mdx @@ -135,9 +135,7 @@ Examples of additional **correct** code for this rule with the default option `{ ```ts showPlaygroundButton const error = new Error(); -const url = new URL('https://typescript-eslint.io'); -const urlSearchParams = new URLSearchParams(); -const msg1 = `args = ${error}, ${url}, ${urlSearchParams}`; +const msg1 = `arg = ${error}`; ``` ## When Not To Use It From 4a3d82441b1d9f6ae098f6005349288827001134 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Fri, 16 Aug 2024 19:15:40 -0500 Subject: [PATCH 50/53] update snapshot --- .../restrict-template-expressions.shot | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/restrict-template-expressions.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/restrict-template-expressions.shot index 29d9783c8c99..18fcf8c5ba31 100644 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/restrict-template-expressions.shot +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/restrict-template-expressions.shot @@ -96,8 +96,6 @@ exports[`Validating rule docs restrict-template-expressions.mdx code examples ES " const error = new Error(); -const url = new URL('https://typescript-eslint.io'); -const urlSearchParams = new URLSearchParams(); -const msg1 = \`args = \${error}, \${url}, \${urlSearchParams}\`; +const msg1 = \`arg = \${error}\`; " `; From 30c1121d7fb5cc5f3ef922ebfc5696f3ba32bc50 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Mon, 2 Sep 2024 09:48:13 -0500 Subject: [PATCH 51/53] update docs --- .../docs/rules/restrict-template-expressions.mdx | 10 ++++++---- .../docs/rules/shared/TypeOrValueSpecifier.md | 7 ------- 2 files changed, 6 insertions(+), 11 deletions(-) delete mode 100644 packages/eslint-plugin/docs/rules/shared/TypeOrValueSpecifier.md diff --git a/packages/eslint-plugin/docs/rules/restrict-template-expressions.mdx b/packages/eslint-plugin/docs/rules/restrict-template-expressions.mdx index 27e375037017..1c81a5a113e9 100644 --- a/packages/eslint-plugin/docs/rules/restrict-template-expressions.mdx +++ b/packages/eslint-plugin/docs/rules/restrict-template-expressions.mdx @@ -86,7 +86,7 @@ const msg2 = `arg = ${arg || 'not truthy'}`; ### `allowAny` -Whether to `any` typed values in template expressions. +Whether to allow `any` typed values in template expressions. Examples of additional **correct** code for this rule with `{ allowAny: true }`: @@ -125,7 +125,7 @@ const msg1 = `arg = ${arg}`; ### `allowNever` -Whether to `never` typed values in template expressions. +Whether to allow `never` typed values in template expressions. Examples of additional **correct** code for this rule with `{ allowNever: true }`: @@ -136,7 +136,7 @@ const msg1 = typeof arg === 'string' ? arg : `arg = ${arg}`; ### `allowArray` -Whether to `Array` typed values in template expressions. +Whether to allow `Array` typed values in template expressions. Examples of additional **correct** code for this rule with `{ allowArray: true }`: @@ -147,7 +147,9 @@ const msg1 = `arg = ${arg}`; ### `allow` - +Whether to allow additional types in template expressions. + +This option takes the shared [`TypeOrValueSpecifier` format](/packages/type-utils/type-or-value-specifier). Examples of additional **correct** code for this rule with the default option `{ allow: [{ from: 'lib', name: 'Error' }, { from: 'lib', name: 'URL' }, { from: 'lib', name: 'URLSearchParams' }] }`: diff --git a/packages/eslint-plugin/docs/rules/shared/TypeOrValueSpecifier.md b/packages/eslint-plugin/docs/rules/shared/TypeOrValueSpecifier.md deleted file mode 100644 index 34671dd567b0..000000000000 --- a/packages/eslint-plugin/docs/rules/shared/TypeOrValueSpecifier.md +++ /dev/null @@ -1,7 +0,0 @@ -This option takes an array of type specifiers to {props.verb}. Each item in the array must have one of the following forms: - -- A type (or multiple types) defined in a file (`{ from: "file", name: "Foo", path: "src/foo-file.ts" }` with `path` being an optional path relative to the project root directory) -- A type (or multiple types) from the default library (`{ from: "lib", name: "Foo" }`) -- A type (or multiple types) from a package (`{ from: "package", name: "Foo", package: "foo-lib" }`, this also works for types defined in a typings package). - -In any of the three object forms, `name` can also be an array of strings representing multiple types, rather than just one string. From cdfbb46710cbe1377fab4a6fb6ad91d387154668 Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Mon, 2 Sep 2024 09:52:48 -0500 Subject: [PATCH 52/53] remove unused import --- .../eslint-plugin/docs/rules/restrict-template-expressions.mdx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/eslint-plugin/docs/rules/restrict-template-expressions.mdx b/packages/eslint-plugin/docs/rules/restrict-template-expressions.mdx index 1c81a5a113e9..60ea75a7c65e 100644 --- a/packages/eslint-plugin/docs/rules/restrict-template-expressions.mdx +++ b/packages/eslint-plugin/docs/rules/restrict-template-expressions.mdx @@ -4,7 +4,6 @@ description: 'Enforce template literal expressions to be of `string` type.' import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; -import TypeOrValueSpecifier from './shared/TypeOrValueSpecifier.md'; > 🛑 This file is source code, not the primary documentation location! 🛑 > From d8509beb49dc3bd93f33f5d0b405f830ef00fe4b Mon Sep 17 00:00:00 2001 From: Abraham Guo Date: Mon, 2 Sep 2024 16:20:17 -0500 Subject: [PATCH 53/53] simplify unit tests to only use non-DOM types --- packages/eslint-plugin/tests/fixtures/tsconfig-withdom.json | 6 ------ .../tests/rules/restrict-template-expressions.test.ts | 6 ++---- 2 files changed, 2 insertions(+), 10 deletions(-) delete mode 100644 packages/eslint-plugin/tests/fixtures/tsconfig-withdom.json diff --git a/packages/eslint-plugin/tests/fixtures/tsconfig-withdom.json b/packages/eslint-plugin/tests/fixtures/tsconfig-withdom.json deleted file mode 100644 index 92629ea04cc6..000000000000 --- a/packages/eslint-plugin/tests/fixtures/tsconfig-withdom.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "lib": ["dom"] - } -} 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 cd32984d5060..e7d6872eaf85 100644 --- a/packages/eslint-plugin/tests/rules/restrict-template-expressions.test.ts +++ b/packages/eslint-plugin/tests/rules/restrict-template-expressions.test.ts @@ -9,7 +9,7 @@ const ruleTester = new RuleTester({ languageOptions: { parserOptions: { tsconfigRootDir: rootPath, - project: './tsconfig-withdom.json', + project: './tsconfig.json', }, }, }); @@ -349,8 +349,6 @@ ruleTester.run('restrict-template-expressions', rule, { options: [{ allow: [{ from: 'lib', name: 'Promise' }] }], code: 'const msg = `arg = ${Promise.resolve()}`;', }, - 'const msg = `arg = ${new URL()}`;', - 'const msg = `arg = ${new URLSearchParams()}`;', 'const msg = `arg = ${new Error()}`;', 'const msg = `arg = ${false}`;', 'const msg = `arg = ${null}`;', @@ -435,7 +433,7 @@ ruleTester.run('restrict-template-expressions', rule, { errors: [{ messageId: 'invalidType' }], }, { - code: 'const msg = `arg = ${new URL()}`;', + code: 'const msg = `arg = ${new Error()}`;', options: [{ allow: [] }], errors: [{ messageId: 'invalidType' }], },