diff --git a/packages/eslint-plugin/src/rules/prefer-includes.ts b/packages/eslint-plugin/src/rules/prefer-includes.ts index e0b5719a5108..94ec93a2609f 100644 --- a/packages/eslint-plugin/src/rules/prefer-includes.ts +++ b/packages/eslint-plugin/src/rules/prefer-includes.ts @@ -124,6 +124,27 @@ export default createRule({ ); } + function escapeString(str: string): string { + const EscapeMap = { + '\0': '\\0', + "'": "\\'", + '\\': '\\\\', + '\n': '\\n', + '\r': '\\r', + '\v': '\\v', + '\t': '\\t', + '\f': '\\f', + // "\b" cause unexpected replacements + // '\b': '\\b', + }; + const replaceRegex = new RegExp(Object.values(EscapeMap).join('|'), 'g'); + + return str.replace( + replaceRegex, + char => EscapeMap[char as keyof typeof EscapeMap], + ); + } + function checkArrayIndexOf( node: TSESTree.MemberExpression, allowFixing: boolean, @@ -202,12 +223,11 @@ export default createRule({ }, // /bar/.test(foo) - 'CallExpression > MemberExpression.callee[property.name="test"][computed=false]'( - node: TSESTree.MemberExpression, + 'CallExpression[arguments.length=1] > MemberExpression.callee[property.name="test"][computed=false]'( + node: TSESTree.MemberExpression & { parent: TSESTree.CallExpression }, ): void { - const callNode = node.parent as TSESTree.CallExpression; - const text = - callNode.arguments.length === 1 ? parseRegExp(node.object) : null; + const callNode = node.parent; + const text = parseRegExp(node.object); if (text == null) { return; } @@ -237,13 +257,14 @@ export default createRule({ argNode.type !== AST_NODE_TYPES.CallExpression; yield fixer.removeRange([callNode.range[0], argNode.range[0]]); + yield fixer.removeRange([argNode.range[1], callNode.range[1]]); if (needsParen) { yield fixer.insertTextBefore(argNode, '('); yield fixer.insertTextAfter(argNode, ')'); } yield fixer.insertTextAfter( argNode, - `${node.optional ? '?.' : '.'}includes('${text}'`, + `${node.optional ? '?.' : '.'}includes('${escapeString(text)}')`, ); }, }); diff --git a/packages/eslint-plugin/tests/rules/prefer-includes.test.ts b/packages/eslint-plugin/tests/rules/prefer-includes.test.ts index 6b37be5c59f0..cca4472c3395 100644 --- a/packages/eslint-plugin/tests/rules/prefer-includes.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-includes.test.ts @@ -234,6 +234,33 @@ ruleTester.run('prefer-includes', rule, { `, errors: [{ messageId: 'preferStringIncludes' }], }, + // test SequenceExpression + { + code: ` + function f(a: string): void { + /bar/.test((1 + 1, a)); + } + `, + output: ` + function f(a: string): void { + (1 + 1, a).includes('bar'); + } + `, + errors: [{ messageId: 'preferStringIncludes' }], + }, + { + code: ` + function f(a: string): void { + /\\0'\\\\\\n\\r\\v\\t\\f/.test(a); + } + `, + output: ` + function f(a: string): void { + a.includes('\\0\\'\\\\\\n\\r\\v\\t\\f'); + } + `, + errors: [{ messageId: 'preferStringIncludes' }], + }, { code: ` const pattern = new RegExp('bar');