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 f2bf27b8e072..1b2d21320bcf 100644 --- a/packages/eslint-plugin/docs/rules/no-base-to-string.mdx +++ b/packages/eslint-plugin/docs/rules/no-base-to-string.mdx @@ -1,5 +1,5 @@ --- -description: 'Require `.toString()` to only be called on objects which provide useful information when stringified.' +description: 'Require `.toString()` and `.toLocaleString()` to only be called on objects which provide useful information when stringified.' --- import Tabs from '@theme/Tabs'; @@ -10,10 +10,10 @@ import TabItem from '@theme/TabItem'; > See **https://typescript-eslint.io/rules/no-base-to-string** for documentation. JavaScript will call `toString()` on an object when it is converted to a string, such as when `+` adding to a string or in `${}` template literals. -The default Object `.toString()` uses the format `"[object Object]"`, which is often not what was intended. -This rule reports on stringified values that aren't primitives and don't define a more useful `.toString()` method. +The default Object `.toString()` and `toLocaleString()` use the format `"[object Object]"`, which is often not what was intended. +This rule reports on stringified values that aren't primitives and don't define a more useful `.toString()` or `toLocaleString()` method. -> Note that `Function` provides its own `.toString()` that returns the function's code. +> Note that `Function` provides its own `.toString()` and `toLocaleString()` that return the function's code. > Functions are not flagged by this rule. ## Examples @@ -29,20 +29,22 @@ class MyClass {} const value = new MyClass(); value + ''; -// Interpolation and manual .toString() calls too: +// Interpolation and manual .toString() and `toLocaleString()` calls too: `Value: ${value}`; ({}).toString(); +({}).toLocaleString(); ``` ```ts -// These types all have useful .toString()s +// These types all have useful .toString() and `toLocaleString()` methods 'Text' + true; `Value: ${123}`; `Arrays too: ${[1, 2, 3]}`; (() => {}).toString(); +(() => {}).toLocaleString(); // Defining a custom .toString class is considered acceptable class CustomToString { @@ -68,8 +70,8 @@ const literalWithToString = { {/* insert option description */} -This is useful for types whose declarations missing `toString()`, but are known to actually have `toString()`. -There are some types missing `toString()` in TypeScript, like `RegExp`, `URL`, `URLSearchParams` etc. +This is useful for types missing `toString()` or `toLocaleString()` (but actually has `toString()` or `toLocaleString()`). +There are some types missing `toString()` or `toLocaleString()` in old versions of TypeScript, like `RegExp`, `URL`, `URLSearchParams` etc. The following patterns are considered correct with the default options `{ ignoredTypeNames: ["RegExp"] }`: @@ -94,4 +96,5 @@ If you don't mind a risk of `"[object Object]"` or incorrect type coercions in y ## Further Reading - [`Object.prototype.toString()` MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString) +- [`Object.prototype.toLocaleString()` MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toLocaleString) - [Microsoft/TypeScript Add missing toString declarations for base types that have them](https://github.com/microsoft/TypeScript/issues/38347) 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 df8fa6bb13cf..86f5a3cc1777 100644 --- a/packages/eslint-plugin/src/rules/no-base-to-string.ts +++ b/packages/eslint-plugin/src/rules/no-base-to-string.ts @@ -24,7 +24,7 @@ export default createRule({ type: 'suggestion', docs: { description: - 'Require `.toString()` to only be called on objects which provide useful information when stringified', + 'Require `.toString()` and `.toLocaleString()` to only be called on objects which provide useful information when stringified', recommended: 'recommended', requiresTypeChecking: true, }, @@ -82,7 +82,9 @@ export default createRule({ } function collectToStringCertainty(type: ts.Type): Usefulness { - const toString = checker.getPropertyOfType(type, 'toString'); + const toString = + checker.getPropertyOfType(type, 'toString') ?? + checker.getPropertyOfType(type, 'toLocaleString'); const declarations = toString?.getDeclarations(); if (!toString || !declarations || declarations.length === 0) { return Usefulness.Always; @@ -167,7 +169,7 @@ export default createRule({ checkExpression(node.left, leftType); } }, - 'CallExpression > MemberExpression.callee > Identifier[name = "toString"].property'( + 'CallExpression > MemberExpression.callee > Identifier[name = /^(toLocaleString|toString)$/].property'( node: TSESTree.Expression, ): void { const memberExpr = node.parent as TSESTree.MemberExpression; 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 f5bc9c118ff1..afc8aa555457 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 @@ -12,22 +12,25 @@ const value = new MyClass(); value + ''; ~~~~~ 'value' will use Object's default stringification format ('[object Object]') when stringified. -// Interpolation and manual .toString() calls too: +// Interpolation and manual .toString() and \`toLocaleString()\` calls too: \`Value: \${value}\`; ~~~~~ 'value' will use Object's default stringification format ('[object Object]') when stringified. ({}).toString(); ~~ '{}' will use Object's default stringification format ('[object Object]') when stringified. +({}).toLocaleString(); + ~~ '{}' will use Object's default stringification format ('[object Object]') when stringified. " `; exports[`Validating rule docs no-base-to-string.mdx code examples ESLint output 2`] = ` "Correct -// These types all have useful .toString()s +// These types all have useful .toString() and \`toLocaleString()\` methods 'Text' + true; \`Value: \${123}\`; \`Arrays too: \${[1, 2, 3]}\`; (() => {}).toString(); +(() => {}).toLocaleString(); // Defining a custom .toString class is considered acceptable class CustomToString { 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 8cd205a13a9f..b57c9d3a78df 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 @@ -63,9 +63,15 @@ ruleTester.run('no-base-to-string', rule, { ` function someFunction() {} someFunction.toString(); +let text = \`\${someFunction}\`; + `, + ` +function someFunction() {} +someFunction.toLocaleString(); let text = \`\${someFunction}\`; `, 'unknownObject.toString();', + 'unknownObject.toLocaleString();', 'unknownObject.someOtherMethod();', ` class CustomToString { @@ -79,7 +85,7 @@ class CustomToString { const literalWithToString = { toString: () => 'Hello, world!', }; -'' + literalToString; +'' + literalWithToString; `, ` const printer = (inVar: string | number | boolean) => { @@ -87,6 +93,14 @@ const printer = (inVar: string | number | boolean) => { }; printer(''); printer(1); +printer(true); + `, + ` +const printer = (inVar: string | number | boolean) => { + inVar.toLocaleString(); +}; +printer(''); +printer(1); printer(true); `, 'let _ = {} * {};', @@ -144,6 +158,18 @@ tag\`\${{}}\`; }, ], }, + { + code: '({}).toLocaleString();', + errors: [ + { + data: { + certainty: 'will', + name: '{}', + }, + messageId: 'baseToString', + }, + ], + }, { code: "'' + {};", errors: [ @@ -183,6 +209,21 @@ tag\`\${{}}\`; }, ], }, + { + code: ` + let someObjectOrString = Math.random() ? { a: true } : 'text'; + someObjectOrString.toLocaleString(); + `, + errors: [ + { + data: { + certainty: 'may', + name: 'someObjectOrString', + }, + messageId: 'baseToString', + }, + ], + }, { code: ` let someObjectOrString = Math.random() ? { a: true } : 'text'; @@ -213,6 +254,21 @@ tag\`\${{}}\`; }, ], }, + { + code: ` + let someObjectOrObject = Math.random() ? { a: true, b: true } : { a: true }; + someObjectOrObject.toLocaleString(); + `, + errors: [ + { + data: { + certainty: 'will', + name: 'someObjectOrObject', + }, + messageId: 'baseToString', + }, + ], + }, { code: ` let someObjectOrObject = Math.random() ? { a: true, b: true } : { a: true };