These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * JavaScript minifier |
||
4 | * |
||
5 | * Please report bugs on https://github.com/matthiasmullie/minify/issues |
||
6 | * |
||
7 | * @author Matthias Mullie <[email protected]> |
||
8 | * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved |
||
9 | * @license MIT License |
||
10 | */ |
||
11 | namespace MatthiasMullie\Minify; |
||
12 | |||
13 | /** |
||
14 | * JavaScript Minifier Class |
||
15 | * |
||
16 | * Please report bugs on https://github.com/matthiasmullie/minify/issues |
||
17 | * |
||
18 | * @package Minify |
||
19 | * @author Matthias Mullie <[email protected]> |
||
20 | * @author Tijs Verkoyen <[email protected]> |
||
21 | * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved |
||
22 | * @license MIT License |
||
23 | */ |
||
24 | class JS extends Minify |
||
25 | { |
||
26 | /** |
||
27 | * Var-matching regex based on http://stackoverflow.com/a/9337047/802993. |
||
28 | * |
||
29 | * Note that regular expressions using that bit must have the PCRE_UTF8 |
||
30 | * pattern modifier (/u) set. |
||
31 | * |
||
32 | * @var string |
||
33 | */ |
||
34 | const REGEX_VARIABLE = '\b[$A-Z\_a-z\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\x{02c1}\x{02c6}-\x{02d1}\x{02e0}-\x{02e4}\x{02ec}\x{02ee}\x{0370}-\x{0374}\x{0376}\x{0377}\x{037a}-\x{037d}\x{0386}\x{0388}-\x{038a}\x{038c}\x{038e}-\x{03a1}\x{03a3}-\x{03f5}\x{03f7}-\x{0481}\x{048a}-\x{0527}\x{0531}-\x{0556}\x{0559}\x{0561}-\x{0587}\x{05d0}-\x{05ea}\x{05f0}-\x{05f2}\x{0620}-\x{064a}\x{066e}\x{066f}\x{0671}-\x{06d3}\x{06d5}\x{06e5}\x{06e6}\x{06ee}\x{06ef}\x{06fa}-\x{06fc}\x{06ff}\x{0710}\x{0712}-\x{072f}\x{074d}-\x{07a5}\x{07b1}\x{07ca}-\x{07ea}\x{07f4}\x{07f5}\x{07fa}\x{0800}-\x{0815}\x{081a}\x{0824}\x{0828}\x{0840}-\x{0858}\x{08a0}\x{08a2}-\x{08ac}\x{0904}-\x{0939}\x{093d}\x{0950}\x{0958}-\x{0961}\x{0971}-\x{0977}\x{0979}-\x{097f}\x{0985}-\x{098c}\x{098f}\x{0990}\x{0993}-\x{09a8}\x{09aa}-\x{09b0}\x{09b2}\x{09b6}-\x{09b9}\x{09bd}\x{09ce}\x{09dc}\x{09dd}\x{09df}-\x{09e1}\x{09f0}\x{09f1}\x{0a05}-\x{0a0a}\x{0a0f}\x{0a10}\x{0a13}-\x{0a28}\x{0a2a}-\x{0a30}\x{0a32}\x{0a33}\x{0a35}\x{0a36}\x{0a38}\x{0a39}\x{0a59}-\x{0a5c}\x{0a5e}\x{0a72}-\x{0a74}\x{0a85}-\x{0a8d}\x{0a8f}-\x{0a91}\x{0a93}-\x{0aa8}\x{0aaa}-\x{0ab0}\x{0ab2}\x{0ab3}\x{0ab5}-\x{0ab9}\x{0abd}\x{0ad0}\x{0ae0}\x{0ae1}\x{0b05}-\x{0b0c}\x{0b0f}\x{0b10}\x{0b13}-\x{0b28}\x{0b2a}-\x{0b30}\x{0b32}\x{0b33}\x{0b35}-\x{0b39}\x{0b3d}\x{0b5c}\x{0b5d}\x{0b5f}-\x{0b61}\x{0b71}\x{0b83}\x{0b85}-\x{0b8a}\x{0b8e}-\x{0b90}\x{0b92}-\x{0b95}\x{0b99}\x{0b9a}\x{0b9c}\x{0b9e}\x{0b9f}\x{0ba3}\x{0ba4}\x{0ba8}-\x{0baa}\x{0bae}-\x{0bb9}\x{0bd0}\x{0c05}-\x{0c0c}\x{0c0e}-\x{0c10}\x{0c12}-\x{0c28}\x{0c2a}-\x{0c33}\x{0c35}-\x{0c39}\x{0c3d}\x{0c58}\x{0c59}\x{0c60}\x{0c61}\x{0c85}-\x{0c8c}\x{0c8e}-\x{0c90}\x{0c92}-\x{0ca8}\x{0caa}-\x{0cb3}\x{0cb5}-\x{0cb9}\x{0cbd}\x{0cde}\x{0ce0}\x{0ce1}\x{0cf1}\x{0cf2}\x{0d05}-\x{0d0c}\x{0d0e}-\x{0d10}\x{0d12}-\x{0d3a}\x{0d3d}\x{0d4e}\x{0d60}\x{0d61}\x{0d7a}-\x{0d7f}\x{0d85}-\x{0d96}\x{0d9a}-\x{0db1}\x{0db3}-\x{0dbb}\x{0dbd}\x{0dc0}-\x{0dc6}\x{0e01}-\x{0e30}\x{0e32}\x{0e33}\x{0e40}-\x{0e46}\x{0e81}\x{0e82}\x{0e84}\x{0e87}\x{0e88}\x{0e8a}\x{0e8d}\x{0e94}-\x{0e97}\x{0e99}-\x{0e9f}\x{0ea1}-\x{0ea3}\x{0ea5}\x{0ea7}\x{0eaa}\x{0eab}\x{0ead}-\x{0eb0}\x{0eb2}\x{0eb3}\x{0ebd}\x{0ec0}-\x{0ec4}\x{0ec6}\x{0edc}-\x{0edf}\x{0f00}\x{0f40}-\x{0f47}\x{0f49}-\x{0f6c}\x{0f88}-\x{0f8c}\x{1000}-\x{102a}\x{103f}\x{1050}-\x{1055}\x{105a}-\x{105d}\x{1061}\x{1065}\x{1066}\x{106e}-\x{1070}\x{1075}-\x{1081}\x{108e}\x{10a0}-\x{10c5}\x{10c7}\x{10cd}\x{10d0}-\x{10fa}\x{10fc}-\x{1248}\x{124a}-\x{124d}\x{1250}-\x{1256}\x{1258}\x{125a}-\x{125d}\x{1260}-\x{1288}\x{128a}-\x{128d}\x{1290}-\x{12b0}\x{12b2}-\x{12b5}\x{12b8}-\x{12be}\x{12c0}\x{12c2}-\x{12c5}\x{12c8}-\x{12d6}\x{12d8}-\x{1310}\x{1312}-\x{1315}\x{1318}-\x{135a}\x{1380}-\x{138f}\x{13a0}-\x{13f4}\x{1401}-\x{166c}\x{166f}-\x{167f}\x{1681}-\x{169a}\x{16a0}-\x{16ea}\x{16ee}-\x{16f0}\x{1700}-\x{170c}\x{170e}-\x{1711}\x{1720}-\x{1731}\x{1740}-\x{1751}\x{1760}-\x{176c}\x{176e}-\x{1770}\x{1780}-\x{17b3}\x{17d7}\x{17dc}\x{1820}-\x{1877}\x{1880}-\x{18a8}\x{18aa}\x{18b0}-\x{18f5}\x{1900}-\x{191c}\x{1950}-\x{196d}\x{1970}-\x{1974}\x{1980}-\x{19ab}\x{19c1}-\x{19c7}\x{1a00}-\x{1a16}\x{1a20}-\x{1a54}\x{1aa7}\x{1b05}-\x{1b33}\x{1b45}-\x{1b4b}\x{1b83}-\x{1ba0}\x{1bae}\x{1baf}\x{1bba}-\x{1be5}\x{1c00}-\x{1c23}\x{1c4d}-\x{1c4f}\x{1c5a}-\x{1c7d}\x{1ce9}-\x{1cec}\x{1cee}-\x{1cf1}\x{1cf5}\x{1cf6}\x{1d00}-\x{1dbf}\x{1e00}-\x{1f15}\x{1f18}-\x{1f1d}\x{1f20}-\x{1f45}\x{1f48}-\x{1f4d}\x{1f50}-\x{1f57}\x{1f59}\x{1f5b}\x{1f5d}\x{1f5f}-\x{1f7d}\x{1f80}-\x{1fb4}\x{1fb6}-\x{1fbc}\x{1fbe}\x{1fc2}-\x{1fc4}\x{1fc6}-\x{1fcc}\x{1fd0}-\x{1fd3}\x{1fd6}-\x{1fdb}\x{1fe0}-\x{1fec}\x{1ff2}-\x{1ff4}\x{1ff6}-\x{1ffc}\x{2071}\x{207f}\x{2090}-\x{209c}\x{2102}\x{2107}\x{210a}-\x{2113}\x{2115}\x{2119}-\x{211d}\x{2124}\x{2126}\x{2128}\x{212a}-\x{212d}\x{212f}-\x{2139}\x{213c}-\x{213f}\x{2145}-\x{2149}\x{214e}\x{2160}-\x{2188}\x{2c00}-\x{2c2e}\x{2c30}-\x{2c5e}\x{2c60}-\x{2ce4}\x{2ceb}-\x{2cee}\x{2cf2}\x{2cf3}\x{2d00}-\x{2d25}\x{2d27}\x{2d2d}\x{2d30}-\x{2d67}\x{2d6f}\x{2d80}-\x{2d96}\x{2da0}-\x{2da6}\x{2da8}-\x{2dae}\x{2db0}-\x{2db6}\x{2db8}-\x{2dbe}\x{2dc0}-\x{2dc6}\x{2dc8}-\x{2dce}\x{2dd0}-\x{2dd6}\x{2dd8}-\x{2dde}\x{2e2f}\x{3005}-\x{3007}\x{3021}-\x{3029}\x{3031}-\x{3035}\x{3038}-\x{303c}\x{3041}-\x{3096}\x{309d}-\x{309f}\x{30a1}-\x{30fa}\x{30fc}-\x{30ff}\x{3105}-\x{312d}\x{3131}-\x{318e}\x{31a0}-\x{31ba}\x{31f0}-\x{31ff}\x{3400}-\x{4db5}\x{4e00}-\x{9fcc}\x{a000}-\x{a48c}\x{a4d0}-\x{a4fd}\x{a500}-\x{a60c}\x{a610}-\x{a61f}\x{a62a}\x{a62b}\x{a640}-\x{a66e}\x{a67f}-\x{a697}\x{a6a0}-\x{a6ef}\x{a717}-\x{a71f}\x{a722}-\x{a788}\x{a78b}-\x{a78e}\x{a790}-\x{a793}\x{a7a0}-\x{a7aa}\x{a7f8}-\x{a801}\x{a803}-\x{a805}\x{a807}-\x{a80a}\x{a80c}-\x{a822}\x{a840}-\x{a873}\x{a882}-\x{a8b3}\x{a8f2}-\x{a8f7}\x{a8fb}\x{a90a}-\x{a925}\x{a930}-\x{a946}\x{a960}-\x{a97c}\x{a984}-\x{a9b2}\x{a9cf}\x{aa00}-\x{aa28}\x{aa40}-\x{aa42}\x{aa44}-\x{aa4b}\x{aa60}-\x{aa76}\x{aa7a}\x{aa80}-\x{aaaf}\x{aab1}\x{aab5}\x{aab6}\x{aab9}-\x{aabd}\x{aac0}\x{aac2}\x{aadb}-\x{aadd}\x{aae0}-\x{aaea}\x{aaf2}-\x{aaf4}\x{ab01}-\x{ab06}\x{ab09}-\x{ab0e}\x{ab11}-\x{ab16}\x{ab20}-\x{ab26}\x{ab28}-\x{ab2e}\x{abc0}-\x{abe2}\x{ac00}-\x{d7a3}\x{d7b0}-\x{d7c6}\x{d7cb}-\x{d7fb}\x{f900}-\x{fa6d}\x{fa70}-\x{fad9}\x{fb00}-\x{fb06}\x{fb13}-\x{fb17}\x{fb1d}\x{fb1f}-\x{fb28}\x{fb2a}-\x{fb36}\x{fb38}-\x{fb3c}\x{fb3e}\x{fb40}\x{fb41}\x{fb43}\x{fb44}\x{fb46}-\x{fbb1}\x{fbd3}-\x{fd3d}\x{fd50}-\x{fd8f}\x{fd92}-\x{fdc7}\x{fdf0}-\x{fdfb}\x{fe70}-\x{fe74}\x{fe76}-\x{fefc}\x{ff21}-\x{ff3a}\x{ff41}-\x{ff5a}\x{ff66}-\x{ffbe}\x{ffc2}-\x{ffc7}\x{ffca}-\x{ffcf}\x{ffd2}-\x{ffd7}\x{ffda}-\x{ffdc}][$A-Z\_a-z\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\x{02c1}\x{02c6}-\x{02d1}\x{02e0}-\x{02e4}\x{02ec}\x{02ee}\x{0370}-\x{0374}\x{0376}\x{0377}\x{037a}-\x{037d}\x{0386}\x{0388}-\x{038a}\x{038c}\x{038e}-\x{03a1}\x{03a3}-\x{03f5}\x{03f7}-\x{0481}\x{048a}-\x{0527}\x{0531}-\x{0556}\x{0559}\x{0561}-\x{0587}\x{05d0}-\x{05ea}\x{05f0}-\x{05f2}\x{0620}-\x{064a}\x{066e}\x{066f}\x{0671}-\x{06d3}\x{06d5}\x{06e5}\x{06e6}\x{06ee}\x{06ef}\x{06fa}-\x{06fc}\x{06ff}\x{0710}\x{0712}-\x{072f}\x{074d}-\x{07a5}\x{07b1}\x{07ca}-\x{07ea}\x{07f4}\x{07f5}\x{07fa}\x{0800}-\x{0815}\x{081a}\x{0824}\x{0828}\x{0840}-\x{0858}\x{08a0}\x{08a2}-\x{08ac}\x{0904}-\x{0939}\x{093d}\x{0950}\x{0958}-\x{0961}\x{0971}-\x{0977}\x{0979}-\x{097f}\x{0985}-\x{098c}\x{098f}\x{0990}\x{0993}-\x{09a8}\x{09aa}-\x{09b0}\x{09b2}\x{09b6}-\x{09b9}\x{09bd}\x{09ce}\x{09dc}\x{09dd}\x{09df}-\x{09e1}\x{09f0}\x{09f1}\x{0a05}-\x{0a0a}\x{0a0f}\x{0a10}\x{0a13}-\x{0a28}\x{0a2a}-\x{0a30}\x{0a32}\x{0a33}\x{0a35}\x{0a36}\x{0a38}\x{0a39}\x{0a59}-\x{0a5c}\x{0a5e}\x{0a72}-\x{0a74}\x{0a85}-\x{0a8d}\x{0a8f}-\x{0a91}\x{0a93}-\x{0aa8}\x{0aaa}-\x{0ab0}\x{0ab2}\x{0ab3}\x{0ab5}-\x{0ab9}\x{0abd}\x{0ad0}\x{0ae0}\x{0ae1}\x{0b05}-\x{0b0c}\x{0b0f}\x{0b10}\x{0b13}-\x{0b28}\x{0b2a}-\x{0b30}\x{0b32}\x{0b33}\x{0b35}-\x{0b39}\x{0b3d}\x{0b5c}\x{0b5d}\x{0b5f}-\x{0b61}\x{0b71}\x{0b83}\x{0b85}-\x{0b8a}\x{0b8e}-\x{0b90}\x{0b92}-\x{0b95}\x{0b99}\x{0b9a}\x{0b9c}\x{0b9e}\x{0b9f}\x{0ba3}\x{0ba4}\x{0ba8}-\x{0baa}\x{0bae}-\x{0bb9}\x{0bd0}\x{0c05}-\x{0c0c}\x{0c0e}-\x{0c10}\x{0c12}-\x{0c28}\x{0c2a}-\x{0c33}\x{0c35}-\x{0c39}\x{0c3d}\x{0c58}\x{0c59}\x{0c60}\x{0c61}\x{0c85}-\x{0c8c}\x{0c8e}-\x{0c90}\x{0c92}-\x{0ca8}\x{0caa}-\x{0cb3}\x{0cb5}-\x{0cb9}\x{0cbd}\x{0cde}\x{0ce0}\x{0ce1}\x{0cf1}\x{0cf2}\x{0d05}-\x{0d0c}\x{0d0e}-\x{0d10}\x{0d12}-\x{0d3a}\x{0d3d}\x{0d4e}\x{0d60}\x{0d61}\x{0d7a}-\x{0d7f}\x{0d85}-\x{0d96}\x{0d9a}-\x{0db1}\x{0db3}-\x{0dbb}\x{0dbd}\x{0dc0}-\x{0dc6}\x{0e01}-\x{0e30}\x{0e32}\x{0e33}\x{0e40}-\x{0e46}\x{0e81}\x{0e82}\x{0e84}\x{0e87}\x{0e88}\x{0e8a}\x{0e8d}\x{0e94}-\x{0e97}\x{0e99}-\x{0e9f}\x{0ea1}-\x{0ea3}\x{0ea5}\x{0ea7}\x{0eaa}\x{0eab}\x{0ead}-\x{0eb0}\x{0eb2}\x{0eb3}\x{0ebd}\x{0ec0}-\x{0ec4}\x{0ec6}\x{0edc}-\x{0edf}\x{0f00}\x{0f40}-\x{0f47}\x{0f49}-\x{0f6c}\x{0f88}-\x{0f8c}\x{1000}-\x{102a}\x{103f}\x{1050}-\x{1055}\x{105a}-\x{105d}\x{1061}\x{1065}\x{1066}\x{106e}-\x{1070}\x{1075}-\x{1081}\x{108e}\x{10a0}-\x{10c5}\x{10c7}\x{10cd}\x{10d0}-\x{10fa}\x{10fc}-\x{1248}\x{124a}-\x{124d}\x{1250}-\x{1256}\x{1258}\x{125a}-\x{125d}\x{1260}-\x{1288}\x{128a}-\x{128d}\x{1290}-\x{12b0}\x{12b2}-\x{12b5}\x{12b8}-\x{12be}\x{12c0}\x{12c2}-\x{12c5}\x{12c8}-\x{12d6}\x{12d8}-\x{1310}\x{1312}-\x{1315}\x{1318}-\x{135a}\x{1380}-\x{138f}\x{13a0}-\x{13f4}\x{1401}-\x{166c}\x{166f}-\x{167f}\x{1681}-\x{169a}\x{16a0}-\x{16ea}\x{16ee}-\x{16f0}\x{1700}-\x{170c}\x{170e}-\x{1711}\x{1720}-\x{1731}\x{1740}-\x{1751}\x{1760}-\x{176c}\x{176e}-\x{1770}\x{1780}-\x{17b3}\x{17d7}\x{17dc}\x{1820}-\x{1877}\x{1880}-\x{18a8}\x{18aa}\x{18b0}-\x{18f5}\x{1900}-\x{191c}\x{1950}-\x{196d}\x{1970}-\x{1974}\x{1980}-\x{19ab}\x{19c1}-\x{19c7}\x{1a00}-\x{1a16}\x{1a20}-\x{1a54}\x{1aa7}\x{1b05}-\x{1b33}\x{1b45}-\x{1b4b}\x{1b83}-\x{1ba0}\x{1bae}\x{1baf}\x{1bba}-\x{1be5}\x{1c00}-\x{1c23}\x{1c4d}-\x{1c4f}\x{1c5a}-\x{1c7d}\x{1ce9}-\x{1cec}\x{1cee}-\x{1cf1}\x{1cf5}\x{1cf6}\x{1d00}-\x{1dbf}\x{1e00}-\x{1f15}\x{1f18}-\x{1f1d}\x{1f20}-\x{1f45}\x{1f48}-\x{1f4d}\x{1f50}-\x{1f57}\x{1f59}\x{1f5b}\x{1f5d}\x{1f5f}-\x{1f7d}\x{1f80}-\x{1fb4}\x{1fb6}-\x{1fbc}\x{1fbe}\x{1fc2}-\x{1fc4}\x{1fc6}-\x{1fcc}\x{1fd0}-\x{1fd3}\x{1fd6}-\x{1fdb}\x{1fe0}-\x{1fec}\x{1ff2}-\x{1ff4}\x{1ff6}-\x{1ffc}\x{2071}\x{207f}\x{2090}-\x{209c}\x{2102}\x{2107}\x{210a}-\x{2113}\x{2115}\x{2119}-\x{211d}\x{2124}\x{2126}\x{2128}\x{212a}-\x{212d}\x{212f}-\x{2139}\x{213c}-\x{213f}\x{2145}-\x{2149}\x{214e}\x{2160}-\x{2188}\x{2c00}-\x{2c2e}\x{2c30}-\x{2c5e}\x{2c60}-\x{2ce4}\x{2ceb}-\x{2cee}\x{2cf2}\x{2cf3}\x{2d00}-\x{2d25}\x{2d27}\x{2d2d}\x{2d30}-\x{2d67}\x{2d6f}\x{2d80}-\x{2d96}\x{2da0}-\x{2da6}\x{2da8}-\x{2dae}\x{2db0}-\x{2db6}\x{2db8}-\x{2dbe}\x{2dc0}-\x{2dc6}\x{2dc8}-\x{2dce}\x{2dd0}-\x{2dd6}\x{2dd8}-\x{2dde}\x{2e2f}\x{3005}-\x{3007}\x{3021}-\x{3029}\x{3031}-\x{3035}\x{3038}-\x{303c}\x{3041}-\x{3096}\x{309d}-\x{309f}\x{30a1}-\x{30fa}\x{30fc}-\x{30ff}\x{3105}-\x{312d}\x{3131}-\x{318e}\x{31a0}-\x{31ba}\x{31f0}-\x{31ff}\x{3400}-\x{4db5}\x{4e00}-\x{9fcc}\x{a000}-\x{a48c}\x{a4d0}-\x{a4fd}\x{a500}-\x{a60c}\x{a610}-\x{a61f}\x{a62a}\x{a62b}\x{a640}-\x{a66e}\x{a67f}-\x{a697}\x{a6a0}-\x{a6ef}\x{a717}-\x{a71f}\x{a722}-\x{a788}\x{a78b}-\x{a78e}\x{a790}-\x{a793}\x{a7a0}-\x{a7aa}\x{a7f8}-\x{a801}\x{a803}-\x{a805}\x{a807}-\x{a80a}\x{a80c}-\x{a822}\x{a840}-\x{a873}\x{a882}-\x{a8b3}\x{a8f2}-\x{a8f7}\x{a8fb}\x{a90a}-\x{a925}\x{a930}-\x{a946}\x{a960}-\x{a97c}\x{a984}-\x{a9b2}\x{a9cf}\x{aa00}-\x{aa28}\x{aa40}-\x{aa42}\x{aa44}-\x{aa4b}\x{aa60}-\x{aa76}\x{aa7a}\x{aa80}-\x{aaaf}\x{aab1}\x{aab5}\x{aab6}\x{aab9}-\x{aabd}\x{aac0}\x{aac2}\x{aadb}-\x{aadd}\x{aae0}-\x{aaea}\x{aaf2}-\x{aaf4}\x{ab01}-\x{ab06}\x{ab09}-\x{ab0e}\x{ab11}-\x{ab16}\x{ab20}-\x{ab26}\x{ab28}-\x{ab2e}\x{abc0}-\x{abe2}\x{ac00}-\x{d7a3}\x{d7b0}-\x{d7c6}\x{d7cb}-\x{d7fb}\x{f900}-\x{fa6d}\x{fa70}-\x{fad9}\x{fb00}-\x{fb06}\x{fb13}-\x{fb17}\x{fb1d}\x{fb1f}-\x{fb28}\x{fb2a}-\x{fb36}\x{fb38}-\x{fb3c}\x{fb3e}\x{fb40}\x{fb41}\x{fb43}\x{fb44}\x{fb46}-\x{fbb1}\x{fbd3}-\x{fd3d}\x{fd50}-\x{fd8f}\x{fd92}-\x{fdc7}\x{fdf0}-\x{fdfb}\x{fe70}-\x{fe74}\x{fe76}-\x{fefc}\x{ff21}-\x{ff3a}\x{ff41}-\x{ff5a}\x{ff66}-\x{ffbe}\x{ffc2}-\x{ffc7}\x{ffca}-\x{ffcf}\x{ffd2}-\x{ffd7}\x{ffda}-\x{ffdc}0-9\x{0300}-\x{036f}\x{0483}-\x{0487}\x{0591}-\x{05bd}\x{05bf}\x{05c1}\x{05c2}\x{05c4}\x{05c5}\x{05c7}\x{0610}-\x{061a}\x{064b}-\x{0669}\x{0670}\x{06d6}-\x{06dc}\x{06df}-\x{06e4}\x{06e7}\x{06e8}\x{06ea}-\x{06ed}\x{06f0}-\x{06f9}\x{0711}\x{0730}-\x{074a}\x{07a6}-\x{07b0}\x{07c0}-\x{07c9}\x{07eb}-\x{07f3}\x{0816}-\x{0819}\x{081b}-\x{0823}\x{0825}-\x{0827}\x{0829}-\x{082d}\x{0859}-\x{085b}\x{08e4}-\x{08fe}\x{0900}-\x{0903}\x{093a}-\x{093c}\x{093e}-\x{094f}\x{0951}-\x{0957}\x{0962}\x{0963}\x{0966}-\x{096f}\x{0981}-\x{0983}\x{09bc}\x{09be}-\x{09c4}\x{09c7}\x{09c8}\x{09cb}-\x{09cd}\x{09d7}\x{09e2}\x{09e3}\x{09e6}-\x{09ef}\x{0a01}-\x{0a03}\x{0a3c}\x{0a3e}-\x{0a42}\x{0a47}\x{0a48}\x{0a4b}-\x{0a4d}\x{0a51}\x{0a66}-\x{0a71}\x{0a75}\x{0a81}-\x{0a83}\x{0abc}\x{0abe}-\x{0ac5}\x{0ac7}-\x{0ac9}\x{0acb}-\x{0acd}\x{0ae2}\x{0ae3}\x{0ae6}-\x{0aef}\x{0b01}-\x{0b03}\x{0b3c}\x{0b3e}-\x{0b44}\x{0b47}\x{0b48}\x{0b4b}-\x{0b4d}\x{0b56}\x{0b57}\x{0b62}\x{0b63}\x{0b66}-\x{0b6f}\x{0b82}\x{0bbe}-\x{0bc2}\x{0bc6}-\x{0bc8}\x{0bca}-\x{0bcd}\x{0bd7}\x{0be6}-\x{0bef}\x{0c01}-\x{0c03}\x{0c3e}-\x{0c44}\x{0c46}-\x{0c48}\x{0c4a}-\x{0c4d}\x{0c55}\x{0c56}\x{0c62}\x{0c63}\x{0c66}-\x{0c6f}\x{0c82}\x{0c83}\x{0cbc}\x{0cbe}-\x{0cc4}\x{0cc6}-\x{0cc8}\x{0cca}-\x{0ccd}\x{0cd5}\x{0cd6}\x{0ce2}\x{0ce3}\x{0ce6}-\x{0cef}\x{0d02}\x{0d03}\x{0d3e}-\x{0d44}\x{0d46}-\x{0d48}\x{0d4a}-\x{0d4d}\x{0d57}\x{0d62}\x{0d63}\x{0d66}-\x{0d6f}\x{0d82}\x{0d83}\x{0dca}\x{0dcf}-\x{0dd4}\x{0dd6}\x{0dd8}-\x{0ddf}\x{0df2}\x{0df3}\x{0e31}\x{0e34}-\x{0e3a}\x{0e47}-\x{0e4e}\x{0e50}-\x{0e59}\x{0eb1}\x{0eb4}-\x{0eb9}\x{0ebb}\x{0ebc}\x{0ec8}-\x{0ecd}\x{0ed0}-\x{0ed9}\x{0f18}\x{0f19}\x{0f20}-\x{0f29}\x{0f35}\x{0f37}\x{0f39}\x{0f3e}\x{0f3f}\x{0f71}-\x{0f84}\x{0f86}\x{0f87}\x{0f8d}-\x{0f97}\x{0f99}-\x{0fbc}\x{0fc6}\x{102b}-\x{103e}\x{1040}-\x{1049}\x{1056}-\x{1059}\x{105e}-\x{1060}\x{1062}-\x{1064}\x{1067}-\x{106d}\x{1071}-\x{1074}\x{1082}-\x{108d}\x{108f}-\x{109d}\x{135d}-\x{135f}\x{1712}-\x{1714}\x{1732}-\x{1734}\x{1752}\x{1753}\x{1772}\x{1773}\x{17b4}-\x{17d3}\x{17dd}\x{17e0}-\x{17e9}\x{180b}-\x{180d}\x{1810}-\x{1819}\x{18a9}\x{1920}-\x{192b}\x{1930}-\x{193b}\x{1946}-\x{194f}\x{19b0}-\x{19c0}\x{19c8}\x{19c9}\x{19d0}-\x{19d9}\x{1a17}-\x{1a1b}\x{1a55}-\x{1a5e}\x{1a60}-\x{1a7c}\x{1a7f}-\x{1a89}\x{1a90}-\x{1a99}\x{1b00}-\x{1b04}\x{1b34}-\x{1b44}\x{1b50}-\x{1b59}\x{1b6b}-\x{1b73}\x{1b80}-\x{1b82}\x{1ba1}-\x{1bad}\x{1bb0}-\x{1bb9}\x{1be6}-\x{1bf3}\x{1c24}-\x{1c37}\x{1c40}-\x{1c49}\x{1c50}-\x{1c59}\x{1cd0}-\x{1cd2}\x{1cd4}-\x{1ce8}\x{1ced}\x{1cf2}-\x{1cf4}\x{1dc0}-\x{1de6}\x{1dfc}-\x{1dff}\x{200c}\x{200d}\x{203f}\x{2040}\x{2054}\x{20d0}-\x{20dc}\x{20e1}\x{20e5}-\x{20f0}\x{2cef}-\x{2cf1}\x{2d7f}\x{2de0}-\x{2dff}\x{302a}-\x{302f}\x{3099}\x{309a}\x{a620}-\x{a629}\x{a66f}\x{a674}-\x{a67d}\x{a69f}\x{a6f0}\x{a6f1}\x{a802}\x{a806}\x{a80b}\x{a823}-\x{a827}\x{a880}\x{a881}\x{a8b4}-\x{a8c4}\x{a8d0}-\x{a8d9}\x{a8e0}-\x{a8f1}\x{a900}-\x{a909}\x{a926}-\x{a92d}\x{a947}-\x{a953}\x{a980}-\x{a983}\x{a9b3}-\x{a9c0}\x{a9d0}-\x{a9d9}\x{aa29}-\x{aa36}\x{aa43}\x{aa4c}\x{aa4d}\x{aa50}-\x{aa59}\x{aa7b}\x{aab0}\x{aab2}-\x{aab4}\x{aab7}\x{aab8}\x{aabe}\x{aabf}\x{aac1}\x{aaeb}-\x{aaef}\x{aaf5}\x{aaf6}\x{abe3}-\x{abea}\x{abec}\x{abed}\x{abf0}-\x{abf9}\x{fb1e}\x{fe00}-\x{fe0f}\x{fe20}-\x{fe26}\x{fe33}\x{fe34}\x{fe4d}-\x{fe4f}\x{ff10}-\x{ff19}\x{ff3f}]*\b'; |
||
35 | |||
36 | /** |
||
37 | * Full list of JavaScript reserved words. |
||
38 | * Will be loaded from /data/js/keywords_reserved.txt. |
||
39 | * |
||
40 | * @see https://mathiasbynens.be/notes/reserved-keywords |
||
41 | * |
||
42 | * @var string[] |
||
43 | */ |
||
44 | protected $keywordsReserved = array(); |
||
45 | |||
46 | /** |
||
47 | * List of JavaScript reserved words that accept a <variable, value, ...> |
||
48 | * after them. Some end of lines are not the end of a statement, like with |
||
49 | * these keywords. |
||
50 | * |
||
51 | * E.g.: we shouldn't insert a ; after this else |
||
52 | * else |
||
53 | * console.log('this is quite fine') |
||
54 | * |
||
55 | * Will be loaded from /data/js/keywords_before.txt |
||
56 | * |
||
57 | * @var string[] |
||
58 | */ |
||
59 | protected $keywordsBefore = array(); |
||
60 | |||
61 | /** |
||
62 | * List of JavaScript reserved words that accept a <variable, value, ...> |
||
63 | * before them. Some end of lines are not the end of a statement, like when |
||
64 | * continued by one of these keywords on the newline. |
||
65 | * |
||
66 | * E.g.: we shouldn't insert a ; before this instanceof |
||
67 | * variable |
||
68 | * instanceof String |
||
69 | * |
||
70 | * Will be loaded from /data/js/keywords_after.txt |
||
71 | * |
||
72 | * @var string[] |
||
73 | */ |
||
74 | protected $keywordsAfter = array(); |
||
75 | |||
76 | /** |
||
77 | * List of all JavaScript operators. |
||
78 | * |
||
79 | * Will be loaded from /data/js/operators.txt |
||
80 | * |
||
81 | * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators |
||
82 | * |
||
83 | * @var string[] |
||
84 | */ |
||
85 | protected $operators = array(); |
||
86 | |||
87 | /** |
||
88 | * List of JavaScript operators that accept a <variable, value, ...> after |
||
89 | * them. Some end of lines are not the end of a statement, like with these |
||
90 | * operators. |
||
91 | * |
||
92 | * Note: Most operators are fine, we've only removed ++ and --. |
||
93 | * ++ & -- have to be joined with the value they're in-/decrementing. |
||
94 | * |
||
95 | * Will be loaded from /data/js/operators_before.txt |
||
96 | * |
||
97 | * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators |
||
98 | * |
||
99 | * @var string[] |
||
100 | */ |
||
101 | protected $operatorsBefore = array(); |
||
102 | |||
103 | /** |
||
104 | * List of JavaScript operators that accept a <variable, value, ...> before |
||
105 | * them. Some end of lines are not the end of a statement, like when |
||
106 | * continued by one of these operators on the newline. |
||
107 | * |
||
108 | * Note: Most operators are fine, we've only removed ), ], ++, --, ! and ~. |
||
109 | * There can't be a newline separating ! or ~ and whatever it is negating. |
||
110 | * ++ & -- have to be joined with the value they're in-/decrementing. |
||
111 | * ) & ] are "special" in that they have lots or usecases. () for example |
||
112 | * is used for function calls, for grouping, in if () and for (), ... |
||
113 | * |
||
114 | * Will be loaded from /data/js/operators_after.txt |
||
115 | * |
||
116 | * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators |
||
117 | * |
||
118 | * @var string[] |
||
119 | */ |
||
120 | protected $operatorsAfter = array(); |
||
121 | |||
122 | /** |
||
123 | * {@inheritdoc} |
||
124 | */ |
||
125 | public function __construct() |
||
126 | { |
||
127 | call_user_func_array(array('parent', '__construct'), func_get_args()); |
||
128 | |||
129 | $dataDir = __DIR__.'/../data/js/'; |
||
130 | $options = FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES; |
||
131 | $this->keywordsReserved = file($dataDir.'keywords_reserved.txt', $options); |
||
132 | $this->keywordsBefore = file($dataDir.'keywords_before.txt', $options); |
||
133 | $this->keywordsAfter = file($dataDir.'keywords_after.txt', $options); |
||
134 | $this->operators = file($dataDir.'operators.txt', $options); |
||
135 | $this->operatorsBefore = file($dataDir.'operators_before.txt', $options); |
||
136 | $this->operatorsAfter = file($dataDir.'operators_after.txt', $options); |
||
137 | } |
||
138 | |||
139 | /** |
||
140 | * Minify the data. |
||
141 | * Perform JS optimizations. |
||
142 | * |
||
143 | * @param string[optional] $path Path to write the data to |
||
144 | * |
||
145 | * @return string The minified data |
||
146 | */ |
||
147 | public function execute($path = null) |
||
148 | { |
||
149 | $content = ''; |
||
150 | |||
151 | /* |
||
152 | * Let's first take out strings, comments and regular expressions. |
||
153 | * All of these can contain JS code-like characters, and we should make |
||
154 | * sure any further magic ignores anything inside of these. |
||
155 | * |
||
156 | * Consider this example, where we should not strip any whitespace: |
||
157 | * var str = "a test"; |
||
158 | * |
||
159 | * Comments will be removed altogether, strings and regular expressions |
||
160 | * will be replaced by placeholder text, which we'll restore later. |
||
161 | */ |
||
162 | $this->extractStrings('\'"`'); |
||
163 | $this->stripComments(); |
||
164 | $this->extractRegex(); |
||
165 | |||
166 | // loop files |
||
167 | foreach ($this->data as $source => $js) { |
||
168 | // take out strings, comments & regex (for which we've registered |
||
169 | // the regexes just a few lines earlier) |
||
170 | $js = $this->replace($js); |
||
171 | |||
172 | $js = $this->propertyNotation($js); |
||
173 | $js = $this->shortenBools($js); |
||
174 | $js = $this->stripWhitespace($js); |
||
175 | |||
176 | // combine js: separating the scripts by a ; |
||
177 | $content .= $js.";"; |
||
178 | } |
||
179 | |||
180 | // clean up leftover `;`s from the combination of multiple scripts |
||
181 | $content = ltrim($content, ';'); |
||
182 | $content = (string) substr($content, 0, -1); |
||
183 | |||
184 | /* |
||
185 | * Earlier, we extracted strings & regular expressions and replaced them |
||
186 | * with placeholder text. This will restore them. |
||
187 | */ |
||
188 | $content = $this->restoreExtractedData($content); |
||
189 | |||
190 | return $content; |
||
191 | } |
||
192 | |||
193 | /** |
||
194 | * Strip comments from source code. |
||
195 | */ |
||
196 | View Code Duplication | protected function stripComments() |
|
0 ignored issues
–
show
|
|||
197 | { |
||
198 | // PHP only supports $this inside anonymous functions since 5.4 |
||
199 | $minifier = $this; |
||
200 | $callback = function ($match) use ($minifier) { |
||
201 | $count = count($minifier->extracted); |
||
202 | $placeholder = '/*'.$count.'*/'; |
||
203 | $minifier->extracted[$placeholder] = $match[0]; |
||
204 | |||
205 | return $placeholder; |
||
206 | }; |
||
207 | // multi-line comments |
||
208 | $this->registerPattern('/\n?\/\*(!|.*?@license|.*?@preserve).*?\*\/\n?/s', $callback); |
||
209 | $this->registerPattern('/\/\*.*?\*\//s', ''); |
||
210 | |||
211 | // single-line comments |
||
212 | $this->registerPattern('/\/\/.*$/m', ''); |
||
213 | } |
||
214 | |||
215 | /** |
||
216 | * JS can have /-delimited regular expressions, like: /ab+c/.match(string). |
||
217 | * |
||
218 | * The content inside the regex can contain characters that may be confused |
||
219 | * for JS code: e.g. it could contain whitespace it needs to match & we |
||
220 | * don't want to strip whitespace in there. |
||
221 | * |
||
222 | * The regex can be pretty simple: we don't have to care about comments, |
||
223 | * (which also use slashes) because stripComments() will have stripped those |
||
224 | * already. |
||
225 | * |
||
226 | * This method will replace all string content with simple REGEX# |
||
227 | * placeholder text, so we've rid all regular expressions from characters |
||
228 | * that may be misinterpreted. Original regex content will be saved in |
||
229 | * $this->extracted and after doing all other minifying, we can restore the |
||
230 | * original content via restoreRegex() |
||
231 | */ |
||
232 | protected function extractRegex() |
||
233 | { |
||
234 | // PHP only supports $this inside anonymous functions since 5.4 |
||
235 | $minifier = $this; |
||
236 | $callback = function ($match) use ($minifier) { |
||
237 | $count = count($minifier->extracted); |
||
238 | $placeholder = '"'.$count.'"'; |
||
239 | $minifier->extracted[$placeholder] = $match[0]; |
||
240 | |||
241 | return $placeholder; |
||
242 | }; |
||
243 | |||
244 | // match all chars except `/` and `\` |
||
245 | // `\` is allowed though, along with whatever char follows (which is the |
||
246 | // one being escaped) |
||
247 | // this should allow all chars, except for an unescaped `/` (= the one |
||
248 | // closing the regex) |
||
249 | // then also ignore bare `/` inside `[]`, where they don't need to be |
||
250 | // escaped: anything inside `[]` can be ignored safely |
||
251 | $pattern = '\\/(?!\*)(?:[^\\[\\/\\\\\n\r]++|(?:\\\\.)++|(?:\\[(?:[^\\]\\\\\n\r]++|(?:\\\\.)++)++\\])++)++\\/[gimuy]*'; |
||
252 | |||
253 | // a regular expression can only be followed by a few operators or some |
||
254 | // of the RegExp methods (a `\` followed by a variable or value is |
||
255 | // likely part of a division, not a regex) |
||
256 | $keywords = array('do', 'in', 'new', 'else', 'throw', 'yield', 'delete', 'return', 'typeof'); |
||
257 | $before = '([=:,;\+\-\*\/\}\(\{\[&\|!]|^|'.implode('|', $keywords).')\s*'; |
||
258 | $propertiesAndMethods = array( |
||
259 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#Properties_2 |
||
260 | 'constructor', |
||
261 | 'flags', |
||
262 | 'global', |
||
263 | 'ignoreCase', |
||
264 | 'multiline', |
||
265 | 'source', |
||
266 | 'sticky', |
||
267 | 'unicode', |
||
268 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#Methods_2 |
||
269 | 'compile(', |
||
270 | 'exec(', |
||
271 | 'test(', |
||
272 | 'toSource(', |
||
273 | 'toString(', |
||
274 | ); |
||
275 | $delimiters = array_fill(0, count($propertiesAndMethods), '/'); |
||
276 | $propertiesAndMethods = array_map('preg_quote', $propertiesAndMethods, $delimiters); |
||
277 | $after = '(?=\s*([\.,;\)\}&\|+]|\/\/|$|\.('.implode('|', $propertiesAndMethods).')))'; |
||
278 | $this->registerPattern('/'.$before.'\K'.$pattern.$after.'/', $callback); |
||
279 | |||
280 | // regular expressions following a `)` are rather annoying to detect... |
||
281 | // quite often, `/` after `)` is a division operator & if it happens to |
||
282 | // be followed by another one (or a comment), it is likely to be |
||
283 | // confused for a regular expression |
||
284 | // however, it's perfectly possible for a regex to follow a `)`: after |
||
285 | // a single-line `if()`, `while()`, ... statement, for example |
||
286 | // since, when they occur like that, they're always the start of a |
||
287 | // statement, there's only a limited amount of ways they can be useful: |
||
288 | // by calling the regex methods directly |
||
289 | // if a regex following `)` is not followed by `.<property or method>`, |
||
290 | // it's quite likely not a regex |
||
291 | $before = '\)\s*'; |
||
292 | $after = '(?=\s*\.('.implode('|', $propertiesAndMethods).'))'; |
||
293 | $this->registerPattern('/'.$before.'\K'.$pattern.$after.'/', $callback); |
||
294 | |||
295 | // 1 more edge case: a regex can be followed by a lot more operators or |
||
296 | // keywords if there's a newline (ASI) in between, where the operator |
||
297 | // actually starts a new statement |
||
298 | // (https://github.com/matthiasmullie/minify/issues/56) |
||
299 | $operators = $this->getOperatorsForRegex($this->operatorsBefore, '/'); |
||
300 | $operators += $this->getOperatorsForRegex($this->keywordsReserved, '/'); |
||
301 | $after = '(?=\s*\n\s*('.implode('|', $operators).'))'; |
||
302 | $this->registerPattern('/'.$pattern.$after.'/', $callback); |
||
303 | } |
||
304 | |||
305 | /** |
||
306 | * Strip whitespace. |
||
307 | * |
||
308 | * We won't strip *all* whitespace, but as much as possible. The thing that |
||
309 | * we'll preserve are newlines we're unsure about. |
||
310 | * JavaScript doesn't require statements to be terminated with a semicolon. |
||
311 | * It will automatically fix missing semicolons with ASI (automatic semi- |
||
312 | * colon insertion) at the end of line causing errors (without semicolon.) |
||
313 | * |
||
314 | * Because it's sometimes hard to tell if a newline is part of a statement |
||
315 | * that should be terminated or not, we'll just leave some of them alone. |
||
316 | * |
||
317 | * @param string $content The content to strip the whitespace for |
||
318 | * |
||
319 | * @return string |
||
320 | */ |
||
321 | protected function stripWhitespace($content) |
||
322 | { |
||
323 | // uniform line endings, make them all line feed |
||
324 | $content = str_replace(array("\r\n", "\r"), "\n", $content); |
||
325 | |||
326 | // collapse all non-line feed whitespace into a single space |
||
327 | $content = preg_replace('/[^\S\n]+/', ' ', $content); |
||
328 | |||
329 | // strip leading & trailing whitespace |
||
330 | $content = str_replace(array(" \n", "\n "), "\n", $content); |
||
331 | |||
332 | // collapse consecutive line feeds into just 1 |
||
333 | $content = preg_replace('/\n+/', "\n", $content); |
||
334 | |||
335 | $operatorsBefore = $this->getOperatorsForRegex($this->operatorsBefore, '/'); |
||
336 | $operatorsAfter = $this->getOperatorsForRegex($this->operatorsAfter, '/'); |
||
337 | $operators = $this->getOperatorsForRegex($this->operators, '/'); |
||
338 | $keywordsBefore = $this->getKeywordsForRegex($this->keywordsBefore, '/'); |
||
339 | $keywordsAfter = $this->getKeywordsForRegex($this->keywordsAfter, '/'); |
||
340 | |||
341 | // strip whitespace that ends in (or next line begin with) an operator |
||
342 | // that allows statements to be broken up over multiple lines |
||
343 | unset($operatorsBefore['+'], $operatorsBefore['-'], $operatorsAfter['+'], $operatorsAfter['-']); |
||
344 | $content = preg_replace( |
||
345 | array( |
||
346 | '/('.implode('|', $operatorsBefore).')\s+/', |
||
347 | '/\s+('.implode('|', $operatorsAfter).')/', |
||
348 | ), '\\1', $content |
||
349 | ); |
||
350 | |||
351 | // make sure + and - can't be mistaken for, or joined into ++ and -- |
||
352 | $content = preg_replace( |
||
353 | array( |
||
354 | '/(?<![\+\-])\s*([\+\-])(?![\+\-])/', |
||
355 | '/(?<![\+\-])([\+\-])\s*(?![\+\-])/', |
||
356 | ), '\\1', $content |
||
357 | ); |
||
358 | |||
359 | // collapse whitespace around reserved words into single space |
||
360 | $content = preg_replace('/(^|[;\}\s])\K('.implode('|', $keywordsBefore).')\s+/', '\\2 ', $content); |
||
361 | $content = preg_replace('/\s+('.implode('|', $keywordsAfter).')(?=([;\{\s]|$))/', ' \\1', $content); |
||
362 | |||
363 | /* |
||
364 | * We didn't strip whitespace after a couple of operators because they |
||
365 | * could be used in different contexts and we can't be sure it's ok to |
||
366 | * strip the newlines. However, we can safely strip any non-line feed |
||
367 | * whitespace that follows them. |
||
368 | */ |
||
369 | $operatorsDiffBefore = array_diff($operators, $operatorsBefore); |
||
370 | $operatorsDiffAfter = array_diff($operators, $operatorsAfter); |
||
371 | $content = preg_replace('/('.implode('|', $operatorsDiffBefore).')[^\S\n]+/', '\\1', $content); |
||
372 | $content = preg_replace('/[^\S\n]+('.implode('|', $operatorsDiffAfter).')/', '\\1', $content); |
||
373 | |||
374 | /* |
||
375 | * Whitespace after `return` can be omitted in a few occasions |
||
376 | * (such as when followed by a string or regex) |
||
377 | * Same for whitespace in between `)` and `{`, or between `{` and some |
||
378 | * keywords. |
||
379 | */ |
||
380 | $content = preg_replace('/\breturn\s+(["\'\/\+\-])/', 'return$1', $content); |
||
381 | $content = preg_replace('/\)\s+\{/', '){', $content); |
||
382 | $content = preg_replace('/}\n(else|catch|finally)\b/', '}$1', $content); |
||
383 | |||
384 | /* |
||
385 | * Get rid of double semicolons, except where they can be used like: |
||
386 | * "for(v=1,_=b;;)", "for(v=1;;v++)" or "for(;;ja||(ja=true))". |
||
387 | * I'll safeguard these double semicolons inside for-loops by |
||
388 | * temporarily replacing them with an invalid condition: they won't have |
||
389 | * a double semicolon and will be easy to spot to restore afterwards. |
||
390 | */ |
||
391 | $content = preg_replace('/\bfor\(([^;]*);;([^;]*)\)/', 'for(\\1;-;\\2)', $content); |
||
392 | $content = preg_replace('/;+/', ';', $content); |
||
393 | $content = preg_replace('/\bfor\(([^;]*);-;([^;]*)\)/', 'for(\\1;;\\2)', $content); |
||
394 | |||
395 | /* |
||
396 | * Next, we'll be removing all semicolons where ASI kicks in. |
||
397 | * for-loops however, can have an empty body (ending in only a |
||
398 | * semicolon), like: `for(i=1;i<3;i++);`, of `for(i in list);` |
||
399 | * Here, nothing happens during the loop; it's just used to keep |
||
400 | * increasing `i`. With that ; omitted, the next line would be expected |
||
401 | * to be the for-loop's body... Same goes for while loops. |
||
402 | * I'm going to double that semicolon (if any) so after the next line, |
||
403 | * which strips semicolons here & there, we're still left with this one. |
||
404 | */ |
||
405 | $content = preg_replace('/(for\([^;\{]*;[^;\{]*;[^;\{]*\));(\}|$)/s', '\\1;;\\2', $content); |
||
406 | $content = preg_replace('/(for\([^;\{]+\s+in\s+[^;\{]+\));(\}|$)/s', '\\1;;\\2', $content); |
||
407 | /* |
||
408 | * Below will also keep `;` after a `do{}while();` along with `while();` |
||
409 | * While these could be stripped after do-while, detecting this |
||
410 | * distinction is cumbersome, so I'll play it safe and make sure `;` |
||
411 | * after any kind of `while` is kept. |
||
412 | */ |
||
413 | $content = preg_replace('/(while\([^;\{]+\));(\}|$)/s', '\\1;;\\2', $content); |
||
414 | |||
415 | /* |
||
416 | * We also can't strip empty else-statements. Even though they're |
||
417 | * useless and probably shouldn't be in the code in the first place, we |
||
418 | * shouldn't be stripping the `;` that follows it as it breaks the code. |
||
419 | * We can just remove those useless else-statements completely. |
||
420 | * |
||
421 | * @see https://github.com/matthiasmullie/minify/issues/91 |
||
422 | */ |
||
423 | $content = preg_replace('/else;/s', '', $content); |
||
424 | |||
425 | /* |
||
426 | * We also don't really want to terminate statements followed by closing |
||
427 | * curly braces (which we've ignored completely up until now) or end-of- |
||
428 | * script: ASI will kick in here & we're all about minifying. |
||
429 | * Semicolons at beginning of the file don't make any sense either. |
||
430 | */ |
||
431 | $content = preg_replace('/;(\}|$)/s', '\\1', $content); |
||
432 | $content = ltrim($content, ';'); |
||
433 | |||
434 | // get rid of remaining whitespace af beginning/end |
||
435 | return trim($content); |
||
436 | } |
||
437 | |||
438 | /** |
||
439 | * We'll strip whitespace around certain operators with regular expressions. |
||
440 | * This will prepare the given array by escaping all characters. |
||
441 | * |
||
442 | * @param string[] $operators |
||
443 | * @param string $delimiter |
||
444 | * |
||
445 | * @return string[] |
||
446 | */ |
||
447 | protected function getOperatorsForRegex(array $operators, $delimiter = '/') |
||
448 | { |
||
449 | // escape operators for use in regex |
||
450 | $delimiters = array_fill(0, count($operators), $delimiter); |
||
451 | $escaped = array_map('preg_quote', $operators, $delimiters); |
||
452 | |||
453 | $operators = array_combine($operators, $escaped); |
||
454 | |||
455 | // ignore + & - for now, they'll get special treatment |
||
456 | unset($operators['+'], $operators['-']); |
||
457 | |||
458 | // dot can not just immediately follow a number; it can be confused for |
||
459 | // decimal point, or calling a method on it, e.g. 42 .toString() |
||
460 | $operators['.'] = '(?<![0-9]\s)\.'; |
||
461 | |||
462 | // don't confuse = with other assignment shortcuts (e.g. +=) |
||
463 | $chars = preg_quote('+-*\=<>%&|', $delimiter); |
||
464 | $operators['='] = '(?<!['.$chars.'])\='; |
||
465 | |||
466 | return $operators; |
||
467 | } |
||
468 | |||
469 | /** |
||
470 | * We'll strip whitespace around certain keywords with regular expressions. |
||
471 | * This will prepare the given array by escaping all characters. |
||
472 | * |
||
473 | * @param string[] $keywords |
||
474 | * @param string $delimiter |
||
475 | * |
||
476 | * @return string[] |
||
477 | */ |
||
478 | protected function getKeywordsForRegex(array $keywords, $delimiter = '/') |
||
479 | { |
||
480 | // escape keywords for use in regex |
||
481 | $delimiter = array_fill(0, count($keywords), $delimiter); |
||
482 | $escaped = array_map('preg_quote', $keywords, $delimiter); |
||
483 | |||
484 | // add word boundaries |
||
485 | array_walk($keywords, function ($value) { |
||
486 | return '\b'.$value.'\b'; |
||
487 | }); |
||
488 | |||
489 | $keywords = array_combine($keywords, $escaped); |
||
490 | |||
491 | return $keywords; |
||
492 | } |
||
493 | |||
494 | /** |
||
495 | * Replaces all occurrences of array['key'] by array.key. |
||
496 | * |
||
497 | * @param string $content |
||
498 | * |
||
499 | * @return string |
||
500 | */ |
||
501 | protected function propertyNotation($content) |
||
502 | { |
||
503 | // PHP only supports $this inside anonymous functions since 5.4 |
||
504 | $minifier = $this; |
||
505 | $keywords = $this->keywordsReserved; |
||
506 | $callback = function ($match) use ($minifier, $keywords) { |
||
507 | $property = trim($minifier->extracted[$match[1]], '\'"'); |
||
508 | |||
509 | /* |
||
510 | * Check if the property is a reserved keyword. In this context (as |
||
511 | * property of an object literal/array) it shouldn't matter, but IE8 |
||
512 | * freaks out with "Expected identifier". |
||
513 | */ |
||
514 | if (in_array($property, $keywords)) { |
||
515 | return $match[0]; |
||
516 | } |
||
517 | |||
518 | /* |
||
519 | * See if the property is in a variable-like format (e.g. |
||
520 | * array['key-here'] can't be replaced by array.key-here since '-' |
||
521 | * is not a valid character there. |
||
522 | */ |
||
523 | if (!preg_match('/^'.$minifier::REGEX_VARIABLE.'$/u', $property)) { |
||
524 | return $match[0]; |
||
525 | } |
||
526 | |||
527 | return '.'.$property; |
||
528 | }; |
||
529 | |||
530 | /* |
||
531 | * Figure out if previous character is a variable name (of the array |
||
532 | * we want to use property notation on) - this is to make sure |
||
533 | * standalone ['value'] arrays aren't confused for keys-of-an-array. |
||
534 | * We can (and only have to) check the last character, because PHP's |
||
535 | * regex implementation doesn't allow unfixed-length look-behind |
||
536 | * assertions. |
||
537 | */ |
||
538 | preg_match('/(\[[^\]]+\])[^\]]*$/', static::REGEX_VARIABLE, $previousChar); |
||
539 | $previousChar = $previousChar[1]; |
||
540 | |||
541 | /* |
||
542 | * Make sure word preceding the ['value'] is not a keyword, e.g. |
||
543 | * return['x']. Because -again- PHP's regex implementation doesn't allow |
||
544 | * unfixed-length look-behind assertions, I'm just going to do a lot of |
||
545 | * separate look-behind assertions, one for each keyword. |
||
546 | */ |
||
547 | $keywords = $this->getKeywordsForRegex($keywords); |
||
548 | $keywords = '(?<!'.implode(')(?<!', $keywords).')'; |
||
549 | |||
550 | return preg_replace_callback('/(?<='.$previousChar.'|\])'.$keywords.'\[\s*(([\'"])[0-9]+\\2)\s*\]/u', $callback, $content); |
||
551 | } |
||
552 | |||
553 | /** |
||
554 | * Replaces true & false by !0 and !1. |
||
555 | * |
||
556 | * @param string $content |
||
557 | * |
||
558 | * @return string |
||
559 | */ |
||
560 | protected function shortenBools($content) |
||
561 | { |
||
562 | /* |
||
563 | * 'true' or 'false' could be used as property names (which may be |
||
564 | * followed by whitespace) - we must not replace those! |
||
565 | * Since PHP doesn't allow variable-length (to account for the |
||
566 | * whitespace) lookbehind assertions, I need to capture the leading |
||
567 | * character and check if it's a `.` |
||
568 | */ |
||
569 | $callback = function ($match) { |
||
570 | if (trim($match[1]) === '.') { |
||
571 | return $match[0]; |
||
572 | } |
||
573 | |||
574 | return $match[1].($match[2] === 'true' ? '!0' : '!1'); |
||
575 | }; |
||
576 | $content = preg_replace_callback('/(^|.\s*)\b(true|false)\b(?!:)/', $callback, $content); |
||
577 | |||
578 | // for(;;) is exactly the same as while(true), but shorter :) |
||
579 | $content = preg_replace('/\bwhile\(!0\){/', 'for(;;){', $content); |
||
580 | |||
581 | // now make sure we didn't turn any do ... while(true) into do ... for(;;) |
||
582 | preg_match_all('/\bdo\b/', $content, $dos, PREG_OFFSET_CAPTURE | PREG_SET_ORDER); |
||
583 | |||
584 | // go backward to make sure positional offsets aren't altered when $content changes |
||
585 | $dos = array_reverse($dos); |
||
586 | foreach ($dos as $do) { |
||
587 | $offsetDo = $do[0][1]; |
||
588 | |||
589 | // find all `while` (now `for`) following `do`: one of those must be |
||
590 | // associated with the `do` and be turned back into `while` |
||
591 | preg_match_all('/\bfor\(;;\)/', $content, $whiles, PREG_OFFSET_CAPTURE | PREG_SET_ORDER, $offsetDo); |
||
592 | foreach ($whiles as $while) { |
||
593 | $offsetWhile = $while[0][1]; |
||
594 | |||
595 | $open = substr_count($content, '{', $offsetDo, $offsetWhile - $offsetDo); |
||
596 | $close = substr_count($content, '}', $offsetDo, $offsetWhile - $offsetDo); |
||
597 | if ($open === $close) { |
||
598 | // only restore `while` if amount of `{` and `}` are the same; |
||
599 | // otherwise, that `for` isn't associated with this `do` |
||
600 | $content = substr_replace($content, 'while(!0)', $offsetWhile, strlen('for(;;)')); |
||
601 | break; |
||
602 | } |
||
603 | } |
||
604 | } |
||
605 | |||
606 | return $content; |
||
607 | } |
||
608 | } |
||
609 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.