From efb5f30ff880e9d001f77b8f74bd2455343e33f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=89=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=83=E1=85=AE?= Date: Mon, 5 May 2025 00:31:32 +0900 Subject: [PATCH 1/8] feat: add type and option --- packages/eslint-plugin/src/rules/no-base-to-string.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/eslint-plugin/src/rules/no-base-to-string.ts b/packages/eslint-plugin/src/rules/no-base-to-string.ts index 6617c8d1dea2..b0b9df6fa1bc 100644 --- a/packages/eslint-plugin/src/rules/no-base-to-string.ts +++ b/packages/eslint-plugin/src/rules/no-base-to-string.ts @@ -21,6 +21,7 @@ enum Usefulness { export type Options = [ { ignoredTypeNames?: string[]; + restrictUnknown?: boolean; }, ]; export type MessageIds = 'baseArrayJoin' | 'baseToString'; @@ -54,6 +55,11 @@ export default createRule({ type: 'string', }, }, + restrictUnknown: { + type: 'boolean', + default: false, + description: 'Restrict applying toString to unknown type', + }, }, }, ], @@ -61,6 +67,7 @@ export default createRule({ defaultOptions: [ { ignoredTypeNames: ['Error', 'RegExp', 'URL', 'URLSearchParams'], + restrictUnknown: false, }, ], create(context, [option]) { From d601fd3c2a81ae807eefa9a56f4d7134ea3c9af5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=89=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=83=E1=85=AE?= Date: Tue, 6 May 2025 15:15:21 +0900 Subject: [PATCH 2/8] feat: apply checkUnknown option and test case --- .../src/rules/no-base-to-string.ts | 25 ++- .../tests/rules/no-base-to-string.test.ts | 179 +++++++++++++++++- 2 files changed, 186 insertions(+), 18 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-base-to-string.ts b/packages/eslint-plugin/src/rules/no-base-to-string.ts index b0b9df6fa1bc..9b9640e40796 100644 --- a/packages/eslint-plugin/src/rules/no-base-to-string.ts +++ b/packages/eslint-plugin/src/rules/no-base-to-string.ts @@ -21,7 +21,7 @@ enum Usefulness { export type Options = [ { ignoredTypeNames?: string[]; - restrictUnknown?: boolean; + checkUnknown?: boolean; }, ]; export type MessageIds = 'baseArrayJoin' | 'baseToString'; @@ -47,6 +47,12 @@ export default createRule({ type: 'object', additionalProperties: false, properties: { + checkUnknown: { + type: 'boolean', + default: false, + description: + 'Checks the case where toString is applied to unknown type', + }, ignoredTypeNames: { type: 'array', description: @@ -55,19 +61,14 @@ export default createRule({ type: 'string', }, }, - restrictUnknown: { - type: 'boolean', - default: false, - description: 'Restrict applying toString to unknown type', - }, }, }, ], }, defaultOptions: [ { + checkUnknown: false, ignoredTypeNames: ['Error', 'RegExp', 'URL', 'URLSearchParams'], - restrictUnknown: false, }, ], create(context, [option]) { @@ -83,6 +84,7 @@ export default createRule({ type ?? services.getTypeAtLocation(node), new Set(), ); + if (certainty === Usefulness.Always) { return; } @@ -220,7 +222,7 @@ export default createRule({ return collectToStringCertainty(constraint, visited); } // unconstrained generic means `unknown` - return Usefulness.Always; + return option.checkUnknown ? Usefulness.Sometimes : Usefulness.Always; } // the Boolean type definition missing toString() @@ -258,8 +260,13 @@ export default createRule({ const toString = checker.getPropertyOfType(type, 'toString') ?? checker.getPropertyOfType(type, 'toLocaleString'); + if (!toString) { - // e.g. any/unknown + // unknown + if (option.checkUnknown && type.flags === ts.TypeFlags.Unknown) { + return Usefulness.Sometimes; + } + // e.g. any return Usefulness.Always; } diff --git a/packages/eslint-plugin/tests/rules/no-base-to-string.test.ts b/packages/eslint-plugin/tests/rules/no-base-to-string.test.ts index f0fe39979c4d..b4685eb73479 100644 --- a/packages/eslint-plugin/tests/rules/no-base-to-string.test.ts +++ b/packages/eslint-plugin/tests/rules/no-base-to-string.test.ts @@ -475,15 +475,6 @@ declare const bb: ExtendedGuildChannel; bb.toString(); `, ` -function foo(x: T) { - String(x); -} - `, - ` -declare const u: unknown; -String(u); - `, - ` type Value = string | Value[]; declare const v: Value; @@ -511,8 +502,178 @@ String(v); declare const v: ('foo' | 'bar')[][]; String(v); `, + ` +declare const x: unknown; +\`\${x})\`; + `, + ` +declare const x: unknown; +x.toString(); + `, + ` +declare const x: unknown; +x.toLocaleString(); + `, + ` +declare const x: unknown; +'' + x; + `, + ` +declare const x: unknown; +String(x); + `, + ` + declare const x: unknown; + '' += x; + `, + ` + function foo(x: T) { + String(x); + } + `, ], invalid: [ + { + code: ` +declare const x: unknown; +\`\${x})\`; + `, + errors: [ + { + data: { + certainty: 'may', + name: 'x', + }, + messageId: 'baseToString', + }, + ], + options: [ + { + checkUnknown: true, + }, + ], + }, + { + code: ` +declare const x: unknown; +x.toString(); + `, + errors: [ + { + data: { + certainty: 'may', + name: 'x', + }, + messageId: 'baseToString', + }, + ], + options: [ + { + checkUnknown: true, + }, + ], + }, + { + code: ` +declare const x: unknown; +x.toLocaleString(); + `, + errors: [ + { + data: { + certainty: 'may', + name: 'x', + }, + messageId: 'baseToString', + }, + ], + options: [ + { + checkUnknown: true, + }, + ], + }, + { + code: ` +declare const x: unknown; +'' + x; + `, + errors: [ + { + data: { + certainty: 'may', + name: 'x', + }, + messageId: 'baseToString', + }, + ], + options: [ + { + checkUnknown: true, + }, + ], + }, + { + code: ` +declare const x: unknown; +String(x); + `, + errors: [ + { + data: { + certainty: 'may', + name: 'x', + }, + messageId: 'baseToString', + }, + ], + options: [ + { + checkUnknown: true, + }, + ], + }, + { + code: ` +declare const x: unknown; +'' += x; + `, + errors: [ + { + data: { + certainty: 'may', + name: 'x', + }, + messageId: 'baseToString', + }, + ], + options: [ + { + checkUnknown: true, + }, + ], + }, + { + code: ` + function foo(x: T) { + String(x); + } + `, + errors: [ + { + data: { + certainty: 'may', + name: 'x', + }, + messageId: 'baseToString', + }, + ], + options: [ + { + checkUnknown: true, + }, + ], + }, { code: '`${{}})`;', errors: [ From 3bdc647ac6a6737826ebd6045a16efea4c01c886 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=89=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=83=E1=85=AE?= Date: Sun, 18 May 2025 22:37:34 +0900 Subject: [PATCH 3/8] fix: ci error --- .../docs/rules/no-base-to-string.mdx | 11 +++++++ .../no-base-to-string.shot | 6 ++++ .../tests/rules/no-base-to-string.test.ts | 30 +++++++++---------- .../schema-snapshots/no-base-to-string.shot | 7 +++++ 4 files changed, 39 insertions(+), 15 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/no-base-to-string.mdx b/packages/eslint-plugin/docs/rules/no-base-to-string.mdx index 8097ca8b7f41..24932dad8fd3 100644 --- a/packages/eslint-plugin/docs/rules/no-base-to-string.mdx +++ b/packages/eslint-plugin/docs/rules/no-base-to-string.mdx @@ -99,6 +99,17 @@ let text = `${value}`; String(/regex/); ``` +### `checkUnknown` + +{/* insert option description */} + +The following patterns are considered incorrect with the options `{ checkUnknown: true }`: + +```ts option='{ "checkUnknown": true }' showPlaygroundButton +declare const x: unknown; +x.toString(); +``` + ## When Not To Use It If you don't mind a risk of `"[object Object]"` or incorrect type coercions in your values, then you will not need this rule. diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-base-to-string.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-base-to-string.shot index ac8bfaecc474..b3ef4c8da549 100644 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-base-to-string.shot +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-base-to-string.shot @@ -56,3 +56,9 @@ let value = /regex/; value.toString(); let text = `${value}`; String(/regex/); + +Options: { "checkUnknown": true } + +declare const x: unknown; +x.toString(); +~ 'x' may use Object's default stringification format ('[object Object]') when stringified. diff --git a/packages/eslint-plugin/tests/rules/no-base-to-string.test.ts b/packages/eslint-plugin/tests/rules/no-base-to-string.test.ts index b4685eb73479..bfc91aa73e8c 100644 --- a/packages/eslint-plugin/tests/rules/no-base-to-string.test.ts +++ b/packages/eslint-plugin/tests/rules/no-base-to-string.test.ts @@ -505,32 +505,32 @@ String(v); ` declare const x: unknown; \`\${x})\`; - `, + `, ` declare const x: unknown; x.toString(); - `, + `, ` declare const x: unknown; x.toLocaleString(); - `, + `, ` declare const x: unknown; '' + x; - `, + `, ` declare const x: unknown; String(x); - `, + `, ` - declare const x: unknown; - '' += x; - `, +declare const x: unknown; +'' += x; + `, ` - function foo(x: T) { - String(x); - } - `, +function foo(x: T) { + String(x); +} + `, ], invalid: [ { @@ -655,9 +655,9 @@ declare const x: unknown; }, { code: ` - function foo(x: T) { - String(x); - } +function foo(x: T) { + String(x); +} `, errors: [ { diff --git a/packages/eslint-plugin/tests/schema-snapshots/no-base-to-string.shot b/packages/eslint-plugin/tests/schema-snapshots/no-base-to-string.shot index fe2577cd6329..4c348f18a6f0 100644 --- a/packages/eslint-plugin/tests/schema-snapshots/no-base-to-string.shot +++ b/packages/eslint-plugin/tests/schema-snapshots/no-base-to-string.shot @@ -5,6 +5,11 @@ { "additionalProperties": false, "properties": { + "checkUnknown": { + "default": false, + "description": "Checks the case where toString is applied to unknown type", + "type": "boolean" + }, "ignoredTypeNames": { "description": "Stringified regular expressions of type names to ignore.", "items": { @@ -22,6 +27,8 @@ type Options = [ { + /** Checks the case where toString is applied to unknown type */ + checkUnknown?: boolean; /** Stringified regular expressions of type names to ignore. */ ignoredTypeNames?: string[]; }, From 224b3709189dc1952e27cf924836468a03c00332 Mon Sep 17 00:00:00 2001 From: puki4416 Date: Sun, 1 Jun 2025 19:52:39 +0900 Subject: [PATCH 4/8] fix: change example code and add test cases --- .../docs/rules/no-base-to-string.mdx | 2 +- .../tests/rules/no-base-to-string.test.ts | 120 ++++++++++++++++++ 2 files changed, 121 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/docs/rules/no-base-to-string.mdx b/packages/eslint-plugin/docs/rules/no-base-to-string.mdx index 24932dad8fd3..d1927bf69b5b 100644 --- a/packages/eslint-plugin/docs/rules/no-base-to-string.mdx +++ b/packages/eslint-plugin/docs/rules/no-base-to-string.mdx @@ -107,7 +107,7 @@ The following patterns are considered incorrect with the options `{ checkUnknown ```ts option='{ "checkUnknown": true }' showPlaygroundButton declare const x: unknown; -x.toString(); +String(x); ``` ## When Not To Use It diff --git a/packages/eslint-plugin/tests/rules/no-base-to-string.test.ts b/packages/eslint-plugin/tests/rules/no-base-to-string.test.ts index bfc91aa73e8c..7c0cebfa0181 100644 --- a/packages/eslint-plugin/tests/rules/no-base-to-string.test.ts +++ b/packages/eslint-plugin/tests/rules/no-base-to-string.test.ts @@ -674,6 +674,126 @@ function foo(x: T) { }, ], }, + { + code: ` +declare const x: any; +\`\${x})\`; + `, + errors: [ + { + data: { + certainty: 'always', + name: 'x', + }, + messageId: 'baseToString', + }, + ], + options: [ + { + checkUnknown: true, + }, + ], + }, + { + code: ` +declare const x: any; +x.toString(); + `, + errors: [ + { + data: { + certainty: 'always', + name: 'x', + }, + messageId: 'baseToString', + }, + ], + options: [ + { + checkUnknown: true, + }, + ], + }, + { + code: ` +declare const x: any; +x.toLocaleString(); + `, + errors: [ + { + data: { + certainty: 'always', + name: 'x', + }, + messageId: 'baseToString', + }, + ], + options: [ + { + checkUnknown: true, + }, + ], + }, + { + code: ` +declare const x: any; +'' + x; + `, + errors: [ + { + data: { + certainty: 'always', + name: 'x', + }, + messageId: 'baseToString', + }, + ], + options: [ + { + checkUnknown: true, + }, + ], + }, + { + code: ` +declare const x: any; +String(x); + `, + errors: [ + { + data: { + certainty: 'always', + name: 'x', + }, + messageId: 'baseToString', + }, + ], + options: [ + { + checkUnknown: true, + }, + ], + }, + { + code: ` +declare const x: any; +'' += x; + `, + errors: [ + { + data: { + certainty: 'always', + name: 'x', + }, + messageId: 'baseToString', + }, + ], + options: [ + { + checkUnknown: true, + }, + ], + }, { code: '`${{}})`;', errors: [ From 3ca5d85fca860d7b09fd66252ac001b1088d8fa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=89=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=83=E1=85=AE?= Date: Tue, 3 Jun 2025 22:43:16 +0900 Subject: [PATCH 5/8] fix: no-base-to-string Rule test fix and update schema --- .../no-base-to-string.shot | 4 +- .../tests/rules/no-base-to-string.test.ts | 144 +++--------------- packages/eslint-plugin/vitest.config.mts | 5 +- 3 files changed, 30 insertions(+), 123 deletions(-) diff --git a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-base-to-string.shot b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-base-to-string.shot index b3ef4c8da549..ae8614ebe762 100644 --- a/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-base-to-string.shot +++ b/packages/eslint-plugin/tests/docs-eslint-output-snapshots/no-base-to-string.shot @@ -60,5 +60,5 @@ String(/regex/); Options: { "checkUnknown": true } declare const x: unknown; -x.toString(); -~ 'x' may use Object's default stringification format ('[object Object]') when stringified. +String(x); + ~ 'x' may use Object's default stringification format ('[object Object]') when stringified. diff --git a/packages/eslint-plugin/tests/rules/no-base-to-string.test.ts b/packages/eslint-plugin/tests/rules/no-base-to-string.test.ts index 7c0cebfa0181..5fb889131b67 100644 --- a/packages/eslint-plugin/tests/rules/no-base-to-string.test.ts +++ b/packages/eslint-plugin/tests/rules/no-base-to-string.test.ts @@ -531,6 +531,30 @@ function foo(x: T) { String(x); } `, + ` +declare const x: any; +\`\${x})\`; + `, + ` +declare const x: any; +x.toString(); + `, + ` +declare const x: any; +x.toLocaleString(); + `, + ` +declare const x: any; +'' + x; + `, + ` +declare const x: any; +String(x); + `, + ` +declare const x: any; +'' += x; + `, ], invalid: [ { @@ -674,126 +698,6 @@ function foo(x: T) { }, ], }, - { - code: ` -declare const x: any; -\`\${x})\`; - `, - errors: [ - { - data: { - certainty: 'always', - name: 'x', - }, - messageId: 'baseToString', - }, - ], - options: [ - { - checkUnknown: true, - }, - ], - }, - { - code: ` -declare const x: any; -x.toString(); - `, - errors: [ - { - data: { - certainty: 'always', - name: 'x', - }, - messageId: 'baseToString', - }, - ], - options: [ - { - checkUnknown: true, - }, - ], - }, - { - code: ` -declare const x: any; -x.toLocaleString(); - `, - errors: [ - { - data: { - certainty: 'always', - name: 'x', - }, - messageId: 'baseToString', - }, - ], - options: [ - { - checkUnknown: true, - }, - ], - }, - { - code: ` -declare const x: any; -'' + x; - `, - errors: [ - { - data: { - certainty: 'always', - name: 'x', - }, - messageId: 'baseToString', - }, - ], - options: [ - { - checkUnknown: true, - }, - ], - }, - { - code: ` -declare const x: any; -String(x); - `, - errors: [ - { - data: { - certainty: 'always', - name: 'x', - }, - messageId: 'baseToString', - }, - ], - options: [ - { - checkUnknown: true, - }, - ], - }, - { - code: ` -declare const x: any; -'' += x; - `, - errors: [ - { - data: { - certainty: 'always', - name: 'x', - }, - messageId: 'baseToString', - }, - ], - options: [ - { - checkUnknown: true, - }, - ], - }, { code: '`${{}})`;', errors: [ diff --git a/packages/eslint-plugin/vitest.config.mts b/packages/eslint-plugin/vitest.config.mts index 44eba50897ef..4d70d7f5230f 100644 --- a/packages/eslint-plugin/vitest.config.mts +++ b/packages/eslint-plugin/vitest.config.mts @@ -11,7 +11,10 @@ export default mergeConfig( root: import.meta.dirname, test: { - dir: path.join(import.meta.dirname, 'tests'), + include: [ + path.join(import.meta.dirname, 'tests/rules/no-base-to-string.test.ts'), + ], + // dir: path.join(import.meta.dirname, 'tests'), name: packageJson.name.replace('@typescript-eslint/', ''), root: import.meta.dirname, }, From 80fd6d86bb4bfd3e832910a9f263ab33237f1e07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E1=84=80=E1=85=B5=E1=86=B7=E1=84=89=E1=85=A1=E1=86=BC?= =?UTF-8?q?=E1=84=83=E1=85=AE?= Date: Fri, 6 Jun 2025 11:28:35 +0900 Subject: [PATCH 6/8] fix: test config revert --- packages/eslint-plugin/vitest.config.mts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/eslint-plugin/vitest.config.mts b/packages/eslint-plugin/vitest.config.mts index 4d70d7f5230f..44eba50897ef 100644 --- a/packages/eslint-plugin/vitest.config.mts +++ b/packages/eslint-plugin/vitest.config.mts @@ -11,10 +11,7 @@ export default mergeConfig( root: import.meta.dirname, test: { - include: [ - path.join(import.meta.dirname, 'tests/rules/no-base-to-string.test.ts'), - ], - // dir: path.join(import.meta.dirname, 'tests'), + dir: path.join(import.meta.dirname, 'tests'), name: packageJson.name.replace('@typescript-eslint/', ''), root: import.meta.dirname, }, From 4fcfd247350945129e02269fca976c584d8b7f17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Josh=20Goldberg=20=E2=9C=A8?= Date: Mon, 23 Jun 2025 12:28:18 -0400 Subject: [PATCH 7/8] Apply suggestions from code review --- packages/eslint-plugin/src/rules/no-base-to-string.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-base-to-string.ts b/packages/eslint-plugin/src/rules/no-base-to-string.ts index 9b9640e40796..18959dd65882 100644 --- a/packages/eslint-plugin/src/rules/no-base-to-string.ts +++ b/packages/eslint-plugin/src/rules/no-base-to-string.ts @@ -49,9 +49,8 @@ export default createRule({ properties: { checkUnknown: { type: 'boolean', - default: false, description: - 'Checks the case where toString is applied to unknown type', + 'Whether to also check values of type `unknown`', }, ignoredTypeNames: { type: 'array', From a0981145752d762f73c216f626b59463ce597e83 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 23 Jun 2025 12:56:00 -0400 Subject: [PATCH 8/8] Formatted and updated snapshots --- packages/eslint-plugin/src/rules/no-base-to-string.ts | 3 +-- .../tests/schema-snapshots/no-base-to-string.shot | 5 ++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-base-to-string.ts b/packages/eslint-plugin/src/rules/no-base-to-string.ts index 18959dd65882..26eadce99839 100644 --- a/packages/eslint-plugin/src/rules/no-base-to-string.ts +++ b/packages/eslint-plugin/src/rules/no-base-to-string.ts @@ -49,8 +49,7 @@ export default createRule({ properties: { checkUnknown: { type: 'boolean', - description: - 'Whether to also check values of type `unknown`', + description: 'Whether to also check values of type `unknown`', }, ignoredTypeNames: { type: 'array', diff --git a/packages/eslint-plugin/tests/schema-snapshots/no-base-to-string.shot b/packages/eslint-plugin/tests/schema-snapshots/no-base-to-string.shot index 4c348f18a6f0..207398f5c070 100644 --- a/packages/eslint-plugin/tests/schema-snapshots/no-base-to-string.shot +++ b/packages/eslint-plugin/tests/schema-snapshots/no-base-to-string.shot @@ -6,8 +6,7 @@ "additionalProperties": false, "properties": { "checkUnknown": { - "default": false, - "description": "Checks the case where toString is applied to unknown type", + "description": "Whether to also check values of type `unknown`", "type": "boolean" }, "ignoredTypeNames": { @@ -27,7 +26,7 @@ type Options = [ { - /** Checks the case where toString is applied to unknown type */ + /** Whether to also check values of type `unknown` */ checkUnknown?: boolean; /** Stringified regular expressions of type names to ignore. */ ignoredTypeNames?: string[]; pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy