Completed
Push — master ( 17888a...f6b0e7 )
by Matthias
01:53
created

src/JS.php (1 issue)

Upgrade to new PHP Analysis Engine

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
This method seems to be duplicated in your project.

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.

Loading history...
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