|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace MatthiasMullie\Minify; |
|
4
|
|
|
|
|
5
|
|
|
/** |
|
6
|
|
|
* JavaScript minifier. |
|
7
|
|
|
* |
|
8
|
|
|
* Please report bugs on https://github.com/matthiasmullie/minify/issues |
|
9
|
|
|
* |
|
10
|
|
|
* @author Matthias Mullie <[email protected]> |
|
11
|
|
|
* @author Tijs Verkoyen <[email protected]> |
|
12
|
|
|
* @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved |
|
13
|
|
|
* @license MIT License |
|
14
|
|
|
*/ |
|
15
|
|
|
class JS extends Minify |
|
16
|
|
|
{ |
|
17
|
|
|
/** |
|
18
|
|
|
* Var-matching regex based on https://stackoverflow.com/a/9337047/802993. |
|
19
|
|
|
* |
|
20
|
|
|
* Note that regular expressions using that bit must have the PCRE_UTF8 |
|
21
|
|
|
* pattern modifier (/u) set. |
|
22
|
|
|
* |
|
23
|
|
|
* @var string |
|
24
|
|
|
*/ |
|
25
|
|
|
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'; |
|
26
|
|
|
|
|
27
|
|
|
/** |
|
28
|
|
|
* Full list of JavaScript reserved words. |
|
29
|
|
|
* Will be loaded from /data/js/keywords_reserved.txt. |
|
30
|
|
|
* |
|
31
|
|
|
* @see https://mathiasbynens.be/notes/reserved-keywords |
|
32
|
|
|
* |
|
33
|
|
|
* @var string[] |
|
34
|
|
|
*/ |
|
35
|
|
|
protected $keywordsReserved = array(); |
|
36
|
|
|
|
|
37
|
|
|
/** |
|
38
|
|
|
* List of JavaScript reserved words that accept a <variable, value, ...> |
|
39
|
|
|
* after them. Some end of lines are not the end of a statement, like with |
|
40
|
|
|
* these keywords. |
|
41
|
|
|
* |
|
42
|
|
|
* E.g.: we shouldn't insert a ; after this else |
|
43
|
|
|
* else |
|
44
|
|
|
* console.log('this is quite fine') |
|
45
|
|
|
* |
|
46
|
|
|
* Will be loaded from /data/js/keywords_before.txt |
|
47
|
|
|
* |
|
48
|
|
|
* @var string[] |
|
49
|
|
|
*/ |
|
50
|
|
|
protected $keywordsBefore = array(); |
|
51
|
|
|
|
|
52
|
|
|
/** |
|
53
|
|
|
* List of JavaScript reserved words that accept a <variable, value, ...> |
|
54
|
|
|
* before them. Some end of lines are not the end of a statement, like when |
|
55
|
|
|
* continued by one of these keywords on the newline. |
|
56
|
|
|
* |
|
57
|
|
|
* E.g.: we shouldn't insert a ; before this instanceof |
|
58
|
|
|
* variable |
|
59
|
|
|
* instanceof String |
|
60
|
|
|
* |
|
61
|
|
|
* Will be loaded from /data/js/keywords_after.txt |
|
62
|
|
|
* |
|
63
|
|
|
* @var string[] |
|
64
|
|
|
*/ |
|
65
|
|
|
protected $keywordsAfter = array(); |
|
66
|
|
|
|
|
67
|
|
|
/** |
|
68
|
|
|
* List of all JavaScript operators. |
|
69
|
|
|
* |
|
70
|
|
|
* Will be loaded from /data/js/operators.txt |
|
71
|
|
|
* |
|
72
|
|
|
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators |
|
73
|
|
|
* |
|
74
|
|
|
* @var string[] |
|
75
|
|
|
*/ |
|
76
|
|
|
protected $operators = array(); |
|
77
|
|
|
|
|
78
|
|
|
/** |
|
79
|
|
|
* List of JavaScript operators that accept a <variable, value, ...> after |
|
80
|
|
|
* them. Some end of lines are not the end of a statement, like with these |
|
81
|
|
|
* operators. |
|
82
|
|
|
* |
|
83
|
|
|
* Note: Most operators are fine, we've only removed !, ++ and --. |
|
84
|
|
|
* There can't be a newline separating ! and whatever it is negating. |
|
85
|
|
|
* ++ & -- have to be joined with the value they're in-/decrementing. |
|
86
|
|
|
* |
|
87
|
|
|
* Will be loaded from /data/js/operators_before.txt |
|
88
|
|
|
* |
|
89
|
|
|
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators |
|
90
|
|
|
* |
|
91
|
|
|
* @var string[] |
|
92
|
|
|
*/ |
|
93
|
|
|
protected $operatorsBefore = array(); |
|
94
|
|
|
|
|
95
|
|
|
/** |
|
96
|
|
|
* List of JavaScript operators that accept a <variable, value, ...> before |
|
97
|
|
|
* them. Some end of lines are not the end of a statement, like when |
|
98
|
|
|
* continued by one of these operators on the newline. |
|
99
|
|
|
* |
|
100
|
|
|
* Note: Most operators are fine, we've only removed ), ], ++ and --. |
|
101
|
|
|
* ++ & -- have to be joined with the value they're in-/decrementing. |
|
102
|
|
|
* ) & ] are "special" in that they have lots or usecases. () for example |
|
103
|
|
|
* is used for function calls, for grouping, in if () and for (), ... |
|
104
|
|
|
* |
|
105
|
|
|
* Will be loaded from /data/js/operators_after.txt |
|
106
|
|
|
* |
|
107
|
|
|
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Expressions_and_Operators |
|
108
|
|
|
* |
|
109
|
|
|
* @var string[] |
|
110
|
|
|
*/ |
|
111
|
|
|
protected $operatorsAfter = array(); |
|
112
|
|
|
|
|
113
|
|
|
/** |
|
114
|
|
|
* {@inheritdoc} |
|
115
|
|
|
*/ |
|
116
|
|
|
public function __construct() |
|
117
|
|
|
{ |
|
118
|
|
|
call_user_func_array(array('parent', '__construct'), func_get_args()); |
|
119
|
|
|
|
|
120
|
|
|
$dataDir = __DIR__.'/../data/js/'; |
|
121
|
|
|
$options = FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES; |
|
122
|
|
|
$this->keywordsReserved = file($dataDir.'keywords_reserved.txt', $options); |
|
123
|
|
|
$this->keywordsBefore = file($dataDir.'keywords_before.txt', $options); |
|
124
|
|
|
$this->keywordsAfter = file($dataDir.'keywords_after.txt', $options); |
|
125
|
|
|
$this->operators = file($dataDir.'operators.txt', $options); |
|
126
|
|
|
$this->operatorsBefore = file($dataDir.'operators_before.txt', $options); |
|
127
|
|
|
$this->operatorsAfter = file($dataDir.'operators_after.txt', $options); |
|
128
|
|
|
} |
|
129
|
|
|
|
|
130
|
|
|
/** |
|
131
|
|
|
* Minify the data. |
|
132
|
|
|
* Perform JS optimizations. |
|
133
|
|
|
* |
|
134
|
|
|
* @param string[optional] $path Path to write the data to |
|
135
|
|
|
* |
|
136
|
|
|
* @return string The minified data |
|
137
|
|
|
*/ |
|
138
|
|
|
public function execute($path = null) |
|
139
|
|
|
{ |
|
140
|
|
|
$content = ''; |
|
141
|
|
|
|
|
142
|
|
|
// loop files |
|
143
|
|
|
foreach ($this->data as $source => $js) { |
|
144
|
|
|
/* |
|
145
|
|
|
* Combine js: separating the scripts by a ; |
|
146
|
|
|
* I'm also adding a newline: it will be eaten when whitespace is |
|
147
|
|
|
* stripped, but we need to make sure we're not just appending |
|
148
|
|
|
* a new script right after a previous script that ended with a |
|
149
|
|
|
* singe-line comment on the last line (in which case it would also |
|
150
|
|
|
* be seen as part of that comment) |
|
151
|
|
|
*/ |
|
152
|
|
|
$content .= $js."\n;"; |
|
153
|
|
|
} |
|
154
|
|
|
|
|
155
|
|
|
/* |
|
|
|
|
|
|
156
|
|
|
* Let's first take out strings, comments and regular expressions. |
|
157
|
|
|
* All of these can contain JS code-like characters, and we should make |
|
158
|
|
|
* sure any further magic ignores anything inside of these. |
|
159
|
|
|
* |
|
160
|
|
|
* Consider this example, where we should not strip any whitespace: |
|
161
|
|
|
* var str = "a test"; |
|
162
|
|
|
* |
|
163
|
|
|
* Comments will be removed altogether, strings and regular expressions |
|
164
|
|
|
* will be replaced by placeholder text, which we'll restore later. |
|
165
|
|
|
*/ |
|
166
|
|
|
$this->extractStrings('\'"`'); |
|
167
|
|
|
$this->stripComments(); |
|
168
|
|
|
$this->extractRegex(); |
|
169
|
|
|
$content = $this->replace($content); |
|
170
|
|
|
|
|
171
|
|
|
$content = $this->propertyNotation($content); |
|
172
|
|
|
$content = $this->shortenBools($content); |
|
173
|
|
|
$content = $this->stripWhitespace($content); |
|
174
|
|
|
|
|
175
|
|
|
/* |
|
176
|
|
|
* Earlier, we extracted strings & regular expressions and replaced them |
|
177
|
|
|
* with placeholder text. This will restore them. |
|
178
|
|
|
*/ |
|
179
|
|
|
$content = $this->restoreExtractedData($content); |
|
180
|
|
|
|
|
181
|
|
|
return $content; |
|
182
|
|
|
} |
|
183
|
|
|
|
|
184
|
|
|
/** |
|
185
|
|
|
* Strip comments from source code. |
|
186
|
|
|
*/ |
|
187
|
|
|
protected function stripComments() |
|
188
|
|
|
{ |
|
189
|
|
|
// single-line comments |
|
190
|
|
|
$this->registerPattern('/\/\/.*$/m', ''); |
|
191
|
|
|
|
|
192
|
|
|
// multi-line comments |
|
193
|
|
|
$this->registerPattern('/\/\*.*?\*\//s', ''); |
|
194
|
|
|
} |
|
195
|
|
|
|
|
196
|
|
|
/** |
|
197
|
|
|
* JS can have /-delimited regular expressions, like: /ab+c/.match(string). |
|
198
|
|
|
* |
|
199
|
|
|
* The content inside the regex can contain characters that may be confused |
|
200
|
|
|
* for JS code: e.g. it could contain whitespace it needs to match & we |
|
201
|
|
|
* don't want to strip whitespace in there. |
|
202
|
|
|
* |
|
203
|
|
|
* The regex can be pretty simple: we don't have to care about comments, |
|
204
|
|
|
* (which also use slashes) because stripComments() will have stripped those |
|
205
|
|
|
* already. |
|
206
|
|
|
* |
|
207
|
|
|
* This method will replace all string content with simple REGEX# |
|
208
|
|
|
* placeholder text, so we've rid all regular expressions from characters |
|
209
|
|
|
* that may be misinterpreted. Original regex content will be saved in |
|
210
|
|
|
* $this->extracted and after doing all other minifying, we can restore the |
|
211
|
|
|
* original content via restoreRegex() |
|
212
|
|
|
*/ |
|
213
|
|
|
protected function extractRegex() |
|
214
|
|
|
{ |
|
215
|
|
|
// PHP only supports $this inside anonymous functions since 5.4 |
|
216
|
|
|
$minifier = $this; |
|
217
|
|
|
$callback = function ($match) use ($minifier) { |
|
218
|
|
|
$count = count($minifier->extracted); |
|
219
|
|
|
$placeholder = '/'.$count.'/'; |
|
220
|
|
|
$minifier->extracted[$placeholder] = $match[0]; |
|
221
|
|
|
|
|
222
|
|
|
return $placeholder; |
|
223
|
|
|
}; |
|
224
|
|
|
|
|
225
|
|
|
$pattern = '\/.+?(?<!\\\\)(\\\\\\\\)*\/[gimy]*(?![0-9a-zA-Z\/])'; |
|
226
|
|
|
|
|
227
|
|
|
// a regular expression can only be followed by a few operators or some |
|
228
|
|
|
// of the RegExp methods (a `\` followed by a variable or value is |
|
229
|
|
|
// likely part of a division, not a regex) |
|
230
|
|
|
$after = '[\.,;\)\}]'; |
|
231
|
|
|
$methods = '\.(exec|test|match|search|replace|split)\('; |
|
232
|
|
|
$this->registerPattern('/'.$pattern.'(?=\s*('.$after.'|'.$methods.'))/', $callback); |
|
233
|
|
|
|
|
234
|
|
|
// 1 more edge case: a regex can be followed by a lot more operators or |
|
235
|
|
|
// keywords if there's a newline (ASI) in between, where the operator |
|
236
|
|
|
// actually starts a new statement |
|
237
|
|
|
// (https://github.com/matthiasmullie/minify/issues/56) |
|
238
|
|
|
$operators = $this->getOperatorsForRegex($this->operatorsBefore, '/'); |
|
239
|
|
|
$operators += $this->getOperatorsForRegex($this->keywordsReserved, '/'); |
|
240
|
|
|
$this->registerPattern('/'.$pattern.'\s*\n(?=\s*('.implode('|', $operators).'))/', $callback); |
|
241
|
|
|
} |
|
242
|
|
|
|
|
243
|
|
|
/** |
|
244
|
|
|
* Strip whitespace. |
|
245
|
|
|
* |
|
246
|
|
|
* We won't strip *all* whitespace, but as much as possible. The thing that |
|
247
|
|
|
* we'll preserve are newlines we're unsure about. |
|
248
|
|
|
* JavaScript doesn't require statements to be terminated with a semicolon. |
|
249
|
|
|
* It will automatically fix missing semicolons with ASI (automatic semi- |
|
250
|
|
|
* colon insertion) at the end of line causing errors (without semicolon.) |
|
251
|
|
|
* |
|
252
|
|
|
* Because it's sometimes hard to tell if a newline is part of a statement |
|
253
|
|
|
* that should be terminated or not, we'll just leave some of them alone. |
|
254
|
|
|
* |
|
255
|
|
|
* @param string $content The content to strip the whitespace for |
|
256
|
|
|
* |
|
257
|
|
|
* @return string |
|
258
|
|
|
*/ |
|
259
|
|
|
protected function stripWhitespace($content) |
|
260
|
|
|
{ |
|
261
|
|
|
// uniform line endings, make them all line feed |
|
262
|
|
|
$content = str_replace(array("\r\n", "\r"), "\n", $content); |
|
263
|
|
|
|
|
264
|
|
|
// collapse all non-line feed whitespace into a single space |
|
265
|
|
|
$content = preg_replace('/[^\S\n]+/', ' ', $content); |
|
266
|
|
|
|
|
267
|
|
|
// strip leading & trailing whitespace |
|
268
|
|
|
$content = str_replace(array(" \n", "\n "), "\n", $content); |
|
269
|
|
|
|
|
270
|
|
|
// collapse consecutive line feeds into just 1 |
|
271
|
|
|
$content = preg_replace('/\n+/', "\n", $content); |
|
272
|
|
|
|
|
273
|
|
|
$operatorsBefore = $this->getOperatorsForRegex($this->operatorsBefore, '/'); |
|
274
|
|
|
$operatorsAfter = $this->getOperatorsForRegex($this->operatorsAfter, '/'); |
|
275
|
|
|
$operators = $this->getOperatorsForRegex($this->operators, '/'); |
|
276
|
|
|
$keywordsBefore = $this->getKeywordsForRegex($this->keywordsBefore, '/'); |
|
277
|
|
|
$keywordsAfter = $this->getKeywordsForRegex($this->keywordsAfter, '/'); |
|
278
|
|
|
|
|
279
|
|
|
// strip whitespace that ends in (or next line begin with) an operator |
|
280
|
|
|
// that allows statements to be broken up over multiple lines |
|
281
|
|
|
unset($operatorsBefore['+'], $operatorsBefore['-'], $operatorsAfter['+'], $operatorsAfter['-']); |
|
282
|
|
|
$content = preg_replace( |
|
283
|
|
|
array( |
|
284
|
|
|
'/('.implode('|', $operatorsBefore).')\s+/', |
|
285
|
|
|
'/\s+('.implode('|', $operatorsAfter).')/', |
|
286
|
|
|
), '\\1', $content |
|
287
|
|
|
); |
|
288
|
|
|
|
|
289
|
|
|
// make sure + and - can't be mistaken for, or joined into ++ and -- |
|
290
|
|
|
$content = preg_replace( |
|
291
|
|
|
array( |
|
292
|
|
|
'/(?<![\+\-])\s*([\+\-])(?![\+\-])/', |
|
293
|
|
|
'/(?<![\+\-])([\+\-])\s*(?![\+\-])/', |
|
294
|
|
|
), '\\1', $content |
|
295
|
|
|
); |
|
296
|
|
|
|
|
297
|
|
|
// collapse whitespace around reserved words into single space |
|
298
|
|
|
$content = preg_replace('/(^|[;\}\s])\K('.implode('|', $keywordsBefore).')\s+/', '\\2 ', $content); |
|
299
|
|
|
$content = preg_replace('/\s+('.implode('|', $keywordsAfter).')(?=([;\{\s]|$))/', ' \\1', $content); |
|
300
|
|
|
|
|
301
|
|
|
/* |
|
302
|
|
|
* We didn't strip whitespace after a couple of operators because they |
|
303
|
|
|
* could be used in different contexts and we can't be sure it's ok to |
|
304
|
|
|
* strip the newlines. However, we can safely strip any non-line feed |
|
305
|
|
|
* whitespace that follows them. |
|
306
|
|
|
*/ |
|
307
|
|
|
$operatorsDiffBefore = array_diff($operators, $operatorsBefore); |
|
308
|
|
|
$operatorsDiffAfter = array_diff($operators, $operatorsAfter); |
|
309
|
|
|
$content = preg_replace('/('.implode('|', $operatorsDiffBefore).')[^\S\n]+/', '\\1', $content); |
|
310
|
|
|
$content = preg_replace('/[^\S\n]+('.implode('|', $operatorsDiffAfter).')/', '\\1', $content); |
|
311
|
|
|
|
|
312
|
|
|
/* |
|
313
|
|
|
* Get rid of double semicolons, except where they can be used like: |
|
314
|
|
|
* "for(v=1,_=b;;)", "for(v=1;;v++)" or "for(;;ja||(ja=true))". |
|
315
|
|
|
* I'll safeguard these double semicolons inside for-loops by |
|
316
|
|
|
* temporarily replacing them with an invalid condition: they won't have |
|
317
|
|
|
* a double semicolon and will be easy to spot to restore afterwards. |
|
318
|
|
|
*/ |
|
319
|
|
|
$content = preg_replace('/\bfor\(([^;]*);;([^;]*)\)/', 'for(\\1;-;\\2)', $content); |
|
320
|
|
|
$content = preg_replace('/;+/', ';', $content); |
|
321
|
|
|
$content = preg_replace('/\bfor\(([^;]*);-;([^;]*)\)/', 'for(\\1;;\\2)', $content); |
|
322
|
|
|
|
|
323
|
|
|
/* |
|
324
|
|
|
* Next, we'll be removing all semicolons where ASI kicks in. |
|
325
|
|
|
* for-loops however, can have an empty body (ending in only a |
|
326
|
|
|
* semicolon), like: `for(i=1;i<3;i++);` |
|
327
|
|
|
* Here, nothing happens during the loop; it's just used to keep |
|
328
|
|
|
* increasing `i`. With that ; omitted, the next line would be expected |
|
329
|
|
|
* to be the for-loop's body... |
|
330
|
|
|
* I'm going to double that semicolon (if any) so after the next line, |
|
331
|
|
|
* which strips semicolons here & there, we're still left with this one. |
|
332
|
|
|
*/ |
|
333
|
|
|
$content = preg_replace('/(for\([^;]*;[^;]*;[^;\{]*\));(\}|$)/s', '\\1;;\\2', $content); |
|
334
|
|
|
|
|
335
|
|
|
/* |
|
336
|
|
|
* We also can't strip empty else-statements. Even though they're |
|
337
|
|
|
* useless and probably shouldn't be in the code in the first place, we |
|
338
|
|
|
* shouldn't be stripping the `;` that follows it as it breaks the code. |
|
339
|
|
|
* We can just remove those useless else-statements completely. |
|
340
|
|
|
* |
|
341
|
|
|
* @see https://github.com/matthiasmullie/minify/issues/91 |
|
342
|
|
|
*/ |
|
343
|
|
|
$content = preg_replace('/else;/s', '', $content); |
|
344
|
|
|
|
|
345
|
|
|
/* |
|
346
|
|
|
* We also don't really want to terminate statements followed by closing |
|
347
|
|
|
* curly braces (which we've ignored completely up until now) or end-of- |
|
348
|
|
|
* script: ASI will kick in here & we're all about minifying. |
|
349
|
|
|
* Semicolons at beginning of the file don't make any sense either. |
|
350
|
|
|
*/ |
|
351
|
|
|
$content = preg_replace('/;(\}|$)/s', '\\1', $content); |
|
352
|
|
|
$content = ltrim($content, ';'); |
|
353
|
|
|
|
|
354
|
|
|
// get rid of remaining whitespace af beginning/end |
|
355
|
|
|
return trim($content); |
|
356
|
|
|
} |
|
357
|
|
|
|
|
358
|
|
|
/** |
|
359
|
|
|
* We'll strip whitespace around certain operators with regular expressions. |
|
360
|
|
|
* This will prepare the given array by escaping all characters. |
|
361
|
|
|
* |
|
362
|
|
|
* @param string[] $operators |
|
363
|
|
|
* @param string $delimiter |
|
364
|
|
|
* |
|
365
|
|
|
* @return string[] |
|
366
|
|
|
*/ |
|
367
|
|
|
protected function getOperatorsForRegex(array $operators, $delimiter = '/') |
|
368
|
|
|
{ |
|
369
|
|
|
// escape operators for use in regex |
|
370
|
|
|
$delimiter = array_fill(0, count($operators), $delimiter); |
|
371
|
|
|
$escaped = array_map('preg_quote', $operators, $delimiter); |
|
372
|
|
|
|
|
373
|
|
|
$operators = array_combine($operators, $escaped); |
|
374
|
|
|
|
|
375
|
|
|
// ignore + & - for now, they'll get special treatment |
|
376
|
|
|
unset($operators['+'], $operators['-']); |
|
377
|
|
|
|
|
378
|
|
|
// dot can not just immediately follow a number; it can be confused for |
|
379
|
|
|
// decimal point, or calling a method on it, e.g. 42 .toString() |
|
380
|
|
|
$operators['.'] = '(?<![0-9]\s)\.'; |
|
381
|
|
|
|
|
382
|
|
|
// don't confuse = with other assignment shortcuts (e.g. +=) |
|
383
|
|
|
$chars = preg_quote('+-*\=<>%&|'); |
|
384
|
|
|
$operators['='] = '(?<!['.$chars.'])\='; |
|
385
|
|
|
|
|
386
|
|
|
return $operators; |
|
387
|
|
|
} |
|
388
|
|
|
|
|
389
|
|
|
/** |
|
390
|
|
|
* We'll strip whitespace around certain keywords with regular expressions. |
|
391
|
|
|
* This will prepare the given array by escaping all characters. |
|
392
|
|
|
* |
|
393
|
|
|
* @param string[] $keywords |
|
394
|
|
|
* @param string $delimiter |
|
395
|
|
|
* |
|
396
|
|
|
* @return string[] |
|
397
|
|
|
*/ |
|
398
|
|
|
protected function getKeywordsForRegex(array $keywords, $delimiter = '/') |
|
399
|
|
|
{ |
|
400
|
|
|
// escape keywords for use in regex |
|
401
|
|
|
$delimiter = array_fill(0, count($keywords), $delimiter); |
|
402
|
|
|
$escaped = array_map('preg_quote', $keywords, $delimiter); |
|
403
|
|
|
|
|
404
|
|
|
// add word boundaries |
|
405
|
|
|
array_walk($keywords, function ($value) { |
|
406
|
|
|
return '\b'.$value.'\b'; |
|
407
|
|
|
}); |
|
408
|
|
|
|
|
409
|
|
|
$keywords = array_combine($keywords, $escaped); |
|
410
|
|
|
|
|
411
|
|
|
return $keywords; |
|
412
|
|
|
} |
|
413
|
|
|
|
|
414
|
|
|
/** |
|
415
|
|
|
* Replaces all occurrences of array['key'] by array.key. |
|
416
|
|
|
* |
|
417
|
|
|
* @param string $content |
|
418
|
|
|
* |
|
419
|
|
|
* @return string |
|
420
|
|
|
*/ |
|
421
|
|
|
protected function propertyNotation($content) |
|
422
|
|
|
{ |
|
423
|
|
|
// PHP only supports $this inside anonymous functions since 5.4 |
|
424
|
|
|
$minifier = $this; |
|
425
|
|
|
$keywords = $this->keywordsReserved; |
|
426
|
|
|
$callback = function ($match) use ($minifier, $keywords) { |
|
427
|
|
|
$property = trim($minifier->extracted[$match[1]], '\'"'); |
|
428
|
|
|
|
|
429
|
|
|
/* |
|
430
|
|
|
* Check if the property is a reserved keyword. In this context (as |
|
431
|
|
|
* property of an object literal/array) it shouldn't matter, but IE8 |
|
432
|
|
|
* freaks out with "Expected identifier". |
|
433
|
|
|
*/ |
|
434
|
|
|
if (in_array($property, $keywords)) { |
|
435
|
|
|
return $match[0]; |
|
436
|
|
|
} |
|
437
|
|
|
|
|
438
|
|
|
/* |
|
439
|
|
|
* See if the property is in a variable-like format (e.g. |
|
440
|
|
|
* array['key-here'] can't be replaced by array.key-here since '-' |
|
441
|
|
|
* is not a valid character there. |
|
442
|
|
|
*/ |
|
443
|
|
|
if (!preg_match('/^'.$minifier::REGEX_VARIABLE.'$/u', $property)) { |
|
444
|
|
|
return $match[0]; |
|
445
|
|
|
} |
|
446
|
|
|
|
|
447
|
|
|
return '.'.$property; |
|
448
|
|
|
}; |
|
449
|
|
|
|
|
450
|
|
|
/* |
|
451
|
|
|
* Figure out if previous character is a variable name (of the array |
|
452
|
|
|
* we want to use property notation on) - this is to make sure |
|
453
|
|
|
* standalone ['value'] arrays aren't confused for keys-of-an-array. |
|
454
|
|
|
* We can (and only have to) check the last character, because PHP's |
|
455
|
|
|
* regex implementation doesn't allow unfixed-length look-behind |
|
456
|
|
|
* assertions. |
|
457
|
|
|
*/ |
|
458
|
|
|
preg_match('/(\[[^\]]+\])[^\]]*$/', static::REGEX_VARIABLE, $previousChar); |
|
459
|
|
|
$previousChar = $previousChar[1]; |
|
460
|
|
|
|
|
461
|
|
|
/* |
|
462
|
|
|
* Make sure word preceding the ['value'] is not a keyword, e.g. |
|
463
|
|
|
* return['x']. Because -again- PHP's regex implementation doesn't allow |
|
464
|
|
|
* unfixed-length look-behind assertions, I'm just going to do a lot of |
|
465
|
|
|
* separate look-behind assertions, one for each keyword. |
|
466
|
|
|
*/ |
|
467
|
|
|
$keywords = $this->getKeywordsForRegex($keywords); |
|
468
|
|
|
$keywords = '(?<!'.implode(')(?<!', $keywords).')'; |
|
469
|
|
|
|
|
470
|
|
|
return preg_replace_callback('/(?<='.$previousChar.'|\])'.$keywords.'\[\s*(([\'"])[0-9]+\\2)\s*\]/u', $callback, $content); |
|
471
|
|
|
} |
|
472
|
|
|
|
|
473
|
|
|
/** |
|
474
|
|
|
* Replaces true & false by !0 and !1. |
|
475
|
|
|
* |
|
476
|
|
|
* @param string $content |
|
477
|
|
|
* |
|
478
|
|
|
* @return string |
|
479
|
|
|
*/ |
|
480
|
|
|
protected function shortenBools($content) |
|
481
|
|
|
{ |
|
482
|
|
|
$content = preg_replace('/\btrue\b(?!:)/', '!0', $content); |
|
483
|
|
|
$content = preg_replace('/\bfalse\b(?!:)/', '!1', $content); |
|
484
|
|
|
|
|
485
|
|
|
// for(;;) is exactly the same as while(true) |
|
|
|
|
|
|
486
|
|
|
$content = preg_replace('/\bwhile\(!0\){/', 'for(;;){', $content); |
|
487
|
|
|
|
|
488
|
|
|
// now make sure we didn't turn any do ... while(true) into do ... for(;;) |
|
489
|
|
|
preg_match_all('/\bdo\b/', $content, $dos, PREG_OFFSET_CAPTURE | PREG_SET_ORDER); |
|
490
|
|
|
|
|
491
|
|
|
// go backward to make sure positional offsets aren't altered when $content changes |
|
492
|
|
|
$dos = array_reverse($dos); |
|
493
|
|
|
foreach ($dos as $do) { |
|
494
|
|
|
$offsetDo = $do[0][1]; |
|
495
|
|
|
|
|
496
|
|
|
// find all `while` (now `for`) following `do`: one of those must be |
|
497
|
|
|
// associated with the `do` and be turned back into `while` |
|
498
|
|
|
preg_match_all('/\bfor\(;;\)/', $content, $whiles, PREG_OFFSET_CAPTURE | PREG_SET_ORDER, $offsetDo); |
|
499
|
|
|
foreach ($whiles as $while) { |
|
|
|
|
|
|
500
|
|
|
$offsetWhile = $while[0][1]; |
|
501
|
|
|
|
|
502
|
|
|
$open = substr_count($content, '{', $offsetDo, $offsetWhile - $offsetDo); |
|
503
|
|
|
$close = substr_count($content, '}', $offsetDo, $offsetWhile - $offsetDo); |
|
504
|
|
|
if ($open === $close) { |
|
505
|
|
|
// only restore `while` if amount of `{` and `}` are the same; |
|
506
|
|
|
// otherwise, that `for` isn't associated with this `do` |
|
507
|
|
|
$content = substr_replace($content, 'while(!0)', $offsetWhile, strlen('for(;;)')); |
|
508
|
|
|
break; |
|
509
|
|
|
} |
|
510
|
|
|
} |
|
511
|
|
|
} |
|
512
|
|
|
|
|
513
|
|
|
return $content; |
|
514
|
|
|
} |
|
515
|
|
|
} |
|
516
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.