Skip to content
This repository was archived by the owner on Apr 14, 2021. It is now read-only.

Commit aa62d07

Browse files
author
Mike Lewis
committed
Rewrite createRegExpRestore for shorter expressions
Previously createRegExpRestore build a regex with the exact characters for each part of the expression (either capturing group, or segment between capturing groups). The new solution begins with this approach, and then shortens the resulting expression by replacing each segment with a match for character count only, then setting lastIndex on the resultant RegExp to ensure that the match occurs at the expected location in the input.
1 parent b5d5bf7 commit aa62d07

File tree

3 files changed

+60
-33
lines changed

3 files changed

+60
-33
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@
7272
"build:dist": "npm run build:dist:dev && npm run build:dist:prod",
7373
"build": "npm run build:data && npm run build:lib && npm run build:dist",
7474
"lint": "eslint .",
75-
"test": "cd tests && node polyfilling.js && node sanity.js && node noderunner.js && node saucelabs.js && node disableregexprestore.js",
75+
"test": "cd tests && node polyfilling.js && node sanity.js && node disableregexprestore.js && node noderunner.js && node saucelabs.js",
7676
"pretest": "npm run lint",
7777
"preversion": "npm run clean && npm run build && npm run test",
7878
"prepublish": "npm run clean && npm run build"

src/util.js

+51-32
Original file line numberDiff line numberDiff line change
@@ -133,49 +133,68 @@ List.prototype = objCreate(null);
133133
*/
134134
export function createRegExpRestore () {
135135
if (internals.disableRegExpRestore) {
136-
return { exp: /a/, input: 'a' };
136+
return function() { /* no-op */ };
137137
}
138138

139-
let esc = /[.?*+^$[\]\\(){}|-]/g,
140-
lm = RegExp.lastMatch || '',
141-
ml = RegExp.multiline ? 'm' : '',
142-
ret = { input: RegExp.input },
143-
reg = new List(),
144-
has = false,
145-
cap = {};
139+
let regExpCache = {
140+
lastMatch: RegExp.lastMatch || '',
141+
leftContext: RegExp.leftContext,
142+
multiline: RegExp.multiline,
143+
input: RegExp.input
144+
},
145+
has = false;
146146

147147
// Create a snapshot of all the 'captured' properties
148148
for (let i = 1; i <= 9; i++)
149-
has = (cap['$'+i] = RegExp['$'+i]) || has;
150-
151-
// Now we've snapshotted some properties, escape the lastMatch string
152-
lm = lm.replace(esc, '\\$&');
149+
has = (regExpCache['$'+i] = RegExp['$'+i]) || has;
150+
151+
return function() {
152+
// Now we've snapshotted some properties, escape the lastMatch string
153+
let esc = /[.?*+^$[\]\\(){}|-]/g,
154+
lm = regExpCache.lastMatch.replace(esc, '\\$&'),
155+
reg = new List();
156+
157+
// If any of the captured strings were non-empty, iterate over them all
158+
if (has) {
159+
for (let i = 1; i <= 9; i++) {
160+
let m = regExpCache['$'+i];
161+
162+
// If it's empty, add an empty capturing group
163+
if (!m)
164+
lm = '()' + lm;
165+
166+
// Else find the string in lm and escape & wrap it to capture it
167+
else {
168+
m = m.replace(esc, '\\$&');
169+
lm = lm.replace(m, '(' + m + ')');
170+
}
153171

154-
// If any of the captured strings were non-empty, iterate over them all
155-
if (has) {
156-
for (let i = 1; i <= 9; i++) {
157-
let m = cap['$'+i];
172+
// Push it to the reg and chop lm to make sure further groups come after
173+
arrPush.call(reg, lm.slice(0, lm.indexOf('(') + 1));
174+
lm = lm.slice(lm.indexOf('(') + 1);
175+
}
176+
}
158177

159-
// If it's empty, add an empty capturing group
160-
if (!m)
161-
lm = '()' + lm;
178+
let exprStr = arrJoin.call(reg, '') + lm;
162179

163-
// Else find the string in lm and escape & wrap it to capture it
164-
else {
165-
m = m.replace(esc, '\\$&');
166-
lm = lm.replace(m, '(' + m + ')');
167-
}
180+
// Shorten the regex by replacing each part of the expression with a match
181+
// for a string of that exact length. This is safe for the type of
182+
// expressions generated above, because the expression matches the whole
183+
// match string, so we know each group and each segment between capturing
184+
// groups can be matched by its length alone.
185+
exprStr = exprStr.replace(/(\\\(|\\\)|[^()])+/g, (match) => {
186+
return `[\\s\\S]{${match.replace('\\','').length}}`;
187+
});
168188

169-
// Push it to the reg and chop lm to make sure further groups come after
170-
arrPush.call(reg, lm.slice(0, lm.indexOf('(') + 1));
171-
lm = lm.slice(lm.indexOf('(') + 1);
172-
}
173-
}
189+
// Create the regular expression that will reconstruct the RegExp properties
190+
let expr = new RegExp(exprStr, regExpCache.multiline ? 'gm' : 'g');
174191

175-
// Create the regular expression that will reconstruct the RegExp properties
176-
ret.exp = new RegExp(arrJoin.call(reg, '') + lm, ml);
192+
// Set the lastIndex of the generated expression to ensure that the match
193+
// is found in the correct index.
194+
expr.lastIndex = regExpCache.leftContext.length;
177195

178-
return ret;
196+
expr.exec(regExpCache.input);
197+
};
179198
}
180199

181200
/**

tests/sanity.js

+8
Original file line numberDiff line numberDiff line change
@@ -57,3 +57,11 @@ assert(new IntlPolyfill.DateTimeFormat('en', {
5757
assert(new IntlPolyfill.DateTimeFormat('en-us', {
5858
month:'long',
5959
}).format(new Date(2016, 0, 1)), 'January', 'month should be long');
60+
61+
// issue #196
62+
(new Array(32768 + 1)).join('a').match(/^a*$/);
63+
assert(new IntlPolyfill.NumberFormat('de-DE', {
64+
minimumFractionDigits: 2,
65+
maximumFractionDigits: 2
66+
}).format(0.015), "0,02", 'RegExp too big warning');
67+
'a'.match(/a/);

0 commit comments

Comments
 (0)