diff --git a/lib/rules/no-global-regexp-flag-in-query.ts b/lib/rules/no-global-regexp-flag-in-query.ts index 9bf9a2ef..de2f108d 100644 --- a/lib/rules/no-global-regexp-flag-in-query.ts +++ b/lib/rules/no-global-regexp-flag-in-query.ts @@ -37,7 +37,13 @@ export default createTestingLibraryRule({ }, defaultOptions: [], create(context, _, helpers) { - function report(literalNode: TSESTree.Node) { + /** + * Checks if node is reportable (has a regex that contains 'g') and if it is, reports it with `context.report()`. + * + * @param literalNode Literal node under to be + * @returns {Boolean} indicatinf if literal was reported + */ + function reportLiteralWithRegex(literalNode: TSESTree.Node) { if ( isLiteral(literalNode) && 'regex' in literalNode && @@ -76,7 +82,37 @@ export default createTestingLibraryRule({ return []; } + // Helper array to store variable nodes that have a literal with regex + // e.g. `const countRegExp = /count/gi` will be store here + const variableNodesWithRegexs: TSESTree.VariableDeclarator[] = []; + + function hasRegexInVariable( + identifier: TSESTree.Identifier + ): TSESTree.VariableDeclarator | undefined { + return variableNodesWithRegexs.find((varNode) => { + if ( + ASTUtils.isVariableDeclarator(varNode) && + ASTUtils.isIdentifier(varNode.id) + ) { + return varNode.id.name === identifier.name; + } + return undefined; + }); + } + return { + // internal helper function, helps store all variables with regex to `variableNodesWithRegexs` + // could potentially be refactored to using context.getDeclaredVariables() + VariableDeclarator(node: TSESTree.Node) { + if ( + ASTUtils.isVariableDeclarator(node) && + isLiteral(node.init) && + 'regex' in node.init && + node.init.regex.flags.includes('g') + ) { + variableNodesWithRegexs.push(node); + } + }, CallExpression(node) { const identifierNode = getDeepestIdentifierNode(node); if (!identifierNode || !helpers.isQuery(identifierNode)) { @@ -85,11 +121,44 @@ export default createTestingLibraryRule({ const [firstArg, secondArg] = getArguments(identifierNode); - const firstArgumentHasError = report(firstArg); + const firstArgumentHasError = reportLiteralWithRegex(firstArg); if (firstArgumentHasError) { return; } + // Case issue #592: a variable that has a regex is passed to testing library query + + if (ASTUtils.isIdentifier(firstArg)) { + const regexVariableNode = hasRegexInVariable(firstArg); + if (regexVariableNode !== undefined) { + context.report({ + node: firstArg, + messageId: 'noGlobalRegExpFlagInQuery', + fix(fixer) { + if ( + ASTUtils.isVariableDeclarator(regexVariableNode) && + isLiteral(regexVariableNode.init) && + 'regex' in regexVariableNode.init && + regexVariableNode.init.regex.flags.includes('g') + ) { + const splitter = regexVariableNode.init.raw.lastIndexOf('/'); + const raw = regexVariableNode.init.raw.substring(0, splitter); + const flags = regexVariableNode.init.raw.substring( + splitter + 1 + ); + const flagsWithoutGlobal = flags.replace('g', ''); + + return fixer.replaceText( + regexVariableNode.init, + `${raw}/${flagsWithoutGlobal}` + ); + } + return null; + }, + }); + } + } + if (isObjectExpression(secondArg)) { const namePropertyNode = secondArg.properties.find( (p) => @@ -100,7 +169,7 @@ export default createTestingLibraryRule({ ) as TSESTree.Property | undefined; if (namePropertyNode) { - report(namePropertyNode.value); + reportLiteralWithRegex(namePropertyNode.value); } } }, diff --git a/tests/lib/rules/no-global-regexp-flag-in-query.test.ts b/tests/lib/rules/no-global-regexp-flag-in-query.test.ts index e53f7451..f885771d 100644 --- a/tests/lib/rules/no-global-regexp-flag-in-query.test.ts +++ b/tests/lib/rules/no-global-regexp-flag-in-query.test.ts @@ -196,5 +196,29 @@ ruleTester.run(RULE_NAME, rule, { import { within } from '@testing-library/dom' within(element).queryAllByText(/hello/i)`, }, + { + code: ` + const countRegExp = /count/gm + const anotherRegExp = /something/mgi + expect(screen.getByText(countRegExp)).toBeInTheDocument() + expect(screen.getByAllText(anotherRegExp)).toBeInTheDocument()`, + errors: [ + { + messageId: 'noGlobalRegExpFlagInQuery', + line: 4, + column: 28, + }, + { + messageId: 'noGlobalRegExpFlagInQuery', + line: 5, + column: 31, + }, + ], + output: ` + const countRegExp = /count/m + const anotherRegExp = /something/mi + expect(screen.getByText(countRegExp)).toBeInTheDocument() + expect(screen.getByAllText(anotherRegExp)).toBeInTheDocument()`, + }, ], });