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..d1927bf69b5b 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; +String(x); +``` + ## 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/src/rules/no-base-to-string.ts b/packages/eslint-plugin/src/rules/no-base-to-string.ts index 6617c8d1dea2..26eadce99839 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[]; + checkUnknown?: boolean; }, ]; export type MessageIds = 'baseArrayJoin' | 'baseToString'; @@ -46,6 +47,10 @@ export default createRule({ type: 'object', additionalProperties: false, properties: { + checkUnknown: { + type: 'boolean', + description: 'Whether to also check values of type `unknown`', + }, ignoredTypeNames: { type: 'array', description: @@ -60,6 +65,7 @@ export default createRule({ }, defaultOptions: [ { + checkUnknown: false, ignoredTypeNames: ['Error', 'RegExp', 'URL', 'URLSearchParams'], }, ], @@ -76,6 +82,7 @@ export default createRule({ type ?? services.getTypeAtLocation(node), new Set(), ); + if (certainty === Usefulness.Always) { return; } @@ -213,7 +220,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() @@ -251,8 +258,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/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..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 @@ -56,3 +56,9 @@ let value = /regex/; value.toString(); let text = `${value}`; String(/regex/); + +Options: { "checkUnknown": true } + +declare const x: unknown; +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 f0fe39979c4d..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 @@ -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,202 @@ 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); +} + `, + ` +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: [ + { + 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: [ 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..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 @@ -5,6 +5,10 @@ { "additionalProperties": false, "properties": { + "checkUnknown": { + "description": "Whether to also check values of type `unknown`", + "type": "boolean" + }, "ignoredTypeNames": { "description": "Stringified regular expressions of type names to ignore.", "items": { @@ -22,6 +26,8 @@ type Options = [ { + /** 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