From ffdb5ff9900e07374a2f3686447e3e2c78fbc38a Mon Sep 17 00:00:00 2001 From: Rafael Santana Date: Tue, 21 Sep 2021 03:37:47 -0300 Subject: [PATCH] fix(eslint-plugin): [prefer-regexp-exec] respect flags when using `RegExp` (#3855) --- .../src/rules/prefer-regexp-exec.ts | 27 +++++++-- .../tests/rules/prefer-regexp-exec.test.ts | 58 ++++++++++++++++++- 2 files changed, 80 insertions(+), 5 deletions(-) diff --git a/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts b/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts index 0d6693715ba..ebb92364e46 100644 --- a/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts +++ b/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts @@ -75,13 +75,31 @@ export default createRule({ return result; } + function isLikelyToContainGlobalFlag( + node: TSESTree.CallExpressionArgument, + ): boolean { + if ( + node.type === AST_NODE_TYPES.CallExpression || + node.type === AST_NODE_TYPES.NewExpression + ) { + const [, flags] = node.arguments; + return ( + flags.type === AST_NODE_TYPES.Literal && + typeof flags.value === 'string' && + flags.value.includes('g') + ); + } + + return node.type === AST_NODE_TYPES.Identifier; + } + return { "CallExpression[arguments.length=1] > MemberExpression.callee[property.name='match'][computed=false]"( memberNode: TSESTree.MemberExpression, ): void { const objectNode = memberNode.object; const callNode = memberNode.parent as TSESTree.CallExpression; - const argumentNode = callNode.arguments[0]; + const [argumentNode] = callNode.arguments; const argumentValue = getStaticValue(argumentNode, globalScope); if ( @@ -96,9 +114,10 @@ export default createRule({ // Don't report regular expressions with global flag. if ( - argumentValue && - argumentValue.value instanceof RegExp && - argumentValue.value.flags.includes('g') + (!argumentValue && isLikelyToContainGlobalFlag(argumentNode)) || + (argumentValue && + argumentValue.value instanceof RegExp && + argumentValue.value.flags.includes('g')) ) { return; } diff --git a/packages/eslint-plugin/tests/rules/prefer-regexp-exec.test.ts b/packages/eslint-plugin/tests/rules/prefer-regexp-exec.test.ts index b4593510807..8a5bdfd099d 100644 --- a/packages/eslint-plugin/tests/rules/prefer-regexp-exec.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-regexp-exec.test.ts @@ -1,5 +1,5 @@ import rule from '../../src/rules/prefer-regexp-exec'; -import { RuleTester, getFixturesRootDir } from '../RuleTester'; +import { getFixturesRootDir, RuleTester } from '../RuleTester'; const rootPath = getFixturesRootDir(); @@ -56,6 +56,24 @@ const matchers = [{ match: (s: RegExp) => false }]; const file = ''; matchers.some(matcher => !!file.match(matcher)); `, + // https://github.com/typescript-eslint/typescript-eslint/issues/3477 + ` +function test(pattern: string) { + 'hello hello'.match(RegExp(pattern, 'g'))?.reduce(() => []); +} + `, + // https://github.com/typescript-eslint/typescript-eslint/issues/3477 + ` +function test(pattern: string) { + 'hello hello'.match(new RegExp(pattern, 'gi'))?.reduce(() => []); +} + `, + // https://github.com/typescript-eslint/typescript-eslint/issues/3477 + ` +const matchCount = (str: string, re: RegExp) => { + return (str.match(re) || []).length; +}; + `, ], invalid: [ { @@ -174,6 +192,44 @@ function f(s: T) { output: ` function f(s: T) { /thing/.exec(s); +} + `, + }, + { + code: ` +const text = 'something'; +const search = new RegExp('test', ''); +text.match(search); + `, + errors: [ + { + messageId: 'regExpExecOverStringMatch', + line: 4, + column: 6, + }, + ], + output: ` +const text = 'something'; +const search = new RegExp('test', ''); +search.exec(text); + `, + }, + { + code: ` +function test(pattern: string) { + 'check'.match(new RegExp(pattern, undefined)); +} + `, + errors: [ + { + messageId: 'regExpExecOverStringMatch', + line: 3, + column: 11, + }, + ], + output: ` +function test(pattern: string) { + new RegExp(pattern, undefined).exec('check'); } `, },