Skip to content

Commit e2ea687

Browse files
authored
feat(no-global-regexp-flag-in-query): Detect global RegExp in variable declarations (#678)
Closes #592 * fix: change function name to more informative and add JS Doc comment * feat(no-global-regexp-flag-in-query): detect global regexp in variable declarations
1 parent c3504a7 commit e2ea687

File tree

2 files changed

+96
-3
lines changed

2 files changed

+96
-3
lines changed

lib/rules/no-global-regexp-flag-in-query.ts

+72-3
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,13 @@ export default createTestingLibraryRule<Options, MessageIds>({
3737
},
3838
defaultOptions: [],
3939
create(context, _, helpers) {
40-
function report(literalNode: TSESTree.Node) {
40+
/**
41+
* Checks if node is reportable (has a regex that contains 'g') and if it is, reports it with `context.report()`.
42+
*
43+
* @param literalNode Literal node under to be
44+
* @returns {Boolean} indicatinf if literal was reported
45+
*/
46+
function reportLiteralWithRegex(literalNode: TSESTree.Node) {
4147
if (
4248
isLiteral(literalNode) &&
4349
'regex' in literalNode &&
@@ -76,7 +82,37 @@ export default createTestingLibraryRule<Options, MessageIds>({
7682
return [];
7783
}
7884

85+
// Helper array to store variable nodes that have a literal with regex
86+
// e.g. `const countRegExp = /count/gi` will be store here
87+
const variableNodesWithRegexs: TSESTree.VariableDeclarator[] = [];
88+
89+
function hasRegexInVariable(
90+
identifier: TSESTree.Identifier
91+
): TSESTree.VariableDeclarator | undefined {
92+
return variableNodesWithRegexs.find((varNode) => {
93+
if (
94+
ASTUtils.isVariableDeclarator(varNode) &&
95+
ASTUtils.isIdentifier(varNode.id)
96+
) {
97+
return varNode.id.name === identifier.name;
98+
}
99+
return undefined;
100+
});
101+
}
102+
79103
return {
104+
// internal helper function, helps store all variables with regex to `variableNodesWithRegexs`
105+
// could potentially be refactored to using context.getDeclaredVariables()
106+
VariableDeclarator(node: TSESTree.Node) {
107+
if (
108+
ASTUtils.isVariableDeclarator(node) &&
109+
isLiteral(node.init) &&
110+
'regex' in node.init &&
111+
node.init.regex.flags.includes('g')
112+
) {
113+
variableNodesWithRegexs.push(node);
114+
}
115+
},
80116
CallExpression(node) {
81117
const identifierNode = getDeepestIdentifierNode(node);
82118
if (!identifierNode || !helpers.isQuery(identifierNode)) {
@@ -85,11 +121,44 @@ export default createTestingLibraryRule<Options, MessageIds>({
85121

86122
const [firstArg, secondArg] = getArguments(identifierNode);
87123

88-
const firstArgumentHasError = report(firstArg);
124+
const firstArgumentHasError = reportLiteralWithRegex(firstArg);
89125
if (firstArgumentHasError) {
90126
return;
91127
}
92128

129+
// Case issue #592: a variable that has a regex is passed to testing library query
130+
131+
if (ASTUtils.isIdentifier(firstArg)) {
132+
const regexVariableNode = hasRegexInVariable(firstArg);
133+
if (regexVariableNode !== undefined) {
134+
context.report({
135+
node: firstArg,
136+
messageId: 'noGlobalRegExpFlagInQuery',
137+
fix(fixer) {
138+
if (
139+
ASTUtils.isVariableDeclarator(regexVariableNode) &&
140+
isLiteral(regexVariableNode.init) &&
141+
'regex' in regexVariableNode.init &&
142+
regexVariableNode.init.regex.flags.includes('g')
143+
) {
144+
const splitter = regexVariableNode.init.raw.lastIndexOf('/');
145+
const raw = regexVariableNode.init.raw.substring(0, splitter);
146+
const flags = regexVariableNode.init.raw.substring(
147+
splitter + 1
148+
);
149+
const flagsWithoutGlobal = flags.replace('g', '');
150+
151+
return fixer.replaceText(
152+
regexVariableNode.init,
153+
`${raw}/${flagsWithoutGlobal}`
154+
);
155+
}
156+
return null;
157+
},
158+
});
159+
}
160+
}
161+
93162
if (isObjectExpression(secondArg)) {
94163
const namePropertyNode = secondArg.properties.find(
95164
(p) =>
@@ -100,7 +169,7 @@ export default createTestingLibraryRule<Options, MessageIds>({
100169
) as TSESTree.Property | undefined;
101170

102171
if (namePropertyNode) {
103-
report(namePropertyNode.value);
172+
reportLiteralWithRegex(namePropertyNode.value);
104173
}
105174
}
106175
},

tests/lib/rules/no-global-regexp-flag-in-query.test.ts

+24
Original file line numberDiff line numberDiff line change
@@ -196,5 +196,29 @@ ruleTester.run(RULE_NAME, rule, {
196196
import { within } from '@testing-library/dom'
197197
within(element).queryAllByText(/hello/i)`,
198198
},
199+
{
200+
code: `
201+
const countRegExp = /count/gm
202+
const anotherRegExp = /something/mgi
203+
expect(screen.getByText(countRegExp)).toBeInTheDocument()
204+
expect(screen.getByAllText(anotherRegExp)).toBeInTheDocument()`,
205+
errors: [
206+
{
207+
messageId: 'noGlobalRegExpFlagInQuery',
208+
line: 4,
209+
column: 28,
210+
},
211+
{
212+
messageId: 'noGlobalRegExpFlagInQuery',
213+
line: 5,
214+
column: 31,
215+
},
216+
],
217+
output: `
218+
const countRegExp = /count/m
219+
const anotherRegExp = /something/mi
220+
expect(screen.getByText(countRegExp)).toBeInTheDocument()
221+
expect(screen.getByAllText(anotherRegExp)).toBeInTheDocument()`,
222+
},
199223
],
200224
});

0 commit comments

Comments
 (0)