PluginProbe
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance / 3.2.21
WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance v3.2.21
4.7.0 4.6.1 4.6.0 4.5.5 4.5.4 4.5.3 4.5.2 3.2.20 3.2.21 3.2.22 3.2.3 3.2.5 3.2.6 3.2.7 3.2.9 3.3.0 3.3.1 3.3.2 3.4.0 3.4.1 3.4.2 3.5.0 3.6.0 3.7.0 3.7.1 All 111 releases
wp-optimize / vendor / matthiasmullie / minify / src / JS.php

JS.php in WP-Optimize – Cache, Compress images, Minify & Clean database to boost page speed & performance 3.2.21, at vendor/matthiasmullie/minify/src/JS.php

623 lines 38.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
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 protected function stripComments()
197 {
198 // PHP only supports $this inside anonymous functions since 5.4
199 $minifier = $this;
200 $callback = function ($match) use ($minifier) {
201 if (
202 substr($match[1], 0, 1) === '!' ||
203 strpos($match[1], '@license') !== false ||
204 strpos($match[1], '@preserve') !== false
205 ) {
206 // preserve multi-line comments that start with /*!
207 // or contain @license or @preserve annotations
208 $count = count($minifier->extracted);
209 $placeholder = '/*'.$count.'*/';
210 $minifier->extracted[$placeholder] = $match[0];
211
212 return $placeholder;
213 }
214
215 return '';
216 };
217
218 // multi-line comments
219 $this->registerPattern('/\n?\/\*(.*?)\*\/\n?/s', $callback);
220
221 // single-line comments
222 $this->registerPattern('/\/\/.*$/m', '');
223 }
224
225 /**
226 * JS can have /-delimited regular expressions, like: /ab+c/.match(string).
227 *
228 * The content inside the regex can contain characters that may be confused
229 * for JS code: e.g. it could contain whitespace it needs to match & we
230 * don't want to strip whitespace in there.
231 *
232 * The regex can be pretty simple: we don't have to care about comments,
233 * (which also use slashes) because stripComments() will have stripped those
234 * already.
235 *
236 * This method will replace all string content with simple REGEX#
237 * placeholder text, so we've rid all regular expressions from characters
238 * that may be misinterpreted. Original regex content will be saved in
239 * $this->extracted and after doing all other minifying, we can restore the
240 * original content via restoreRegex()
241 */
242 protected function extractRegex()
243 {
244 // PHP only supports $this inside anonymous functions since 5.4
245 $minifier = $this;
246 $callback = function ($match) use ($minifier) {
247 $count = count($minifier->extracted);
248 $placeholder = '"'.$count.'"';
249 $minifier->extracted[$placeholder] = $match[0];
250
251 return $placeholder;
252 };
253
254 // match all chars except `/` and `\`
255 // `\` is allowed though, along with whatever char follows (which is the
256 // one being escaped)
257 // this should allow all chars, except for an unescaped `/` (= the one
258 // closing the regex)
259 // then also ignore bare `/` inside `[]`, where they don't need to be
260 // escaped: anything inside `[]` can be ignored safely
261 $pattern = '\\/(?!\*)(?:[^\\[\\/\\\\\n\r]++|(?:\\\\.)++|(?:\\[(?:[^\\]\\\\\n\r]++|(?:\\\\.)++)++\\])++)++\\/[gimuy]*';
262
263 // a regular expression can only be followed by a few operators or some
264 // of the RegExp methods (a `\` followed by a variable or value is
265 // likely part of a division, not a regex)
266 $keywords = array('do', 'in', 'new', 'else', 'throw', 'yield', 'delete', 'return', 'typeof');
267 $before = '(^|[=:,;\+\-\*\/\}\(\{\[&\|!]|'.implode('|', $keywords).')\s*';
268 $propertiesAndMethods = array(
269 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#Properties_2
270 'constructor',
271 'flags',
272 'global',
273 'ignoreCase',
274 'multiline',
275 'source',
276 'sticky',
277 'unicode',
278 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp#Methods_2
279 'compile(',
280 'exec(',
281 'test(',
282 'toSource(',
283 'toString(',
284 );
285 $delimiters = array_fill(0, count($propertiesAndMethods), '/');
286 $propertiesAndMethods = array_map('preg_quote', $propertiesAndMethods, $delimiters);
287 $after = '(?=\s*([\.,;\)\}&\|+]|\/\/|$|\.('.implode('|', $propertiesAndMethods).')))';
288 $this->registerPattern('/'.$before.'\K'.$pattern.$after.'/', $callback);
289
290 // regular expressions following a `)` are rather annoying to detect...
291 // quite often, `/` after `)` is a division operator & if it happens to
292 // be followed by another one (or a comment), it is likely to be
293 // confused for a regular expression
294 // however, it's perfectly possible for a regex to follow a `)`: after
295 // a single-line `if()`, `while()`, ... statement, for example
296 // since, when they occur like that, they're always the start of a
297 // statement, there's only a limited amount of ways they can be useful:
298 // by calling the regex methods directly
299 // if a regex following `)` is not followed by `.<property or method>`,
300 // it's quite likely not a regex
301 $before = '\)\s*';
302 $after = '(?=\s*\.('.implode('|', $propertiesAndMethods).'))';
303 $this->registerPattern('/'.$before.'\K'.$pattern.$after.'/', $callback);
304
305 // 1 more edge case: a regex can be followed by a lot more operators or
306 // keywords if there's a newline (ASI) in between, where the operator
307 // actually starts a new statement
308 // (https://github.com/matthiasmullie/minify/issues/56)
309 $operators = $this->getOperatorsForRegex($this->operatorsBefore, '/');
310 $operators += $this->getOperatorsForRegex($this->keywordsReserved, '/');
311 $after = '(?=\s*\n\s*('.implode('|', $operators).'))';
312 $this->registerPattern('/'.$pattern.$after.'/', $callback);
313 }
314
315 /**
316 * Strip whitespace.
317 *
318 * We won't strip *all* whitespace, but as much as possible. The thing that
319 * we'll preserve are newlines we're unsure about.
320 * JavaScript doesn't require statements to be terminated with a semicolon.
321 * It will automatically fix missing semicolons with ASI (automatic semi-
322 * colon insertion) at the end of line causing errors (without semicolon.)
323 *
324 * Because it's sometimes hard to tell if a newline is part of a statement
325 * that should be terminated or not, we'll just leave some of them alone.
326 *
327 * @param string $content The content to strip the whitespace for
328 *
329 * @return string
330 */
331 protected function stripWhitespace($content)
332 {
333 // uniform line endings, make them all line feed
334 $content = str_replace(array("\r\n", "\r"), "\n", $content);
335
336 // collapse all non-line feed whitespace into a single space
337 $content = preg_replace('/[^\S\n]+/', ' ', $content);
338
339 // strip leading & trailing whitespace
340 $content = str_replace(array(" \n", "\n "), "\n", $content);
341
342 // collapse consecutive line feeds into just 1
343 $content = preg_replace('/\n+/', "\n", $content);
344
345 $operatorsBefore = $this->getOperatorsForRegex($this->operatorsBefore, '/');
346 $operatorsAfter = $this->getOperatorsForRegex($this->operatorsAfter, '/');
347 $operators = $this->getOperatorsForRegex($this->operators, '/');
348 $keywordsBefore = $this->getKeywordsForRegex($this->keywordsBefore, '/');
349 $keywordsAfter = $this->getKeywordsForRegex($this->keywordsAfter, '/');
350
351 // strip whitespace that ends in (or next line begin with) an operator
352 // that allows statements to be broken up over multiple lines
353 unset($operatorsBefore['+'], $operatorsBefore['-'], $operatorsAfter['+'], $operatorsAfter['-']);
354 $content = preg_replace(
355 array(
356 '/('.implode('|', $operatorsBefore).')\s+/',
357 '/\s+('.implode('|', $operatorsAfter).')/',
358 ),
359 '\\1',
360 $content
361 );
362
363 // make sure + and - can't be mistaken for, or joined into ++ and --
364 $content = preg_replace(
365 array(
366 '/(?<![\+\-])\s*([\+\-])(?![\+\-])/',
367 '/(?<![\+\-])([\+\-])\s*(?![\+\-])/',
368 ),
369 '\\1',
370 $content
371 );
372
373 // collapse whitespace around reserved words into single space
374 $content = preg_replace('/(^|[;\}\s])\K('.implode('|', $keywordsBefore).')\s+/', '\\2 ', $content);
375 $content = preg_replace('/\s+('.implode('|', $keywordsAfter).')(?=([;\{\s]|$))/', ' \\1', $content);
376
377 /*
378 * We didn't strip whitespace after a couple of operators because they
379 * could be used in different contexts and we can't be sure it's ok to
380 * strip the newlines. However, we can safely strip any non-line feed
381 * whitespace that follows them.
382 */
383 $operatorsDiffBefore = array_diff($operators, $operatorsBefore);
384 $operatorsDiffAfter = array_diff($operators, $operatorsAfter);
385 $content = preg_replace('/('.implode('|', $operatorsDiffBefore).')[^\S\n]+/', '\\1', $content);
386 $content = preg_replace('/[^\S\n]+('.implode('|', $operatorsDiffAfter).')/', '\\1', $content);
387
388 /*
389 * Whitespace after `return` can be omitted in a few occasions
390 * (such as when followed by a string or regex)
391 * Same for whitespace in between `)` and `{`, or between `{` and some
392 * keywords.
393 */
394 $content = preg_replace('/\breturn\s+(["\'\/\+\-])/', 'return$1', $content);
395 $content = preg_replace('/\)\s+\{/', '){', $content);
396 $content = preg_replace('/}\n(else|catch|finally)\b/', '}$1', $content);
397
398 /*
399 * Get rid of double semicolons, except where they can be used like:
400 * "for(v=1,_=b;;)", "for(v=1;;v++)" or "for(;;ja||(ja=true))".
401 * I'll safeguard these double semicolons inside for-loops by
402 * temporarily replacing them with an invalid condition: they won't have
403 * a double semicolon and will be easy to spot to restore afterwards.
404 */
405 $content = preg_replace('/\bfor\(([^;]*);;([^;]*)\)/', 'for(\\1;-;\\2)', $content);
406 $content = preg_replace('/;+/', ';', $content);
407 $content = preg_replace('/\bfor\(([^;]*);-;([^;]*)\)/', 'for(\\1;;\\2)', $content);
408
409 /*
410 * Next, we'll be removing all semicolons where ASI kicks in.
411 * for-loops however, can have an empty body (ending in only a
412 * semicolon), like: `for(i=1;i<3;i++);`, of `for(i in list);`
413 * Here, nothing happens during the loop; it's just used to keep
414 * increasing `i`. With that ; omitted, the next line would be expected
415 * to be the for-loop's body... Same goes for while loops.
416 * I'm going to double that semicolon (if any) so after the next line,
417 * which strips semicolons here & there, we're still left with this one.
418 */
419 $content = preg_replace('/(for\([^;\{]*;[^;\{]*;[^;\{]*\));(\}|$)/s', '\\1;;\\2', $content);
420 $content = preg_replace('/(for\([^;\{]+\s+in\s+[^;\{]+\));(\}|$)/s', '\\1;;\\2', $content);
421 /*
422 * Below will also keep `;` after a `do{}while();` along with `while();`
423 * While these could be stripped after do-while, detecting this
424 * distinction is cumbersome, so I'll play it safe and make sure `;`
425 * after any kind of `while` is kept.
426 */
427 $content = preg_replace('/(while\([^;\{]+\));(\}|$)/s', '\\1;;\\2', $content);
428
429 /*
430 * We also can't strip empty else-statements. Even though they're
431 * useless and probably shouldn't be in the code in the first place, we
432 * shouldn't be stripping the `;` that follows it as it breaks the code.
433 * We can just remove those useless else-statements completely.
434 *
435 * @see https://github.com/matthiasmullie/minify/issues/91
436 */
437 $content = preg_replace('/else;/s', '', $content);
438
439 /*
440 * We also don't really want to terminate statements followed by closing
441 * curly braces (which we've ignored completely up until now) or end-of-
442 * script: ASI will kick in here & we're all about minifying.
443 * Semicolons at beginning of the file don't make any sense either.
444 */
445 $content = preg_replace('/;(\}|$)/s', '\\1', $content);
446 $content = ltrim($content, ';');
447
448 // get rid of remaining whitespace af beginning/end
449 return trim($content);
450 }
451
452 /**
453 * We'll strip whitespace around certain operators with regular expressions.
454 * This will prepare the given array by escaping all characters.
455 *
456 * @param string[] $operators
457 * @param string $delimiter
458 *
459 * @return string[]
460 */
461 protected function getOperatorsForRegex(array $operators, $delimiter = '/')
462 {
463 // escape operators for use in regex
464 $delimiters = array_fill(0, count($operators), $delimiter);
465 $escaped = array_map('preg_quote', $operators, $delimiters);
466
467 $operators = array_combine($operators, $escaped);
468
469 // ignore + & - for now, they'll get special treatment
470 unset($operators['+'], $operators['-']);
471
472 // dot can not just immediately follow a number; it can be confused for
473 // decimal point, or calling a method on it, e.g. 42 .toString()
474 $operators['.'] = '(?<![0-9]\s)\.';
475
476 // don't confuse = with other assignment shortcuts (e.g. +=)
477 $chars = preg_quote('+-*\=<>%&|', $delimiter);
478 $operators['='] = '(?<!['.$chars.'])\=';
479
480 return $operators;
481 }
482
483 /**
484 * We'll strip whitespace around certain keywords with regular expressions.
485 * This will prepare the given array by escaping all characters.
486 *
487 * @param string[] $keywords
488 * @param string $delimiter
489 *
490 * @return string[]
491 */
492 protected function getKeywordsForRegex(array $keywords, $delimiter = '/')
493 {
494 // escape keywords for use in regex
495 $delimiter = array_fill(0, count($keywords), $delimiter);
496 $escaped = array_map('preg_quote', $keywords, $delimiter);
497
498 // add word boundaries
499 array_walk($keywords, function ($value) {
500 return '\b'.$value.'\b';
501 });
502
503 $keywords = array_combine($keywords, $escaped);
504
505 return $keywords;
506 }
507
508 /**
509 * Replaces all occurrences of array['key'] by array.key.
510 *
511 * @param string $content
512 *
513 * @return string
514 */
515 protected function propertyNotation($content)
516 {
517 // PHP only supports $this inside anonymous functions since 5.4
518 $minifier = $this;
519 $keywords = $this->keywordsReserved;
520 $callback = function ($match) use ($minifier, $keywords) {
521 $property = trim($minifier->extracted[$match[1]], '\'"');
522
523 /*
524 * Check if the property is a reserved keyword. In this context (as
525 * property of an object literal/array) it shouldn't matter, but IE8
526 * freaks out with "Expected identifier".
527 */
528 if (in_array($property, $keywords)) {
529 return $match[0];
530 }
531
532 /*
533 * See if the property is in a variable-like format (e.g.
534 * array['key-here'] can't be replaced by array.key-here since '-'
535 * is not a valid character there.
536 */
537 if (!preg_match('/^'.$minifier::REGEX_VARIABLE.'$/u', $property)) {
538 return $match[0];
539 }
540
541 return '.'.$property;
542 };
543
544 /*
545 * Figure out if previous character is a variable name (of the array
546 * we want to use property notation on) - this is to make sure
547 * standalone ['value'] arrays aren't confused for keys-of-an-array.
548 * We can (and only have to) check the last character, because PHP's
549 * regex implementation doesn't allow unfixed-length look-behind
550 * assertions.
551 */
552 preg_match('/(\[[^\]]+\])[^\]]*$/', static::REGEX_VARIABLE, $previousChar);
553 $previousChar = $previousChar[1];
554
555 /*
556 * Make sure word preceding the ['value'] is not a keyword, e.g.
557 * return['x']. Because -again- PHP's regex implementation doesn't allow
558 * unfixed-length look-behind assertions, I'm just going to do a lot of
559 * separate look-behind assertions, one for each keyword.
560 */
561 $keywords = $this->getKeywordsForRegex($keywords);
562 $keywords = '(?<!'.implode(')(?<!', $keywords).')';
563
564 return preg_replace_callback('/(?<='.$previousChar.'|\])'.$keywords.'\[\s*(([\'"])[0-9]+\\2)\s*\]/u', $callback, $content);
565 }
566
567 /**
568 * Replaces true & false by !0 and !1.
569 *
570 * @param string $content
571 *
572 * @return string
573 */
574 protected function shortenBools($content)
575 {
576 /*
577 * 'true' or 'false' could be used as property names (which may be
578 * followed by whitespace) - we must not replace those!
579 * Since PHP doesn't allow variable-length (to account for the
580 * whitespace) lookbehind assertions, I need to capture the leading
581 * character and check if it's a `.`
582 */
583 $callback = function ($match) {
584 if (trim($match[1]) === '.') {
585 return $match[0];
586 }
587
588 return $match[1].($match[2] === 'true' ? '!0' : '!1');
589 };
590 $content = preg_replace_callback('/(^|.\s*)\b(true|false)\b(?!:)/', $callback, $content);
591
592 // for(;;) is exactly the same as while(true), but shorter :)
593 $content = preg_replace('/\bwhile\(!0\){/', 'for(;;){', $content);
594
595 // now make sure we didn't turn any do ... while(true) into do ... for(;;)
596 preg_match_all('/\bdo\b/', $content, $dos, PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
597
598 // go backward to make sure positional offsets aren't altered when $content changes
599 $dos = array_reverse($dos);
600 foreach ($dos as $do) {
601 $offsetDo = $do[0][1];
602
603 // find all `while` (now `for`) following `do`: one of those must be
604 // associated with the `do` and be turned back into `while`
605 preg_match_all('/\bfor\(;;\)/', $content, $whiles, PREG_OFFSET_CAPTURE | PREG_SET_ORDER, $offsetDo);
606 foreach ($whiles as $while) {
607 $offsetWhile = $while[0][1];
608
609 $open = substr_count($content, '{', $offsetDo, $offsetWhile - $offsetDo);
610 $close = substr_count($content, '}', $offsetDo, $offsetWhile - $offsetDo);
611 if ($open === $close) {
612 // only restore `while` if amount of `{` and `}` are the same;
613 // otherwise, that `for` isn't associated with this `do`
614 $content = substr_replace($content, 'while(!0)', $offsetWhile, strlen('for(;;)'));
615 break;
616 }
617 }
618 }
619
620 return $content;
621 }
622 }
623