From 2f45e9992a8f12b6233716e77a6159f9cea2c879 Mon Sep 17 00:00:00 2001 From: duduluu Date: Mon, 27 Apr 2020 18:08:13 +0800 Subject: [PATCH] fix(eslint-plugin): fix no-base-to-string boolean literal check (#1850) --- .../docs/rules/no-base-to-string.md | 28 ++++++++ .../src/rules/no-base-to-string.ts | 26 ++++++- .../tests/rules/no-base-to-string.test.ts | 70 ++++++++++++------- 3 files changed, 97 insertions(+), 27 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/no-base-to-string.md b/packages/eslint-plugin/docs/rules/no-base-to-string.md index 569ffe0e6d9..5c74cb4d84c 100644 --- a/packages/eslint-plugin/docs/rules/no-base-to-string.md +++ b/packages/eslint-plugin/docs/rules/no-base-to-string.md @@ -52,6 +52,34 @@ const literalWithToString = { `Value: ${literalWithToString}`; ``` +## Options + +```ts +type Options = { + ignoredTypeNames?: string[]; +}; + +const defaultOptions: Options = { + ignoredTypeNames: ['RegExp'], +}; +``` + +### `ignoreTypeNames` + +A string array of type names to ignore, this is useful for types missing `toString()` (but actually has `toString()`). +There are some types missing `toString()` in old version TypeScript, like `RegExp`, `URL`, `URLSearchParams` etc. + +The following patterns are considered correct with the default options `{ ignoreTypeNames: ["RegExp"] }`: + +```ts +`${/regex/}`; +'' + /regex/; +/regex/.toString(); +let value = /regex/; +value.toString(); +let text = `${value}`; +``` + ## When Not To Use It If you don't mind `"[object Object]"` in your strings, 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 a1121f37abd..2e06bdb870d 100644 --- a/packages/eslint-plugin/src/rules/no-base-to-string.ts +++ b/packages/eslint-plugin/src/rules/no-base-to-string.ts @@ -16,6 +16,7 @@ type Options = [ { /** @deprecated This option is now ignored and treated as always true, it will be removed in 3.0 */ ignoreTaggedTemplateExpressions?: boolean; + ignoredTypeNames?: string[]; }, ]; type MessageIds = 'baseToString'; @@ -42,16 +43,28 @@ export default util.createRule({ type: 'boolean', default: true, }, + ignoredTypeNames: { + type: 'array', + items: { + type: 'string', + }, + }, }, additionalProperties: false, }, ], type: 'suggestion', }, - defaultOptions: [{ ignoreTaggedTemplateExpressions: true }], - create(context) { + defaultOptions: [ + { + ignoreTaggedTemplateExpressions: true, + ignoredTypeNames: ['RegExp'], + }, + ], + create(context, [option]) { const parserServices = util.getParserServices(context); const typeChecker = parserServices.program.getTypeChecker(); + const ignoredTypeNames = option.ignoredTypeNames ?? []; function checkExpression(node: TSESTree.Expression, type?: ts.Type): void { if (node.type === AST_NODE_TYPES.Literal) { @@ -84,6 +97,15 @@ export default util.createRule({ return Usefulness.Always; } + // Patch for old version TypeScript, the Boolean type definition missing toString() + if (type.flags & ts.TypeFlags.BooleanLiteral) { + return Usefulness.Always; + } + + if (ignoredTypeNames.includes(util.getTypeName(typeChecker, type))) { + return Usefulness.Always; + } + if ( toString.declarations.every( ({ parent }) => 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 82bd4e0302e..842ead488c1 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 @@ -11,39 +11,59 @@ const ruleTester = new RuleTester({ }, }); +const literalListBasic: string[] = [ + "''", + "'text'", + 'true', + 'false', + '1', + '1n', + '[]', + '/regex/', +]; + +const literalListNeedParen: string[] = [ + '{}.constructor()', + '() => {}', + 'function() {}', +]; + +const literalList = [...literalListBasic, ...literalListNeedParen]; + +const literalListWrapped = [ + ...literalListBasic, + ...literalListNeedParen.map(i => `(${i})`), +]; + ruleTester.run('no-base-to-string', rule, { valid: [ - "`${''}`;", - '`${true}`;', - '`${[]}`;', - '`${function() {}}`;', - "'' + '';", - "'' + true;", - "'' + [];", - 'true + true;', - "true + '';", - 'true + [];', - '[] + [];', - '[] + true;', - "[] + '';", - '({}.constructor());', - "'text'.toString();", - 'false.toString();', - ` -let value = 1; -value.toString(); - `, - ` -let value = 1n; -value.toString(); - `, + // template + ...literalList.map(i => `\`\${${i}}\`;`), + + // operator + += + ...literalListWrapped + .map(l => literalListWrapped.map(r => `${l} + ${r};`)) + .reduce((pre, cur) => [...pre, ...cur]), + + // toString() + ...literalListWrapped.map(i => `${i === '1' ? `(${i})` : i}.toString();`), + + // variable toString() and template + ...literalList.map( + i => ` + let value = ${i}; + value.toString(); + let text = \`\${value}\`; + `, + ), + ` function someFunction() {} someFunction.toString(); +let text = \`\${someFunction}\`; `, 'unknownObject.toString();', 'unknownObject.someOtherMethod();', - '(() => {}).toString();', ` class CustomToString { toString() {