diff options
| author | Jano Slota <jano@alokal.eu> | 2014-05-05 22:55:36 +0200 |
|---|---|---|
| committer | czaks <marcin@6irc.net> | 2014-05-07 11:17:32 +0200 |
| commit | c25b8f01c560ca97ca8a9dc217b495966aaa4d7f (patch) | |
| tree | 54d4c0aaa98a7cb581832094de4e707c9ae9ac93 /inc/lib/minify/JSMin.php | |
| parent | 593560956c629fb2df9b97b5afe9c1eb2666652b (diff) | |
Updated minify, jQuery, MixItUp and Tooltipster
Diffstat (limited to 'inc/lib/minify/JSMin.php')
| -rwxr-xr-x | inc/lib/minify/JSMin.php | 226 |
1 files changed, 145 insertions, 81 deletions
diff --git a/inc/lib/minify/JSMin.php b/inc/lib/minify/JSMin.php index b6879f37..9840d8b3 100755 --- a/inc/lib/minify/JSMin.php +++ b/inc/lib/minify/JSMin.php @@ -7,14 +7,14 @@ * </code> * * This is a modified port of jsmin.c. Improvements: - * + * * Does not choke on some regexp literals containing quote characters. E.g. /'/ - * - * Spaces are preserved after some add/sub operators, so they are not mistakenly + * + * Spaces are preserved after some add/sub operators, so they are not mistakenly * converted to post-inc/dec. E.g. a + ++b -> a+ ++b * * Preserves multi-line comments that begin with /*! - * + * * PHP 5 or higher is required. * * Permission is hereby granted to use this version of the library under the @@ -69,6 +69,7 @@ class JSMin { protected $lookAhead = null; protected $output = ''; protected $lastByteOut = ''; + protected $keptComment = ''; /** * Minify Javascript. @@ -116,8 +117,8 @@ class JSMin { // determine next command $command = self::ACTION_KEEP_A; // default if ($this->a === ' ') { - if (($this->lastByteOut === '+' || $this->lastByteOut === '-') - && ($this->b === $this->lastByteOut)) { + if (($this->lastByteOut === '+' || $this->lastByteOut === '-') + && ($this->b === $this->lastByteOut)) { // Don't delete this space. If we do, the addition/subtraction // could be parsed as a post-increment } elseif (! $this->isAlphaNum($this->b)) { @@ -126,16 +127,17 @@ class JSMin { } elseif ($this->a === "\n") { if ($this->b === ' ') { $command = self::ACTION_DELETE_A_B; - // in case of mbstring.func_overload & 2, must check for null b, - // otherwise mb_strpos will give WARNING + + // in case of mbstring.func_overload & 2, must check for null b, + // otherwise mb_strpos will give WARNING } elseif ($this->b === null - || (false === strpos('{[(+-', $this->b) + || (false === strpos('{[(+-!~', $this->b) && ! $this->isAlphaNum($this->b))) { $command = self::ACTION_DELETE_A; } } elseif (! $this->isAlphaNum($this->a)) { if ($this->b === ' ' - || ($this->b === "\n" + || ($this->b === "\n" && (false === strpos('}])+-"\'', $this->a)))) { $command = self::ACTION_DELETE_A_B; } @@ -160,7 +162,8 @@ class JSMin { */ protected function action($command) { - if ($command === self::ACTION_DELETE_A_B + // make sure we don't compress "a + ++b" to "a+++b", etc. + if ($command === self::ACTION_DELETE_A_B && $this->b === ' ' && ($this->a === '+' || $this->a === '-')) { // Note: we're at an addition/substraction operator; the inputIndex @@ -170,58 +173,88 @@ class JSMin { $command = self::ACTION_KEEP_A; } } + switch ($command) { - case self::ACTION_KEEP_A: + case self::ACTION_KEEP_A: // 1 $this->output .= $this->a; + + if ($this->keptComment) { + $this->output = rtrim($this->output, "\n"); + $this->output .= $this->keptComment; + $this->keptComment = ''; + } + $this->lastByteOut = $this->a; - - // fallthrough - case self::ACTION_DELETE_A: + + // fallthrough intentional + case self::ACTION_DELETE_A: // 2 $this->a = $this->b; if ($this->a === "'" || $this->a === '"') { // string literal $str = $this->a; // in case needed for exception - while (true) { + for(;;) { $this->output .= $this->a; $this->lastByteOut = $this->a; - - $this->a = $this->get(); + + $this->a = $this->get(); if ($this->a === $this->b) { // end quote break; } - if (ord($this->a) <= self::ORD_LF) { + if ($this->isEOF($this->a)) { + $byte = $this->inputIndex - 1; throw new JSMin_UnterminatedStringException( - "JSMin: Unterminated String at byte " - . $this->inputIndex . ": {$str}"); + "JSMin: Unterminated String at byte {$byte}: {$str}"); } $str .= $this->a; if ($this->a === '\\') { $this->output .= $this->a; $this->lastByteOut = $this->a; - + $this->a = $this->get(); $str .= $this->a; } } } - // fallthrough - case self::ACTION_DELETE_A_B: + + // fallthrough intentional + case self::ACTION_DELETE_A_B: // 3 $this->b = $this->next(); - if ($this->b === '/' && $this->isRegexpLiteral()) { // RegExp literal + if ($this->b === '/' && $this->isRegexpLiteral()) { $this->output .= $this->a . $this->b; - $pattern = '/'; // in case needed for exception - while (true) { + $pattern = '/'; // keep entire pattern in case we need to report it in the exception + for(;;) { $this->a = $this->get(); $pattern .= $this->a; + if ($this->a === '[') { + for(;;) { + $this->output .= $this->a; + $this->a = $this->get(); + $pattern .= $this->a; + if ($this->a === ']') { + break; + } + if ($this->a === '\\') { + $this->output .= $this->a; + $this->a = $this->get(); + $pattern .= $this->a; + } + if ($this->isEOF($this->a)) { + throw new JSMin_UnterminatedRegExpException( + "JSMin: Unterminated set in RegExp at byte " + . $this->inputIndex .": {$pattern}"); + } + } + } + if ($this->a === '/') { // end pattern break; // while (true) } elseif ($this->a === '\\') { $this->output .= $this->a; - $this->a = $this->get(); - $pattern .= $this->a; - } elseif (ord($this->a) <= self::ORD_LF) { + $this->a = $this->get(); + $pattern .= $this->a; + } elseif ($this->isEOF($this->a)) { + $byte = $this->inputIndex - 1; throw new JSMin_UnterminatedRegExpException( - "JSMin: Unterminated RegExp at byte " - . $this->inputIndex .": {$pattern}"); + "JSMin: Unterminated RegExp at byte {$byte}: {$pattern}"); } $this->output .= $this->a; $this->lastByteOut = $this->a; @@ -237,31 +270,43 @@ class JSMin { */ protected function isRegexpLiteral() { - if (false !== strpos("\n{;(,=:[!&|?", $this->a)) { // we aren't dividing + if (false !== strpos("(,=:[!&|?+-~*{;", $this->a)) { + // we obviously aren't dividing return true; } - if (' ' === $this->a) { - $length = strlen($this->output); - if ($length < 2) { // weird edge case - return true; + + // we have to check for a preceding keyword, and we don't need to pattern + // match over the whole output. + $recentOutput = substr($this->output, -10); + + // check if return/typeof directly precede a pattern without a space + foreach (array('return', 'typeof') as $keyword) { + if ($this->a !== substr($keyword, -1)) { + // certainly wasn't keyword + continue; } - // you can't divide a keyword - if (preg_match('/(?:case|else|in|return|typeof)$/', $this->output, $m)) { - if ($this->output === $m[0]) { // odd but could happen - return true; - } - // make sure it's a keyword, not end of an identifier - $charBeforeKeyword = substr($this->output, $length - strlen($m[0]) - 1, 1); - if (! $this->isAlphaNum($charBeforeKeyword)) { + if (preg_match("~(^|[\\s\\S])" . substr($keyword, 0, -1) . "$~", $recentOutput, $m)) { + if ($m[1] === '' || !$this->isAlphaNum($m[1])) { return true; } } } + + // check all keywords + if ($this->a === ' ' || $this->a === "\n") { + if (preg_match('~(^|[\\s\\S])(?:case|else|in|return|typeof)$~', $recentOutput, $m)) { + if ($m[1] === '' || !$this->isAlphaNum($m[1])) { + return true; + } + } + } + return false; } /** - * Get next char. Convert ctrl char to space. + * Return the next character from stdin. Watch out for lookahead. If the character is a control character, + * translate it to a space or linefeed. * * @return string */ @@ -270,24 +315,36 @@ class JSMin { $c = $this->lookAhead; $this->lookAhead = null; if ($c === null) { + // getc(stdin) if ($this->inputIndex < $this->inputLength) { $c = $this->input[$this->inputIndex]; $this->inputIndex += 1; } else { - return null; + $c = null; } } - if ($c === "\r" || $c === "\n") { - return "\n"; + if (ord($c) >= self::ORD_SPACE || $c === "\n" || $c === null) { + return $c; } - if (ord($c) < self::ORD_SPACE) { // control char - return ' '; + if ($c === "\r") { + return "\n"; } - return $c; + return ' '; + } + + /** + * Does $a indicate end of input? + * + * @param string $a + * @return bool + */ + protected function isEOF($a) + { + return ord($a) <= self::ORD_LF; } /** - * Get next char. If is ctrl character, translate to a space or newline. + * Get next char (without getting it). If is ctrl character, translate to a space or newline. * * @return string */ @@ -298,7 +355,7 @@ class JSMin { } /** - * Is $c a letter, digit, underscore, dollar sign, escape, or non-ASCII? + * Return true if the character is a letter, digit, underscore, dollar sign, or non-ASCII character. * * @param string $c * @@ -306,77 +363,84 @@ class JSMin { */ protected function isAlphaNum($c) { - return (preg_match('/^[0-9a-zA-Z_\\$\\\\]$/', $c) || ord($c) > 126); + return (preg_match('/^[a-z0-9A-Z_\\$\\\\]$/', $c) || ord($c) > 126); } /** - * @return string + * Consume a single line comment from input (possibly retaining it) */ - protected function singleLineComment() + protected function consumeSingleLineComment() { $comment = ''; while (true) { $get = $this->get(); $comment .= $get; - if (ord($get) <= self::ORD_LF) { // EOL reached + if (ord($get) <= self::ORD_LF) { // end of line reached // if IE conditional comment if (preg_match('/^\\/@(?:cc_on|if|elif|else|end)\\b/', $comment)) { - return "/{$comment}"; + $this->keptComment .= "/{$comment}"; } - return $get; + return; } } } /** - * @return string + * Consume a multiple line comment from input (possibly retaining it) + * * @throws JSMin_UnterminatedCommentException */ - protected function multipleLineComment() + protected function consumeMultipleLineComment() { $this->get(); $comment = ''; - while (true) { + for(;;) { $get = $this->get(); if ($get === '*') { if ($this->peek() === '/') { // end of comment reached $this->get(); - // if comment preserved by YUI Compressor if (0 === strpos($comment, '!')) { - return "\n/*!" . substr($comment, 1) . "*/\n"; - } - // if IE conditional comment - if (preg_match('/^@(?:cc_on|if|elif|else|end)\\b/', $comment)) { - return "/*{$comment}*/"; + // preserved by YUI Compressor + if (!$this->keptComment) { + // don't prepend a newline if two comments right after one another + $this->keptComment = "\n"; + } + $this->keptComment .= "/*!" . substr($comment, 1) . "*/\n"; + } else if (preg_match('/^@(?:cc_on|if|elif|else|end)\\b/', $comment)) { + // IE conditional + $this->keptComment .= "/*{$comment}*/"; } - return ' '; + return; } } elseif ($get === null) { throw new JSMin_UnterminatedCommentException( - "JSMin: Unterminated comment at byte " - . $this->inputIndex . ": /*{$comment}"); + "JSMin: Unterminated comment at byte {$this->inputIndex}: /*{$comment}"); } $comment .= $get; } } /** - * Get the next character, skipping over comments. - * Some comments may be preserved. + * Get the next character, skipping over comments. Some comments may be preserved. * * @return string */ protected function next() { $get = $this->get(); - if ($get !== '/') { - return $get; - } - switch ($this->peek()) { - case '/': return $this->singleLineComment(); - case '*': return $this->multipleLineComment(); - default: return $get; + if ($get === '/') { + switch ($this->peek()) { + case '/': + $this->consumeSingleLineComment(); + $get = "\n"; + break; + case '*': + $this->consumeMultipleLineComment(); + $get = ' '; + break; + } } + return $get; } } |
