Replacement References A Group The Pattern Does Not Have
ID |
javascript.regex_replacement_group_reference |
Severity |
high |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Reliability |
Language |
JavaScript |
Tags |
regex, reliability |
Description
Reports a replace or replaceAll whose replacement string references a capture group the pattern does not have. JavaScript does not fail here: an out-of-range $n or an undeclared $<name> is substituted with the empty string, so the call quietly produces text with a piece missing.
The rule requires the pattern to declare at least one capturing group before judging a numeric reference. In a group-less pattern the language leaves $1 as literal text, which is exactly what a currency template wants, so only once a group is written is $n unambiguously meant as a reference. A named reference is judged on the same principle.
The replacement must be a single string literal; a template literal, a concatenation or a variable is built at run time and carries no decidable reference. The replacer-function form takes its groups as parameters and is not a replacement string. $$, $&, the backtick form and $' are the language’s own escapes and never group references.
Rationale
// Bad
"2026-09".replace(/(\d+)-(\d+)/, "$3/$1"); // "/2026" -- $3 vanished
"2026".replace(/(?<y>\d{4})/, "$<yer>"); // "" -- the group is named y
-
Nothing throws and nothing warns. The result is a string that is merely wrong, usually shorter than expected.
-
The defect survives review because the intent reads correctly: the reader counts the groups in the pattern and the references in the replacement separately, and they are far apart on the line.
-
A mistyped group name behaves the same way as a mistyped one-off, so a rename in the pattern silently breaks every replacement that referred to the old name.
Remediation
Renumber the references to the groups the pattern actually captures, or add the group the replacement expects. Named groups make the pairing checkable by eye.
// Good
"2026-09".replace(/(\d+)-(\d+)/, "$2/$1");
"2026".replace(/(?<y>\d{4})/, "$<y>");
// Also good: a non-capturing group does not consume a number
"ab".replace(/(?:a)(b)/, "$1");