biome complexity/noAdjacentSpacesInRegex
labelled safe
Analysis Nemotron 3 Super
The rule flags regex literals containing two or more adjacent literal space characters.
Fix: Replaces every maximal sequence of two or more consecutive literal space characters inside a regex literal with exactly one space character.
10 hazards
- Changes meaning when spaces are significant, e.g., in patterns like '/a b/' where two spaces are required to match.
- Breaks quantifier semantics if spaces are part of a quantified subpattern, e.g., '/(x ){2}/' becomes '/(x ){2}/', altering repetition count.
- Alters behavior in free-spacing mode (/x flag) where literal spaces are ignored and the fix removes meaningful visual spacing.
- Affects regexes relying on exact space counts for parsing, such as fixed-format text matchers like '/\d{3} \d{2}/'.
- Modifies patterns using spaces as delimiters in alternatives, e.g., '/foo | bar/' becomes '/foo | bar/', changing token boundaries.
- Impacts negated character classes containing spaces, e.g., '/[^ ] /' becomes '/[^ ] /', altering what characters are matched.
- Disrupts tab-separated value parsers where literal spaces are significant, e.g., '/\t \t/' becomes '/\t \t/'.
- Removes intentional formatting in complex regexes where spaces improve readability without affecting semantics, but the fix still alters the literal.
- May break code that generates regexes via string concatenation relying on exact space counts from literals, e.g., `new RegExp('/' + pattern + '/')`.
- Interferes with source inspection or debugging where the original literal's space count is logged or compared.
Findings
| program | oracle | label | judge | status | summary |
|---|---|---|---|---|---|
| r1-p008 | fix-changes-behavior | labelled safe | introspection-only · low | duplicate | The fix rewrites the regex literal from /a b c/ to /a {2}b {2}c/, which changes the value of RegExp.prototype.source. The program only inspects this source t… |
| r2-p001 | fix-changes-behavior | labelled safe | introspection-only · low | related | The fix rewrites the regex literal from `/hello world/` to `/hello {2}world/`, which changes the value of `RegExp.prototype.source` that the program explicitl… |
Programs
| id | result | title | hazard |
|---|---|---|---|
| r1-p001 | fix ok | Null/Undefined Distinction | null/undefined distinction, optional chaining short-circuit, nullish vs falsy |
| r1-p002 | fix ok | Order of Evaluation Merged/Duplicated | order of evaluation when expressions are merged, duplicated or deleted (side effects run twice or never) |
| r1-p003 | fix ok | Comments Attached to Removed Code | comments attached to the removed or moved code |
| r1-p004 | fix ok | Strict vs Sloppy Mode Semantics | strict vs sloppy mode semantics |
| r1-p005 | invalid | Free-Spacing Mode (/x Flag) | Alters behavior in free-spacing mode (/x flag) where literal spaces are ignored and the fix removes meaningful visual spacing. |
| r1-p006 | fix ok | Intentional Formatting for Readability | Removes intentional formatting in complex regexes where spaces improve readability without affecting semantics, but the fix still alters the literal. |
| r1-p007 | fix ok | Arguments Object and Function Length | arguments object, function.length, default parameters, rest parameters |
| r1-p008 | fix-changes-behavior | Source Inspection or Debugging Interference | Interferes with source inspection or debugging where the original literal's space count is logged or compared. |
| r2-p001 | fix-changes-behavior | Getter/setter accessing modified regex source | getters, setters and Object.defineProperty side effects |
| r2-p002 | fix ok | Var hoisting captures altered regex before redeclaration | var hoisting and re-declaration inside loops or after use |
| r2-p003 | fix ok | Alternatives with delimiter spaces changed by fix | Modifies patterns using spaces as delimiters in alternatives, e.g., '/foo | bar/' becomes '/foo | bar/', changing token boundaries. |
| r2-p004 | fix ok | Block-scoped function declaration uses altered regex | function declarations inside blocks, switch cases, labeled statements |
| r2-p005 | fix ok prediction off | ASI hazard: regex on next line interpreted as division | ASI and semicolon-less code, leading ( or [ on the next line |
| r2-p006 | fix ok | Exception thrown during regex literal creation | exceptions thrown inside the code the fix removes or rewrites |
| r2-p007 | fix ok | Tab-separated value parser broken by space collapse | Disrupts tab-separated value parsers where literal spaces are significant, e.g., '/\t \t/' becomes '/\t \t/'. |
| r2-p008 | fix ok | Unicode flag and lastIndex altered by fix | regular expressions: escapes, unicode flag, lastIndex with the g flag |
Sources
r1-p001 · Null/Undefined Distinction
1const input = 'a bc';
2let result;
3if ((/(a) (bc)/).test(input)) {
4 result = RegExp.$1 ?? 'no';
5} else {
6 result = undefined;
7}
8console.log(result);
--- orig.js 2026-10-17 16:03:56.217465690 -0400 +++ fixed.js 2026-10-17 16:03:56.506446164 -0400 @@ -1,6 +1,6 @@ const input = 'a bc'; let result; -if ((/(a) (bc)/).test(input)) { +if ((/(a) {2}(bc)/).test(input)) { result = RegExp.$1 ?? 'no'; } else { result = undefined;
a
r1-p002 · Order of Evaluation Merged/Duplicated
1let calls = 0;
2function getStr() { return `str${++calls}`; }
3const str = getStr();
4const re = /foo ${getStr()}bar/;
5console.log(calls, re.test(str));
--- orig.js 2026-10-17 16:03:56.669435151 -0400 +++ fixed.js 2026-10-17 16:03:56.955415828 -0400 @@ -1,5 +1,5 @@ let calls = 0; function getStr() { return `str${++calls}`; } const str = getStr(); -const re = /foo ${getStr()}bar/; +const re = /foo {2}${getStr()}bar/; console.log(calls, re.test(str));
1 false
r1-p003 · Comments Attached to Removed Code
1/** spaced */
2const re = /a /* inner */b /* end */c/;
3console.log(re.source);
--- orig.js 2026-10-17 16:03:57.127404225 -0400 +++ fixed.js 2026-10-17 16:03:57.400385818 -0400 @@ -1,3 +1,3 @@ /** spaced */ -const re = /a /* inner */b /* end */c/; +const re = /a {2}/* inner */b {2}/* end */c/; console.log(re.source);
r1-p004 · Strict vs Sloppy Mode Semantics
1"use strict";
2(() => {
3 const re = /\s \s/;
4 try {
5 eval("re.test(' ')");
6 console.log('ok');
7 } catch (e) {
8 console.log(e.name);
9 }
10})();
--- orig.js 2026-10-17 16:03:57.561374963 -0400 +++ fixed.js 2026-10-17 16:03:57.843355949 -0400 @@ -1,6 +1,6 @@ "use strict"; (() => { - const re = /\s \s/; + const re = /\s {2}\s/; try { eval("re.test(' ')"); console.log('ok');
ok
r1-p005 · Free-Spacing Mode (/x Flag) (generated program does not parse: /home/sun/dev/diaghunt/runs/2026-10-17T20-03-4)
1/** x-mode off */
2const re = /a b/x;
3console.log(re.test('a b'), re.source);
r1-p006 · Intentional Formatting for Readability
1const re = /^ (\d{3}) - (\d{2}) - (\d{4})$/;
2const m = '123-45-6789'.match(re);
3console.log(m ? m[1] + '-' + m[2] + '-' + m[3] : null);
--- orig.js 2026-10-17 16:03:58.060341327 -0400 +++ fixed.js 2026-10-17 16:03:58.344322218 -0400 @@ -1,3 +1,3 @@ -const re = /^ (\d{3}) - (\d{2}) - (\d{4})$/; +const re = /^ {2}(\d{3}) {2}- {2}(\d{2}) {2}- {2}(\d{4})$/; const m = '123-45-6789'.match(re); console.log(m ? m[1] + '-' + m[2] + '-' + m[3] : null);
null
r1-p007 · Arguments Object and Function Length
1function fn(a, b = /x y/.test('x y')) {
2 console.log(arguments.length, b);
3}
4fn(1);
--- orig.js 2026-10-17 16:03:58.516310645 -0400 +++ fixed.js 2026-10-17 16:03:58.774293285 -0400 @@ -1,4 +1,4 @@ -function fn(a, b = /x y/.test('x y')) { +function fn(a, b = /x {2}y/.test('x y')) { console.log(arguments.length, b); } fn(1);
1 true
r1-p008 · Source Inspection or Debugging Interference
1const re = /a b c/;
2console.log(re.source.split(' ').length - 1);
--- orig.js 2026-10-17 16:03:58.947281645 -0400 +++ fixed.js 2026-10-17 16:03:59.213263777 -0400 @@ -1,2 +1,2 @@ -const re = /a b c/; +const re = /a {2}b {2}c/; console.log(re.source.split(' ').length - 1);
4
r2-p001 · Getter/setter accessing modified regex source
1const obj = {};
2Object.defineProperty(obj, 'pattern', {
3 get() { return /hello world/.source; },
4 enumerable: true,
5 configurable: true
6});
7console.log('Match:', /hello world/.test(obj.pattern));
--- orig.js 2026-10-17 16:04:03.154000070 -0400 +++ fixed.js 2026-10-17 16:04:03.418982422 -0400 @@ -1,7 +1,7 @@ const obj = {}; Object.defineProperty(obj, 'pattern', { - get() { return /hello world/.source; }, + get() { return /hello {2}world/.source; }, enumerable: true, configurable: true }); -console.log('Match:', /hello world/.test(obj.pattern)); +console.log('Match:', /hello {2}world/.test(obj.pattern));
Match: true
r2-p002 · Var hoisting captures altered regex before redeclaration
1function test() {
2 if (false) var r = /a b/;
3 else var r = /c d/;
4 console.log(r.test('a b'));
5}
6test();
--- orig.js 2026-10-17 16:04:03.594970702 -0400 +++ fixed.js 2026-10-17 16:04:03.869952388 -0400 @@ -1,6 +1,6 @@ function test() { - if (false) var r = /a b/; - else var r = /c d/; + if (false) var r = /a {2}b/; + else var r = /c {2}d/; console.log(r.test('a b')); } test();
false
r2-p003 · Alternatives with delimiter spaces changed by fix
1const re = /foo | bar/;
2console.log(re.test('foo'));
3console.log(re.test(' bar'));
4console.log(re.test('foobar'));
--- orig.js 2026-10-17 16:04:04.042940874 -0400 +++ fixed.js 2026-10-17 16:04:04.305923395 -0400 @@ -1,4 +1,4 @@ -const re = /foo | bar/; +const re = /foo {2}| {2}bar/; console.log(re.test('foo')); console.log(re.test(' bar')); console.log(re.test('foobar'));
false true false
r2-p004 · Block-scoped function declaration uses altered regex
1{
2 function check(s) { return /test case/.test(s); }
3 console.log(check('test case'));
4 console.log(check('testcase'));
5}
--- orig.js 2026-10-17 16:04:04.481911699 -0400 +++ fixed.js 2026-10-17 16:04:04.752893690 -0400 @@ -1,5 +1,5 @@ { - function check(s) { return /test case/.test(s); } + function check(s) { return /test {2}case/.test(s); } console.log(check('test case')); console.log(check('testcase')); }
true false
r2-p005 · ASI hazard: regex on next line interpreted as division
1let x = 1
2(/space /).test('space ') ? console.log('matched') : console.log('not')
--- orig.js 2026-10-17 16:04:04.928881994 -0400 +++ fixed.js 2026-10-17 16:04:05.208863414 -0400 @@ -1,2 +1,2 @@ let x = 1 -(/space /).test('space ') ? console.log('matched') : console.log('not') +(/space {2}/).test('space ') ? console.log('matched') : console.log('not')
r2-p006 · Exception thrown during regex literal creation
1try {
2 const re = /fail here/;
3 throw new Error('deliberate');
4} catch (e) {
5 console.log('Caught:', e.constructor.name, e.message);
6}
--- orig.js 2026-10-17 16:04:05.371852604 -0400 +++ fixed.js 2026-10-17 16:04:05.641834698 -0400 @@ -1,5 +1,5 @@ try { - const re = /fail here/; + const re = /fail {2}here/; throw new Error('deliberate'); } catch (e) { console.log('Caught:', e.constructor.name, e.message);
Caught: Error deliberate
r2-p007 · Tab-separated value parser broken by space collapse
1const line = 'a\tb\tc';
2const re = /\t \t/;
3console.log('TSV match:', re.test(line));
4console.log('Expected fields:', line.split('\t'));
--- orig.js 2026-10-17 16:04:05.805823822 -0400 +++ fixed.js 2026-10-17 16:04:06.068806389 -0400 @@ -1,4 +1,4 @@ const line = 'a\tb\tc'; -const re = /\t \t/; +const re = /\t {2}\t/; console.log('TSV match:', re.test(line)); console.log('Expected fields:', line.split('\t'));
TSV match: false Expected fields: [ 'a', 'b', 'c' ]
r2-p008 · Unicode flag and lastIndex altered by fix
1const re = /💩 💩/gu;
2console.log('First:', re.test('💩 💩'));
3console.log('lastIndex after:', re.lastIndex);
4re.lastIndex = 0;
5console.log('Second:', re.test('💩💩'));
--- orig.js 2026-10-17 16:04:06.229795734 -0400 +++ fixed.js 2026-10-17 16:04:06.504777534 -0400 @@ -1,4 +1,4 @@ -const re = /💩 💩/gu; +const re = /💩 {2}💩/gu; console.log('First:', re.test('💩 💩')); console.log('lastIndex after:', re.lastIndex); re.lastIndex = 0;
First: true lastIndex after: 6 Second: false