| @@ -1,5 +1,8 @@ | ||
| 1 | 1 | <?php |
| 2 | + | |
| 3 | +//namespace JSMin; | |
| 4 | + | |
| 2 | 5 | /** |
| 3 | 6 | * JSMin.php - modified PHP implementation of Douglas Crockford's JSMin. |
| 4 | 7 | * |
| 5 | 8 | * <code> |
| @@ -52,14 +55,9 @@ | ||
| 52 | 55 | * @copyright 2008 Ryan Grove <[email protected]> (PHP port) |
| 53 | 56 | * @license http://opensource.org/licenses/mit-license.php MIT License |
| 54 | 57 | * @link http://code.google.com/p/jsmin-php/ |
| 55 | 58 | */ |
| 56 | - | |
| 57 | -// This is from https://github.com/mrclay/jsmin-php 2.3.2 | |
| 58 | - | |
| 59 | 59 | class JSMin { |
| 60 | - const ORD_LF = 10; | |
| 61 | - const ORD_SPACE = 32; | |
| 62 | 60 | const ACTION_KEEP_A = 1; |
| 63 | 61 | const ACTION_DELETE_A = 2; |
| 64 | 62 | const ACTION_DELETE_A_B = 3; |
| 65 | 63 | |
| @@ -122,9 +120,9 @@ | ||
| 122 | 120 | |
| 123 | 121 | while ($this->a !== null) { |
| 124 | 122 | // determine next command |
| 125 | 123 | $command = self::ACTION_KEEP_A; // default |
| 126 | - if ($this->a === ' ') { | |
| 124 | + if ($this->isWhiteSpace($this->a)) { | |
| 127 | 125 | if (($this->lastByteOut === '+' || $this->lastByteOut === '-') |
| 128 | 126 | && ($this->b === $this->lastByteOut)) { |
| 129 | 127 | // Don't delete this space. If we do, the addition/subtraction |
| 130 | 128 | // could be parsed as a post-increment |
| @@ -130,23 +128,23 @@ | ||
| 130 | 128 | // could be parsed as a post-increment |
| 131 | 129 | } elseif (! $this->isAlphaNum($this->b)) { |
| 132 | 130 | $command = self::ACTION_DELETE_A; |
| 133 | 131 | } |
| 134 | - } elseif ($this->a === "\n") { | |
| 135 | - if ($this->b === ' ') { | |
| 132 | + } elseif ($this->isLineTerminator($this->a)) { | |
| 133 | + if ($this->isWhiteSpace($this->b)) { | |
| 136 | 134 | $command = self::ACTION_DELETE_A_B; |
| 137 | 135 | |
| 138 | 136 | // in case of mbstring.func_overload & 2, must check for null b, |
| 139 | 137 | // otherwise mb_strpos will give WARNING |
| 140 | 138 | } elseif ($this->b === null |
| 141 | - || (false === strpos('{[(+-!~', $this->b) | |
| 139 | + || (false === strpos('{[(+-!~#', $this->b) | |
| 142 | 140 | && ! $this->isAlphaNum($this->b))) { |
| 143 | 141 | $command = self::ACTION_DELETE_A; |
| 144 | 142 | } |
| 145 | 143 | } elseif (! $this->isAlphaNum($this->a)) { |
| 146 | - if ($this->b === ' ' | |
| 147 | - || ($this->b === "\n" | |
| 148 | - && (false === strpos('}])+-"\'', $this->a)))) { | |
| 144 | + if ($this->isWhiteSpace($this->b) | |
| 145 | + || ($this->isLineTerminator($this->b) | |
| 146 | + && (false === strpos('}])+-"\'`', $this->a)))) { | |
| 149 | 147 | $command = self::ACTION_DELETE_A_B; |
| 150 | 148 | } |
| 151 | 149 | } |
| 152 | 150 | $this->action($command); |
| @@ -164,9 +162,9 @@ | ||
| 164 | 162 | * ACTION_DELETE_A = Copy B to A. Get the next B. |
| 165 | 163 | * ACTION_DELETE_A_B = Get the next B. |
| 166 | 164 | * |
| 167 | 165 | * @param int $command |
| 168 | - * @throws JSMin_UnterminatedRegExpException|JSMin_UnterminatedStringException | |
| 166 | + * @throws JSMin_UnterminatedRegExpException|UnterminatedStringException | |
| 169 | 167 | */ |
| 170 | 168 | protected function action($command) |
| 171 | 169 | { |
| 172 | 170 | // make sure we don't compress "a + ++b" to "a+++b", etc. |
| @@ -195,9 +193,10 @@ | ||
| 195 | 193 | |
| 196 | 194 | // fallthrough intentional |
| 197 | 195 | case self::ACTION_DELETE_A: // 2 |
| 198 | 196 | $this->a = $this->b; |
| 199 | - if ($this->a === "'" || $this->a === '"') { // string literal | |
| 197 | + if ($this->a === "'" || $this->a === '"' || $this->a === '`') { // string/template literal | |
| 198 | + $delimiter = $this->a; | |
| 200 | 199 | $str = $this->a; // in case needed for exception |
| 201 | 200 | for(;;) { |
| 202 | 201 | $this->output .= $this->a; |
| 203 | 202 | $this->lastByteOut = $this->a; |
| @@ -205,9 +204,11 @@ | ||
| 205 | 204 | $this->a = $this->get(); |
| 206 | 205 | if ($this->a === $this->b) { // end quote |
| 207 | 206 | break; |
| 208 | 207 | } |
| 209 | - if ($this->isEOF($this->a)) { | |
| 208 | + if ($delimiter === '`' && $this->isLineTerminator($this->a)) { | |
| 209 | + // leave the newline | |
| 210 | + } elseif ($this->isEOF($this->a)) { | |
| 210 | 211 | $byte = $this->inputIndex - 1; |
| 211 | 212 | throw new JSMin_UnterminatedStringException( |
| 212 | 213 | "JSMin: Unterminated String at byte {$byte}: {$str}"); |
| 213 | 214 | } |
| @@ -215,9 +216,9 @@ | ||
| 215 | 216 | if ($this->a === '\\') { |
| 216 | 217 | $this->output .= $this->a; |
| 217 | 218 | $this->lastByteOut = $this->a; |
| 218 | 219 | |
| 219 | - $this->a = $this->get(); | |
| 220 | + $this->a = $this->get(); | |
| 220 | 221 | $str .= $this->a; |
| 221 | 222 | } |
| 222 | 223 | } |
| 223 | 224 | } |
| @@ -283,9 +284,9 @@ | ||
| 283 | 284 | } |
| 284 | 285 | |
| 285 | 286 | // check if first non-ws token is "/" (see starts-regex.js) |
| 286 | 287 | $length = strlen($this->output); |
| 287 | - if ($this->a === ' ' || $this->a === "\n") { | |
| 288 | + if ($this->isWhiteSpace($this->a) || $this->isLineTerminator($this->a)) { | |
| 288 | 289 | if ($length < 2) { // weird edge case |
| 289 | 290 | return true; |
| 290 | 291 | } |
| 291 | 292 | } |
| @@ -305,9 +306,9 @@ | ||
| 305 | 306 | return false; |
| 306 | 307 | } |
| 307 | 308 | |
| 308 | 309 | // it's a regexp. Remove unneeded whitespace after keyword |
| 309 | - if ($this->a === ' ' || $this->a === "\n") { | |
| 310 | + if ($this->isWhiteSpace($this->a) || $this->isLineTerminator($this->a)) { | |
| 310 | 311 | $this->a = ''; |
| 311 | 312 | } |
| 312 | 313 | |
| 313 | 314 | return true; |
| @@ -331,15 +332,12 @@ | ||
| 331 | 332 | } else { |
| 332 | 333 | $c = null; |
| 333 | 334 | } |
| 334 | 335 | } |
| 335 | - if (ord($c) >= self::ORD_SPACE || $c === "\n" || $c === null) { | |
| 336 | - return $c; | |
| 337 | - } | |
| 338 | 336 | if ($c === "\r") { |
| 339 | 337 | return "\n"; |
| 340 | 338 | } |
| 341 | - return ' '; | |
| 339 | + return $c; | |
| 342 | 340 | } |
| 343 | 341 | |
| 344 | 342 | /** |
| 345 | 343 | * Does $a indicate end of input? |
| @@ -348,9 +346,9 @@ | ||
| 348 | 346 | * @return bool |
| 349 | 347 | */ |
| 350 | 348 | protected function isEOF($a) |
| 351 | 349 | { |
| 352 | - return ord($a) <= self::ORD_LF; | |
| 350 | + return $a === null || $this->isLineTerminator($a); | |
| 353 | 351 | } |
| 354 | 352 | |
| 355 | 353 | /** |
| 356 | 354 | * Get next char (without getting it). If is ctrl character, translate to a space or newline. |
| @@ -383,9 +381,9 @@ | ||
| 383 | 381 | $comment = ''; |
| 384 | 382 | while (true) { |
| 385 | 383 | $get = $this->get(); |
| 386 | 384 | $comment .= $get; |
| 387 | - if (ord($get) <= self::ORD_LF) { // end of line reached | |
| 385 | + if ($this->isEOF($get)) { | |
| 388 | 386 | // if IE conditional comment |
| 389 | 387 | if (preg_match('/^\\/@(?:cc_on|if|elif|else|end)\\b/', $comment)) { |
| 390 | 388 | $this->keptComment .= "/{$comment}"; |
| 391 | 389 | } |
| @@ -449,8 +447,18 @@ | ||
| 449 | 447 | break; |
| 450 | 448 | } |
| 451 | 449 | } |
| 452 | 450 | return $get; |
| 451 | + } | |
| 452 | + | |
| 453 | + protected function isWhiteSpace($s) { | |
| 454 | + // https://www.ecma-international.org/ecma-262/#sec-white-space | |
| 455 | + return $s !== null && strpos(" \t\v\f", $s) !== false; | |
| 456 | + } | |
| 457 | + | |
| 458 | + protected function isLineTerminator($s) { | |
| 459 | + // https://www.ecma-international.org/ecma-262/#sec-line-terminators | |
| 460 | + return $s !== null && strpos("\n\r", $s) !== false; | |
| 453 | 461 | } |
| 454 | 462 | } |
| 455 | 463 | |
| 456 | 464 | class JSMin_UnterminatedStringException extends Exception {} |