Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge pull request #5295 from mrclay/jsmin-fixes

Update JSMin (in Minify)
  • Loading branch information...
commit 592efd02dabbe42cabb9b4e09ee422b2e3845322 2 parents 0d02c81 + 3583161
@mrclay mrclay authored
View
192 vendors/minify/lib/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 @@ public function min()
// 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 @@ public function min()
} 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 @@ public function min()
*/
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,86 @@ protected function action($command)
$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)) {
throw new JSMin_UnterminatedStringException(
- "JSMin: Unterminated String at byte "
- . $this->inputIndex . ": {$str}");
+ "JSMin: Unterminated String at byte {$this->inputIndex}: {$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)) {
throw new JSMin_UnterminatedRegExpException(
- "JSMin: Unterminated RegExp at byte "
- . $this->inputIndex .": {$pattern}");
+ "JSMin: Unterminated RegExp at byte {$this->inputIndex}: {$pattern}");
}
$this->output .= $this->a;
$this->lastByteOut = $this->a;
@@ -237,10 +268,11 @@ protected function action($command)
*/
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) {
+ if ($this->a === ' ' || $this->a === "\n") {
$length = strlen($this->output);
if ($length < 2) { // weird edge case
return true;
@@ -261,7 +293,8 @@ protected function isRegexpLiteral()
}
/**
- * 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 +303,36 @@ protected function get()
$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 +343,7 @@ protected function peek()
}
/**
- * 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 +351,84 @@ protected function peek()
*/
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;
}
}
View
54 vendors/minify/lib/Minify/HTML/Helper.php
@@ -15,10 +15,10 @@ class Minify_HTML_Helper {
public $minAppUri = '/min';
public $groupsConfigFile = '';
- /*
+ /**
* Get an HTML-escaped Minify URI for a group or set of files
*
- * @param mixed $keyOrFiles a group key or array of filepaths/URIs
+ * @param string|array $keyOrFiles a group key or array of filepaths/URIs
* @param array $opts options:
* 'farExpires' : (default true) append a modified timestamp for cache revving
* 'debug' : (default false) append debug flag
@@ -51,8 +51,12 @@ public static function getUri($keyOrFiles, $opts = array())
return htmlspecialchars($uri, ENT_QUOTES, $opts['charset']);
}
- /*
+ /**
* Get non-HTML-escaped URI to minify the specified files
+ *
+ * @param bool $farExpires
+ * @param bool $debug
+ * @return string
*/
public function getRawUri($farExpires = true, $debug = false)
{
@@ -74,6 +78,12 @@ public function getRawUri($farExpires = true, $debug = false)
return $path;
}
+ /**
+ * Set the files that will comprise the URI we're building
+ *
+ * @param array $files
+ * @param bool $checkLastModified
+ */
public function setFiles($files, $checkLastModified = true)
{
$this->_groupKey = null;
@@ -94,6 +104,12 @@ public function setFiles($files, $checkLastModified = true)
$this->_filePaths = $files;
}
+ /**
+ * Set the group of files that will comprise the URI we're building
+ *
+ * @param string $key
+ * @param bool $checkLastModified
+ */
public function setGroup($key, $checkLastModified = true)
{
$this->_groupKey = $key;
@@ -103,13 +119,23 @@ public function setGroup($key, $checkLastModified = true)
}
if (is_file($this->groupsConfigFile)) {
$gc = (require $this->groupsConfigFile);
- if (isset($gc[$key])) {
- $this->_lastModified = self::getLastModified($gc[$key]);
+ $keys = explode(',', $key);
+ foreach ($keys as $key) {
+ if (isset($gc[$key])) {
+ $this->_lastModified = self::getLastModified($gc[$key], $this->_lastModified);
+ }
}
}
}
}
-
+
+ /**
+ * Get the max(lastModified) of all files
+ *
+ * @param array|string $sources
+ * @param int $lastModified
+ * @return int
+ */
public static function getLastModified($sources, $lastModified = 0)
{
$max = $lastModified;
@@ -142,13 +168,19 @@ public static function getLastModified($sources, $lastModified = 0)
* @return mixed a common char or '' if any do not match
*/
protected static function _getCommonCharAtPos($arr, $pos) {
- $l = count($arr);
+ if (!isset($arr[0][$pos])) {
+ return '';
+ }
$c = $arr[0][$pos];
- if ($c === '' || $l === 1)
+ $l = count($arr);
+ if ($l === 1) {
return $c;
- for ($i = 1; $i < $l; ++$i)
- if ($arr[$i][$pos] !== $c)
+ }
+ for ($i = 1; $i < $l; ++$i) {
+ if ($arr[$i][$pos] !== $c) {
return '';
+ }
+ }
return $c;
}
@@ -157,11 +189,11 @@ protected static function _getCommonCharAtPos($arr, $pos) {
*
* @param array $paths root-relative URIs of files
* @param string $minRoot root-relative URI of the "min" application
+ * @return string
*/
protected static function _getShortestUri($paths, $minRoot = '/min/') {
$pos = 0;
$base = '';
- $c;
while (true) {
$c = self::_getCommonCharAtPos($paths, $pos);
if ($c === '') {
View
9 vendors/minify/lib/Minify/Lines.php
@@ -55,7 +55,11 @@ public static function minify($content, $options = array())
$newLines = array();
while (null !== ($line = array_shift($lines))) {
if (('' !== $id) && (0 == $i % 50)) {
- array_push($newLines, '', "/* {$id} */", '');
+ if ($inComment) {
+ array_push($newLines, '', "/* {$id} *|", '');
+ } else {
+ array_push($newLines, '', "/* {$id} */", '');
+ }
}
++$i;
$newLines[] = self::_addNote($line, $i, $inComment, $padTo);
@@ -92,6 +96,9 @@ public static function minify($content, $options = array())
*/
private static function _eolInComment($line, $inComment)
{
+ // crude way to avoid things like // */
+ $line = preg_replace('~//.*?(\\*/|/\\*).*~', '', $line);
+
while (strlen($line)) {
$search = $inComment
? '*/'
Please sign in to comment.
Something went wrong with that request. Please try again.