@@ -110,6 +110,11 @@ class JS extends Minify
110
110
*/
111
111
protected $ operatorsAfter = array ();
112
112
113
+ /**
114
+ * @var array
115
+ */
116
+ protected $ nestedExtracted = array ();
117
+
113
118
/**
114
119
* {@inheritdoc}
115
120
*/
@@ -216,30 +221,101 @@ protected function extractRegex()
216
221
$ minifier = $ this ;
217
222
$ callback = function ($ match ) use ($ minifier ) {
218
223
$ count = count ($ minifier ->extracted );
219
- $ placeholder = '/ ' .$ count .'/ ' ;
220
- $ minifier ->extracted [$ placeholder ] = $ match [0 ];
224
+ $ placeholder = '" ' .$ count .'" ' ;
225
+ $ minifier ->extracted [$ placeholder ] = $ match ['regex ' ];
226
+
227
+ // because we're also trying to find regular expressions that follow
228
+ // if/when/for statements, we should also make sure that the content
229
+ // within these statements is also minified...
230
+ // e.g. `if("some string"/* or comment */)` should become
231
+ // `if("some string")`
232
+ if (isset ($ match ['before ' ])) {
233
+ $ other = new static ();
234
+ $ other ->extractStrings ('\'"` ' , "$ count- " );
235
+ $ other ->stripComments ();
236
+ $ match ['before ' ] = $ other ->replace ($ match ['before ' ]);
237
+ $ this ->nestedExtracted += $ other ->extracted ;
238
+ }
221
239
222
- return $ placeholder ;
240
+ return (isset ($ match ['before ' ]) ? $ match ['before ' ] : '' ).
241
+ $ placeholder .
242
+ (isset ($ match ['after ' ]) ? $ match ['after ' ] : '' );
223
243
};
224
244
225
- $ pattern = '\/.*?(?<! \\\\)( \\\\\\\\)*\/[gimy]*(?![0-9a-zA-Z\/]) ' ;
245
+ $ pattern = '(?P<regex> \/.*?(?<! \\\\)( \\\\\\\\)*\/[gimy]*) (?![0-9a-zA-Z\/]) ' ;
226
246
227
247
// a regular expression can only be followed by a few operators or some
228
248
// of the RegExp methods (a `\` followed by a variable or value is
229
249
// likely part of a division, not a regex)
230
- $ keywords = $ this ->getKeywordsForRegex ($ this ->keywordsReserved , '/ ' );
231
- $ before = '([=:,;\)\}\(\{]|^| ' .implode ('| ' , $ keywords ).')\s* ' ;
232
- $ after = '[\.,;\)\}] ' ;
233
- $ methods = '\.(exec|test|match|search|replace|split)\( ' ;
234
- $ this ->registerPattern ('/ ' .$ before .'\K ' .$ pattern .'(?=\s*( ' .$ after .'| ' .$ methods .'))/ ' , $ callback );
250
+ $ keywords = array ('do ' , 'in ' , 'new ' , 'else ' , 'throw ' , 'yield ' , 'delete ' , 'return ' , 'typeof ' );
251
+ $ before = '(?P<before>[=:,;\}\(\{&\|]|^| ' .implode ('| ' , $ keywords ).') ' ;
252
+ $ propertiesAndMethods = array (
253
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#Properties
254
+ 'prototype ' ,
255
+ 'length ' ,
256
+ 'lastIndex ' ,
257
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#Properties_2
258
+ 'constructor ' ,
259
+ 'flags ' ,
260
+ 'global ' ,
261
+ 'ignoreCase ' ,
262
+ 'multiline ' ,
263
+ 'source ' ,
264
+ 'sticky ' ,
265
+ 'unicode ' ,
266
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#Methods_2
267
+ 'compile( ' ,
268
+ 'exec( ' ,
269
+ 'test( ' ,
270
+ 'match ' ,
271
+ 'replace( ' ,
272
+ 'search( ' ,
273
+ 'split( ' ,
274
+ 'toSource( ' ,
275
+ 'toString( ' ,
276
+ );
277
+ $ delimiters = array_fill (0 , count ($ propertiesAndMethods ), '/ ' );
278
+ $ propertiesAndMethods = array_map ('preg_quote ' , $ propertiesAndMethods , $ delimiters );
279
+ $ after = '(?P<after>[\.,;\)\}&\|+]|$|\.( ' .implode ('| ' , $ propertiesAndMethods ).')) ' ;
280
+ $ this ->registerPattern ('/ ' .$ before .'\s* ' .$ pattern .'\s* ' .$ after .'/ ' , $ callback );
281
+
282
+ // we didn't check for regular expressions after `)`, because that is
283
+ // more often than not not a character where a regex can follow (e.g.
284
+ // (1+2)/3/4 -> /3/ could be considered a regex, but it's not)
285
+ // however, after single-line if/while/for, there could very well be a
286
+ // regex after `)` (e.g. if(true)/regex/)
287
+ // there is one problem, though: it's (near) impossible to check for
288
+ // when the if/while/for statement is closed (same amount of closing
289
+ // brackets as there were opened), so I'll ignore single-line statements
290
+ // with nested brackets followed by a regex for now...
291
+ $ before = '(?P<before>\b(if|while|for)\s*\((?P<code>[^\(]+?)\)) ' ;
292
+ $ this ->registerPattern ('/ ' .$ before .'\s* ' .$ pattern .'\s* ' .$ after .'/ ' , $ callback );
235
293
236
294
// 1 more edge case: a regex can be followed by a lot more operators or
237
295
// keywords if there's a newline (ASI) in between, where the operator
238
296
// actually starts a new statement
239
297
// (https://github.com/matthiasmullie/minify/issues/56)
240
298
$ operators = $ this ->getOperatorsForRegex ($ this ->operatorsBefore , '/ ' );
241
299
$ operators += $ this ->getOperatorsForRegex ($ this ->keywordsReserved , '/ ' );
242
- $ this ->registerPattern ('/ ' .$ pattern .'\s*\n(?=\s*( ' .implode ('| ' , $ operators ).'))/ ' , $ callback );
300
+ $ after = '(?P<after>\n\s*( ' .implode ('| ' , $ operators ).')) ' ;
301
+ $ this ->registerPattern ('/ ' .$ pattern .'\s* ' .$ after .'/ ' , $ callback );
302
+ }
303
+
304
+ /**
305
+ * In addition to the regular restore routine, we also need to restore a few
306
+ * more things that have been extracted as part of the regex extraction...
307
+ *
308
+ * {@inheritdoc}
309
+ */
310
+ protected function restoreExtractedData ($ content )
311
+ {
312
+ // restore regular extracted stuff
313
+ $ content = parent ::restoreExtractedData ($ content );
314
+
315
+ // restore nested stuff from within regex extraction
316
+ $ content = strtr ($ content , $ this ->nestedExtracted );
317
+
318
+ return $ content ;
243
319
}
244
320
245
321
/**
0 commit comments