Skip to content

Commit 5525b91

Browse files
authored
Fix incorrect pattern parsing when giving RegExp argument to RegExp constructor (#259)
1 parent 6ac574c commit 5525b91

File tree

4 files changed

+111
-12
lines changed

4 files changed

+111
-12
lines changed

lib/rules/no-useless-flag.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,10 @@ function getFlagLocation(
176176
end: sourceCode.getLocFromIndex(flagIndex + 1),
177177
}
178178
}
179-
return node.arguments[1].loc!
179+
if (node.arguments.length >= 2) {
180+
return node.arguments[1].loc!
181+
}
182+
return context.getSourceCode().getTokenAfter(node.arguments[0])!.loc!
180183
}
181184

182185
/**

lib/utils/index.ts

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { RegExpParser, visitRegExpAST } from "regexpp"
66
import { CALL, CONSTRUCT, ReferenceTracker } from "eslint-utils"
77
import type { Rule, AST, SourceCode } from "eslint"
88
import { parseStringTokens } from "./string-literal-parser"
9-
import { findVariable, getStringIfConstant } from "./ast-utils"
9+
import { findVariable, getStaticValue, getStringIfConstant } from "./ast-utils"
1010
import type { ReadonlyFlags, ToCharSetElement } from "regexp-ast-analysis"
1111
// eslint-disable-next-line no-restricted-imports -- Implement RegExpContext#toCharSet
1212
import { toCharSet } from "regexp-ast-analysis"
@@ -338,6 +338,7 @@ function buildRegexpVisitor(
338338
pattern: string | null
339339
flagsNode: ESTree.Expression | ESTree.SpreadElement | undefined
340340
flagsString: string | null
341+
ownsFlags: boolean
341342
}[] = []
342343
for (const { node } of tracker.iterateGlobalReferences({
343344
RegExp: { [CALL]: true, [CONSTRUCT]: true },
@@ -349,25 +350,47 @@ function buildRegexpVisitor(
349350
if (!patternNode || patternNode.type === "SpreadElement") {
350351
continue
351352
}
352-
const pattern = getStringIfConstant(context, patternNode)
353-
const flagsString = flagsNode
354-
? getStringIfConstant(context, flagsNode)
355-
: null
353+
const patternResult = getStaticValue(context, patternNode)
354+
if (!patternResult || patternResult.value == null) {
355+
continue
356+
}
357+
let pattern: string | null = null
358+
let { flagsString, ownsFlags } =
359+
flagsNode && flagsNode.type !== "SpreadElement"
360+
? {
361+
flagsString: getStringIfConstant(
362+
context,
363+
flagsNode,
364+
),
365+
ownsFlags: isStringLiteral(flagsNode),
366+
}
367+
: { flagsString: null, ownsFlags: false }
368+
if (patternResult.value instanceof RegExp) {
369+
pattern = patternResult.value.source
370+
if (!flagsNode) {
371+
// If no flag is given, the flag is also cloned.
372+
flagsString = patternResult.value.flags
373+
ownsFlags = true
374+
}
375+
} else {
376+
pattern = String(patternResult.value)
377+
}
356378

357379
regexpDataList.push({
358380
newOrCall,
359381
patternNode,
360382
pattern,
361383
flagsNode,
362384
flagsString,
385+
ownsFlags,
363386
})
364387
}
365388
for (const {
366389
newOrCall,
367390
patternNode,
368391
pattern,
369-
flagsNode,
370392
flagsString,
393+
ownsFlags,
371394
} of regexpDataList) {
372395
if (typeof pattern === "string") {
373396
let verifyPatternNode = patternNode
@@ -421,11 +444,7 @@ function buildRegexpVisitor(
421444
pattern,
422445
flags,
423446
flagsString,
424-
ownsFlags: Boolean(
425-
flagsNode &&
426-
flagsNode.type !== "SpreadElement" &&
427-
isStringLiteral(flagsNode),
428-
),
447+
ownsFlags,
429448
regexpNode: newOrCall,
430449
...helpers,
431450
getRegexpRange: (regexpNode) =>

tests/lib/rules/no-useless-assertions.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ tester.run("no-useless-assertions", rule as any, {
5050
// exponentially for nested loops. In this case, the 48 nested loops necessitate 2.8e14 starting positions.
5151
// There needs to be some optimization to work around this problem.
5252
String.raw`/((((((((((((((((((((((((((((((((((((((((((((((((\b)+)+)+)+)+)+)+)+)+)+)+)+)+)+)+)+)+)+)+)+)+)+)+)+)+)+)+)+)+)+)+)+)+)+)+)+)+)+)+)+)+)+)+)+)+)+)+)+/`,
53+
54+
// https://github.com/ota-meshi/eslint-plugin-regexp/issues/258
55+
String.raw`
56+
const orig = /^https?:\/\//i;
57+
const clone = new RegExp(orig);
58+
`,
5359
],
5460
invalid: [
5561
{

tests/lib/rules/no-useless-flag.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,77 @@ tester.run("no-useless-flag", rule as any, {
568568
},
569569
],
570570
},
571+
572+
// test for RegExp constructor with RegExp arguments
573+
{
574+
code: String.raw`
575+
const orig = /\w/i; // eslint-disable-line
576+
const clone = new RegExp(orig);
577+
`,
578+
output: null,
579+
errors: [
580+
{
581+
message:
582+
"The 'i' flag is unnecessary because the pattern only contains case-invariant characters.",
583+
line: 3,
584+
column: 42,
585+
},
586+
],
587+
},
588+
{
589+
code: String.raw`
590+
const orig = /\w/i; // eslint-disable-line
591+
const clone = new RegExp(orig, 'i');
592+
`,
593+
output: String.raw`
594+
const orig = /\w/i; // eslint-disable-line
595+
const clone = new RegExp(orig, '');
596+
`,
597+
errors: [
598+
{
599+
message:
600+
"The 'i' flag is unnecessary because the pattern only contains case-invariant characters.",
601+
line: 3,
602+
column: 44,
603+
},
604+
],
605+
},
606+
{
607+
code: String.raw`
608+
const orig = /\w/i;
609+
const clone = new RegExp(orig, '');
610+
`,
611+
output: String.raw`
612+
const orig = /\w/;
613+
const clone = new RegExp(orig, '');
614+
`,
615+
errors: [
616+
{
617+
message:
618+
"The 'i' flag is unnecessary because the pattern only contains case-invariant characters.",
619+
line: 2,
620+
column: 30,
621+
},
622+
],
623+
},
624+
{
625+
code: String.raw`
626+
const orig = /\w/;
627+
const clone = new RegExp(orig, 'i');
628+
`,
629+
output: String.raw`
630+
const orig = /\w/;
631+
const clone = new RegExp(orig, '');
632+
`,
633+
errors: [
634+
{
635+
message:
636+
"The 'i' flag is unnecessary because the pattern only contains case-invariant characters.",
637+
line: 3,
638+
column: 44,
639+
},
640+
],
641+
},
571642
],
572643
})
573644

0 commit comments

Comments
 (0)