biome complexity/useRegexLiterals
labelled safe
Analysis Nemotron 3 Super
Flags uses of the RegExp constructor where both arguments are static strings, recommending replacement with a regex literal.
Fix: Replaces `new RegExp(pattern, flags)` with `/pattern/flags` when both `pattern` and `flags` are string literals, preserving the exact literal values including escape sequences.
10 hazards
- If the pattern contains a forward slash (`/`), the regex literal may terminate early, causing a syntax error or incorrect pattern (e.g., `new RegExp("/abc/", "")` becomes `///abc///`, which is invalid).
- If the pattern contains unescaped regex metacharacters that were intended to be literal (e.g., `new RegExp("[abc]", "")`), the literal behaves the same, but future maintainers might misinterpret intent—though behavior is preserved, this is noted as a readability hazard in some contexts.
- If the flags string contains characters not valid in regex literals (e.g., `new RegExp("a", "xy")`), the literal `/a/xy` is invalid and throws a SyntaxError, whereas the constructor would have thrown a runtime TypeError—changing error timing and type.
- If the pattern is an empty string (`new RegExp("", "g")`), the literal `//g` is invalid due to ambiguous comment start, causing a parse failure where the constructor succeeded.
- If the flags string is empty (`new RegExp("abc", "")`), the literal `/abc/` is valid and equivalent, so no hazard here—but included for completeness in edge-case analysis.
- If the RegExp call is inside a larger expression (e.g., `x = new RegExp("a", "b") + 1`), the rewrite changes the expression structure and may affect operator precedence or parsing if not properly parenthesized (though Biome’s fix assumes standalone or safe contexts).
- If the code relies on the RegExp constructor creating a distinct object each time (e.g., for testing `instanceof`), the literal also creates a new object per evaluation, so behavior is preserved—no hazard in this case.
- If the pattern or flags string literals contain line terminators (e.g., via template literals misidentified as literals—though rule only fires on string literals), the rewrite would fail, but the rule only triggers on actual string literals, so this is excluded by precondition.
- If a comment is between `new` and `RegExp` (e.g., `new /* hint */ RegExp("a", "b")`), Biome’s fixer may not trigger or may mishandle spacing, but the rule as documented flags the call shape regardless of whitespace—so assuming the fixer handles whitespace, no semantic hazard, but potential formatting loss.
- If the code is in a context where `RegExp` has been shadowed (e.g., `let RegExp = String;`), the rule should not fire because it’s not the built-in, but if it does due to flawed detection, rewriting to a literal would break behavior by bypassing the shadow.
Findings
| program | oracle | label | judge | status | summary |
|---|---|---|---|---|---|
| r2-p001 | fix-breaks-syntax | labelled safe | real · high | related | The fix rewrites `new RegExp('a', 'xy')` to the invalid regex literal `/a/xy`, which contains illegal flags 'x' and 'y'. The RegExp constructor accepts arbitra… |
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | fix ok | Var hoisting and redeclaration | var hoisting and re-declaration inside loops or after use |
| r1-p002 | silent prediction off | Name collision across scopes | name collisions across scopes after a move (shadowing) |
| r1-p003 | fix ok | Distinct object creation | If the code relies on the RegExp constructor creating a distinct object each time (e.g., for testing `instanceof`), the literal also creates a new object per evaluation, so behavior is preserved—no ha |
| r1-p004 | silent prediction off | RegExp shadowing | If the code is in a context where `RegExp` has been shadowed (e.g., `let RegExp = String;`), the rule should not fire because it’s not the built-in, but if it does due to flawed detection, rewriting t |
| r1-p005 | silent prediction off | Null/undefined distinction | null/undefined distinction, optional chaining short-circuit, nullish vs falsy |
| r1-p006 | silent prediction off | This binding | this binding: arrow vs function, methods extracted from objects, call/apply/bind |
| r1-p007 | fix ok | Pattern contains forward slash | If the pattern contains a forward slash (`/`), the regex literal may terminate early, causing a syntax error or incorrect pattern (e.g., `new RegExp("/abc/", "")` becomes `///abc///`, which is invalid |
| r1-p008 | silent prediction off | Numeric/string coercion | numeric/string coercion: + vs template literal, objects with toString/valueOf, Symbol, BigInt |
| r2-p001 | fix-breaks-syntax | Invalid flags in regex literal cause SyntaxError instead of TypeError | If the flags string contains characters not valid in regex literals (e.g., `new RegExp("a", "xy")`), the literal `/a/xy` is invalid and throws a SyntaxError, whereas the constructor would have thrown |
| r2-p002 | silent prediction off | Name collision across scopes after a move (shadowing) | name collisions across scopes after a move (shadowing) |
| r2-p003 | silent prediction off | Null/undefined distinction with optional chaining | null/undefined distinction, optional chaining short-circuit, nullish vs falsy |
| r2-p004 | fix ok | Sparse arrays and holes interaction | sparse arrays, holes, Array(n) vs [n], array-likes |
| r2-p005 | silent prediction off | Order of evaluation with side effects duplicated | order of evaluation when expressions are merged, duplicated or deleted (side effects run twice or never) |
| r2-p006 | silent prediction off | Temporal dead zone after hoisting/reordering | temporal dead zone after code is hoisted or reordered |
| r2-p007 | silent prediction off | Function declaration inside block interacts with rewrite | function declarations inside blocks, switch cases, labeled statements |
| r2-p008 | fix ok prediction off | Var hoisting and redeclaration inside loop | Var hoisting and redeclaration (var hoisting and re-declaration inside loops or after use) |
Sources
r1-p001 · Var hoisting and redeclaration
1for (var i = 0; i < 2; i++) {
2 var re = new RegExp('a', '');
3 console.log(re.test('a'));
4}
5console.log(typeof re);
--- orig.js 2026-10-17 16:13:28.891035700 -0400 +++ fixed.js 2026-10-17 16:13:29.198029711 -0400 @@ -1,5 +1,5 @@ for (var i = 0; i < 2; i++) { - var re = new RegExp('a', ''); + var re = /a/; console.log(re.test('a')); } console.log(typeof re);
true true object
r1-p002 · Name collision across scopes
1function outer() {
2 var RegExp = Array;
3 function inner() {
4 var re = new RegExp('x', '');
5 console.log(re instanceof RegExp);
6 }
7 inner();
8}
9outer();
true
r1-p003 · Distinct object creation
1function test() {
2 var a = new RegExp('a', '');
3 var b = new RegExp('a', '');
4 console.log(a !== b);
5}
6test();
--- orig.js 2026-10-17 16:13:29.583022207 -0400 +++ fixed.js 2026-10-17 16:13:29.864016730 -0400 @@ -1,6 +1,6 @@ function test() { - var a = new RegExp('a', ''); - var b = new RegExp('a', ''); + var a = /a/; + var b = /a/; console.log(a !== b); } test();
true
r1-p004 · RegExp shadowing
1let RegExp = String;
2try {
3 var re = new RegExp('a', '');
4 console.log(re);
5} catch (e) {
6 console.log(e.name + ': ' + e.message);
7}
[String: 'a']
r1-p005 · Null/undefined distinction
1function getPattern() { return null; }
2var re = new RegExp(getPattern(), '');
3console.log(re === null ? 'null' : typeof re);
object
r1-p006 · This binding
1var obj = {
2 getPattern: function() { return 'a'; },
3 makeRE: function() { return new RegExp(this.getPattern(), ''); }
4};
5var fn = obj.makeRE;
6console.log(fn().test('a')); // Should be false due to lost this
r1-p007 · Pattern contains forward slash
1try {
2 var re = new RegExp('/abc/', '');
3 console.log(re.test('/abc/'));
4} catch (e) {
5 console.log(e.name + ': ' + e.message);
6}
--- orig.js 2026-10-17 16:13:30.706000351 -0400 +++ fixed.js 2026-10-17 16:13:30.959995412 -0400 @@ -1,5 +1,5 @@ try { - var re = new RegExp('/abc/', ''); + var re = /\/abc\//; console.log(re.test('/abc/')); } catch (e) { console.log(e.name + ': ' + e.message);
true
r1-p008 · Numeric/string coercion
1var obj = {
2 toString: function() { return '\\d+'; }
3};
4var re = new RegExp(obj, 'g');
5console.log(re.test('123'));
true
r2-p001 · Invalid flags in regex literal cause SyntaxError instead of TypeError
1try {
2 console.log(new RegExp('a', 'xy').test(''));
3} catch (e) {
4 console.log(e.constructor.name + ': ' + e.message);
5}
--- orig.js 2026-10-17 16:13:37.696865576 -0400 +++ fixed.js 2026-10-17 16:13:37.977860201 -0400 @@ -1,5 +1,5 @@ try { - console.log(new RegExp('a', 'xy').test('')); + console.log(/a/xy.test('')); } catch (e) { console.log(e.constructor.name + ': ' + e.message); }
SyntaxError: Invalid flags supplied to RegExp constructor 'xy'
r2-p002 · Name collision across scopes after a move (shadowing)
1function outer() {
2 let RegExp = class {};
3 try {
4 console.log(new RegExp('a', 'g').test('a'));
5 } catch (e) {
6 console.log(e.constructor.name + ': ' + e.message);
7 }
8}
9outer();
TypeError: (intermediate value).test is not a function
r2-p003 · Null/undefined distinction with optional chaining
1const maybePattern = null;
2console.log((maybePattern ?? '\\d+').length);
3try {
4 console.log(new RegExp(maybePattern ?? '\\d+', 'g').test('1'));
5} catch (e) {
6 console.log(e.constructor.name + ': ' + e.message);
7}
3 true
r2-p004 · Sparse arrays and holes interaction
1const arr = Array(2);
2arr[0] = new RegExp('a', 'g');
3console.log(arr[0].test('a'));
4console.log(arr[1] === undefined ? 'hole' : arr[1]);
--- orig.js 2026-10-17 16:13:38.464850905 -0400 +++ fixed.js 2026-10-17 16:13:38.734845752 -0400 @@ -1,4 +1,4 @@ const arr = Array(2); -arr[0] = new RegExp('a', 'g'); +arr[0] = /a/g; console.log(arr[0].test('a')); console.log(arr[1] === undefined ? 'hole' : arr[1]);
true hole
r2-p005 · Order of evaluation with side effects duplicated
1let count = 0;
2function getPattern() {
3 count++;
4 return 'a';
5}
6function getFlags() {
7 count++;
8 return 'g';
9}
10try {
11 console.log(new RegExp(getPattern(), getFlags()).test('a'));
12} catch (e) {
13 console.log(e.constructor.name + ': ' + e.message);
14}
15console.log('count: ' + count);
true count: 2
r2-p006 · Temporal dead zone after hoisting/reordering
1function test() {
2 console.log(typeof pattern);
3 const pattern = '\\d+';
4 try {
5 console.log(new RegExp(pattern, 'g').test('1'));
6 } catch (e) {
7 console.log(e.constructor.name + ': ' + e.message);
8 }
9}
10test();
r2-p007 · Function declaration inside block interacts with rewrite
1{
2 function getFlags() { return 'g'; }
3 try {
4 console.log(new RegExp('a', getFlags()).test('a'));
5 } catch (e) {
6 console.log(e.constructor.name + ': ' + e.message);
7 }
8}
true
r2-p008 · Var hoisting and redeclaration inside loop
1for (var i = 0; i < 2; i++) {
2 var re = new RegExp('a', 'g');
3 console.log(re.test('a'));
4}
5console.log(typeof re);
--- orig.js 2026-10-17 16:13:39.594829366 -0400 +++ fixed.js 2026-10-17 16:13:39.853824434 -0400 @@ -1,5 +1,5 @@ for (var i = 0; i < 2; i++) { - var re = new RegExp('a', 'g'); + var re = /a/g; console.log(re.test('a')); } console.log(typeof re);
true true object