biome nursery/useUnicodeRegex
labelled safe
Analysis Nemotron 3 Super
The rule flags any RegExp literal or constructor call missing the 'u' or 'v' flag.
Fix: Adds the 'u' flag to RegExp literals lacking any flag, appends 'u' to existing flags in literals, and adds 'u' as the second argument to RegExp constructor calls missing it or having insufficient flags.
10 hazards
- Adding 'u' to a RegExp literal containing an invalid Unicode escape like \\x{1} changes it from a syntax error to valid code, altering program behavior where the error was intentional.
- Appending 'u' to flags in a literal like /[a-z&&[^m-z]]/ may break execution in environments without Unicode Sets support, as the 'v' flag would be required but only 'u' is added.
- For RegExp('pattern', 'uw'), the fix produces 'uwu' which includes duplicate 'u' flags; while harmless, it is non-standard and could confuse tools expecting canonical flag order.
- If the pattern string in new RegExp contains a backreference like \\1, adding 'u' changes its meaning from octal escape to backreference, potentially altering matches.
- In code relying on legacy octal escape interpretation (e.g., new RegExp('\\256')), adding 'u' forbids octal escapes, causing the pattern to fail to compile where it previously matched extended ASCII.
- When the RegExp constructor is called with a non-string pattern (e.g., another RegExp), adding 'u' as a second argument is ignored per spec, but if the first argument is a RegExp and flags are provided, the fix may incorrectly add 'u' when flags should replace rather than append.
- If the regex is used in a context where the 'u' flag changes the interpretation of ^ and $ to match line terminators in Unicode-sensitive ways, adding 'u' could alter multiline matching behavior unexpectedly.
- Comments attached to the RegExp token (e.g., /foo/* comment */) may be lost or misplaced during the rewrite if the tool does not preserve trailing or inner comments.
- In a sequence like /foo/+bar, inserting 'u' to make /foo/u/+bar could be misparsed due to precedence if whitespace is not managed, though unlikely, it risks altering expression structure.
- For dynamically constructed regexes via new RegExp(userInput, 'g'), the fix adds 'u' making it 'gu', but if userInput contains unpaired surrogates, the 'u' flag may cause a SyntaxError where the code previously relied on loose matching.
Findings
| program | oracle | label | judge | status | summary |
|---|---|---|---|---|---|
| r1-p003 | fix-changes-behavior | labelled safe | intended-change · low | related | The rule adds the 'u' flag to regexes lacking Unicode mode. The program constructs a new RegExp from an existing one and passes an additional 'i' flag; per the… |
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | fix ok | Label break continue with finally | label/break/continue control flow, finally blocks, early returns in nested blocks |
| r1-p002 | fix ok | Multiline ^$ behavior change | If the regex is used in a context where the 'u' flag changes the interpretation of ^ and $ to match line terminators in Unicode-sensitive ways, adding 'u' could alter multiline matching behavior unexp |
| r1-p003 | fix-changes-behavior | RegExp constructor with RegExp arg | When the RegExp constructor is called with a non-string pattern (e.g., another RegExp), adding 'u' as a second argument is ignored per spec, but if the first argument is a RegExp and flags are provide |
| r1-p004 | fix ok | Coercion in pattern | numeric/string coercion: + vs template literal, objects with toString/valueOf, Symbol, BigInt |
| r1-p005 | invalid | Comment preservation | Comments attached to the RegExp token (e.g., /foo/* comment */) may be lost or misplaced during the rewrite if the tool does not preserve trailing or inner comments. |
| r1-p006 | fix ok | Order of evaluation side effects | order of evaluation when expressions are merged, duplicated or deleted (side effects run twice or never) |
| r1-p007 | fix ok | Class fields interaction | class fields, static blocks, private names, super calls |
| r1-p008 | fix ok | Var hoisting in loop | var hoisting and re-declaration inside loops or after use |
| r2-p001 | fix ok | Precedence risk in unary plus concatenation | In a sequence like /foo/+bar, inserting 'u' to make /foo/u/+bar could be misparsed due to precedence if whitespace is not managed. |
| r2-p002 | fix ok | Label break continue with finally | Label/break/continue control flow, finally blocks, early returns in nested blocks |
| r2-p003 | fix ok | Getters, setters and Object.defineProperty side effects | Getters, setters and Object.defineProperty side effects |
| r2-p004 | fix ok | Async/await ordering, generators and yield position | Async/await ordering, generators and yield position |
| r2-p005 | fired, no fix | Legacy octal escape interpretation | In code relying on legacy octal escape interpretation (e.g., new RegExp('\256')), adding 'u' forbids octal escapes, causing the pattern to fail to compile where it previously matched extended ASCII. |
| r2-p006 | fired, no fix | Backreference meaning change under 'u' flag | If the pattern string in new RegExp contains a backreference like \\1, adding 'u' changes its meaning from octal escape to backreference, potentially altering matches. |
| r2-p007 | fix ok | Multiline ^$ behavior change with Unicode flag | If the regex is used in a context where the 'u' flag changes the interpretation of ^ and $ to match line terminators in Unicode-sensitive ways, adding 'u' could alter multiline matching behavior unexp |
| r2-p008 | fix ok | Null/undefined distinction, optional chaining short-circuit | Null/undefined distinction, optional chaining short-circuit, nullish vs falsy |
Sources
r1-p001 · Label break continue with finally
1outer: {
2 let result = '';
3 try {
4 for (let i = 0; i < 2; i++) {
5 const m = /a/.exec('a');
6 result += m ? m[0] : 'x';
7 if (i === 0) break outer;
8 }
9 } finally {
10 result += 'f';
11 }
12 console.log(result);
13}
--- orig.js 2026-10-17 16:50:55.968201295 -0400 +++ fixed.js 2026-10-17 16:50:56.229206192 -0400 @@ -2,7 +2,7 @@ let result = ''; try { for (let i = 0; i < 2; i++) { - const m = /a/.exec('a'); + const m = /a/u.exec('a'); result += m ? m[0] : 'x'; if (i === 0) break outer; }
r1-p002 · Multiline ^$ behavior change
1let s = 'a\nb';
2let r = /^.$/;
3console.log(r.test(s));
--- orig.js 2026-10-17 16:50:56.395209305 -0400 +++ fixed.js 2026-10-17 16:50:56.665214370 -0400 @@ -1,3 +1,3 @@ let s = 'a\nb'; -let r = /^.$/; +let r = /^.$/u; console.log(r.test(s));
false
r1-p003 · RegExp constructor with RegExp arg
1let base = /a/g;
2let copy = new RegExp(base, 'i');
3console.log(copy.flags);
--- orig.js 2026-10-17 16:50:56.840217652 -0400 +++ fixed.js 2026-10-17 16:50:57.122222940 -0400 @@ -1,3 +1,3 @@ -let base = /a/g; -let copy = new RegExp(base, 'i'); +let base = /a/gu; +let copy = new RegExp(base, 'iu'); console.log(copy.flags);
i
r1-p004 · Coercion in pattern
1let obj = { valueOf: () => 'b', toString: () => 'B' };
2let p = +obj;
3let r = new RegExp(p, '');
4console.log(r.test('b'));
--- orig.js 2026-10-17 16:50:57.291226107 -0400 +++ fixed.js 2026-10-17 16:50:57.565231241 -0400 @@ -1,4 +1,4 @@ let obj = { valueOf: () => 'b', toString: () => 'B' }; let p = +obj; -let r = new RegExp(p, ''); +let r = new RegExp(p, 'u'); console.log(r.test('b'));
false
r1-p005 · Comment preservation (generated program does not parse: /home/sun/dev/diaghunt/runs/2026-10-17T20-50-4)
1/* start */
2let r = /a/* middle */;
3let m = r.exec('a');
4console.log(m ? m[0] : null);
5/* end */
r1-p006 · Order of evaluation side effects
1let count = 0;
2let getPat = () => {
3 count++;
4 return 'a';
5};
6let r = new RegExp(getPat(), '');
7console.log(count);
--- orig.js 2026-10-17 16:50:57.785235364 -0400 +++ fixed.js 2026-10-17 16:50:58.059240497 -0400 @@ -3,5 +3,5 @@ count++; return 'a'; }; -let r = new RegExp(getPat(), ''); +let r = new RegExp(getPat(), 'u'); console.log(count);
1
r1-p007 · Class fields interaction
1class C {
2 static r = /a/;
3 static {
4 console.log(C.r.test('a'));
5 }
6}
--- orig.js 2026-10-17 16:50:58.224243586 -0400 +++ fixed.js 2026-10-17 16:50:58.500248753 -0400 @@ -1,5 +1,5 @@ class C { - static r = /a/; + static r = /a/u; static { console.log(C.r.test('a')); }
true
r1-p008 · Var hoisting in loop
1function f() {
2 for (let i = 0; i < 2; i++) {
3 if (i === 0) {
4 var r = /a/;
5 }
6 console.log(r.test('a'));
7 }
8}
9f();
--- orig.js 2026-10-17 16:50:58.664251824 -0400 +++ fixed.js 2026-10-17 16:50:58.939256972 -0400 @@ -1,7 +1,7 @@ function f() { for (let i = 0; i < 2; i++) { if (i === 0) { - var r = /a/; + var r = /a/u; } console.log(r.test('a')); }
true true
r2-p001 · Precedence risk in unary plus concatenation
1/abc/+1
--- orig.js 2026-10-17 16:51:05.062371213 -0400 +++ fixed.js 2026-10-17 16:51:05.317375954 -0400 @@ -1 +1 @@ -/abc/+1 +/abc/u+1
r2-p002 · Label break continue with finally
1outer: {
2 try {
3 if (/test/.test('x')) break outer;
4 console.log('should not reach');
5 } finally {
6 console.log('finally ran');
7 }
8}
9console.log('after label');
--- orig.js 2026-10-17 16:51:05.482379022 -0400 +++ fixed.js 2026-10-17 16:51:05.750384006 -0400 @@ -1,6 +1,6 @@ outer: { try { - if (/test/.test('x')) break outer; + if (/test/u.test('x')) break outer; console.log('should not reach'); } finally { console.log('finally ran');
should not reach finally ran after label
r2-p003 · Getters, setters and Object.defineProperty side effects
1const obj = {};
2Object.defineProperty(obj, 'getter', {
3 get() { return /pattern/.test('input'); }
4});
5console.log(obj.getter);
--- orig.js 2026-10-17 16:51:05.932387390 -0400 +++ fixed.js 2026-10-17 16:51:06.205392463 -0400 @@ -1,5 +1,5 @@ const obj = {}; Object.defineProperty(obj, 'getter', { - get() { return /pattern/.test('input'); } + get() { return /pattern/u.test('input'); } }); console.log(obj.getter);
false
r2-p004 · Async/await ordering, generators and yield position
1(async () => {
2 const result = await Promise.resolve(/hello/.test('hello'));
3 console.log(result);
4})();
--- orig.js 2026-10-17 16:51:06.373395584 -0400 +++ fixed.js 2026-10-17 16:51:06.660400915 -0400 @@ -1,4 +1,4 @@ (async () => { - const result = await Promise.resolve(/hello/.test('hello')); + const result = await Promise.resolve(/hello/u.test('hello')); console.log(result); })();
true
r2-p005 · Legacy octal escape interpretation
1try {
2 const regex = new RegExp('\\256');
3 console.log(regex.test('\xa0'));
4} catch (e) {
5 console.log(e.name + ': ' + e.message);
6}
false
r2-p006 · Backreference meaning change under 'u' flag
1try {
2 const regex1 = new RegExp('\\1');
3 console.log('Pattern without u:', regex1);
4 console.log('Test "\\x01"\\x01:', regex1.test('\x01\x01'));
5} catch (e) {
6 console.log('Error:', e.name + ': ' + e.message);
7}
8try {
9 const regex2 = new RegExp('\\1', 'u');
10 console.log('Pattern with u:', regex2);
11 console.log('Test "\\x01"\\x01:', regex2.test('\x01\x01'));
12} catch (e) {
13 console.log('Error with u:', e.name + ': ' + e.message);
14}
Pattern without u: /\1/ Test "\x01"\x01: true Error with u: SyntaxError: Invalid regular expression: /\1/u: Invalid escape
r2-p007 · Multiline ^$ behavior change with Unicode flag
1const str = 'a\nb\n';
2console.log(/^b$/gm.test(str));
3console.log(/^b$/gmu.test(str));
--- orig.js 2026-10-17 16:51:07.406414766 -0400 +++ fixed.js 2026-10-17 16:51:07.689420018 -0400 @@ -1,3 +1,3 @@ const str = 'a\nb\n'; -console.log(/^b$/gm.test(str)); +console.log(/^b$/gmu.test(str)); console.log(/^b$/gmu.test(str));
true true
r2-p008 · Null/undefined distinction, optional chaining short-circuit
1let obj = { prop: { value: /test/.test('foo') } };
2console.log(obj?.prop?.value);
3obj = null;
4console.log(obj?.prop?.value);
--- orig.js 2026-10-17 16:51:07.863423248 -0400 +++ fixed.js 2026-10-17 16:51:08.144428460 -0400 @@ -1,4 +1,4 @@ -let obj = { prop: { value: /test/.test('foo') } }; +let obj = { prop: { value: /test/u.test('foo') } }; console.log(obj?.prop?.value); obj = null; console.log(obj?.prop?.value);
false undefined