Skip to content

Commit

Permalink
Merge branch 'hjtappe-RegexpParsing'
Browse files Browse the repository at this point in the history
  • Loading branch information
mrclay committed Mar 30, 2015
2 parents f9fe6ee + a42d247 commit 47cace7
Show file tree
Hide file tree
Showing 11 changed files with 44 additions and 27 deletions.
48 changes: 25 additions & 23 deletions src/JSMin/JSMin.php
Original file line number Diff line number Diff line change
Expand Up @@ -278,37 +278,39 @@ protected function action($command)
protected function isRegexpLiteral()
{
if (false !== strpos("(,=:[!&|?+-~*{;", $this->a)) {
// we obviously aren't dividing
// we can't divide after these tokens
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;
}
if (preg_match("~(^|[\\s\\S])" . substr($keyword, 0, -1) . "$~", $recentOutput, $m)) {
if ($m[1] === '' || !$this->isAlphaNum($m[1])) {
return true;
}
// check if first non-ws token is "/" (see starts-regex.js)
$length = strlen($this->output);
if ($this->a === ' ' || $this->a === "\n") {
if ($length < 2) { // weird edge case
return true;
}
}

// check all keywords
// if the "/" follows a keyword, it must be a regexp, otherwise it's best to assume division

$subject = $this->output . trim($this->a);
if (!preg_match('/(?:case|else|in|return|typeof)$/', $subject, $m)) {
// not a keyword
return false;
}

// can't be sure it's a keyword yet (see not-regexp.js)
$charBeforeKeyword = substr($subject, 0 - strlen($m[0]) - 1, 1);
if ($this->isAlphaNum($charBeforeKeyword)) {
// this is really an identifier ending in a keyword, e.g. "xreturn"
return false;
}

// it's a regexp. Remove unneeded whitespace after keyword
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;
}
}
}
$this->a = '';
}

return false;
return true;
}

/**
Expand Down
1 change: 1 addition & 0 deletions tests/Resources/minify/expected/keyword-regex.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
return/return/;
1 change: 1 addition & 0 deletions tests/Resources/minify/expected/not-regexp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
!function(){return xreturn/foo}();!function(){return xtypeof/foo}();
5 changes: 2 additions & 3 deletions tests/Resources/minify/expected/regexes.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
function testIssue74(){return /'/;}
!function(s){return /^[£$?.]/.test(s);}();typeof
/ ' /;x=/ [/] /;1/foo;(2)/foo;function(){return/foo/};function(){return typeof/foo/};
function testIssue74(){return/'/;}
!function(s){return/^[£$?.]/.test(s);}();typeof/ ' /;x=/ [/] /;1/foo;(2)/foo;function(){return/foo/};function(){return typeof/foo/};
1 change: 1 addition & 0 deletions tests/Resources/minify/expected/starts-regex.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/return/.test(bar);
1 change: 1 addition & 0 deletions tests/Resources/minify/expected/token-regexp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
typeof[/return/];
3 changes: 3 additions & 0 deletions tests/Resources/minify/input/keyword-regex.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// this is specifically designed so that, if the first "/" is misinterpreted as division,
// then "/;" will be interpreted as an incomplete regexp, causing a failing test.
return /return/;
3 changes: 3 additions & 0 deletions tests/Resources/minify/input/not-regexp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
!function(){return xreturn/foo}();

!function(){return xtypeof/foo}();
3 changes: 3 additions & 0 deletions tests/Resources/minify/input/starts-regex.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// this is specifically designed so that, if the first "/" is misinterpreted as division,
// then "/.test(bar);" will be interpreted as an incomplete regexp, causing a failing test.
/return/.test(bar);
3 changes: 3 additions & 0 deletions tests/Resources/minify/input/token-regexp.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// this is specifically designed so that, if the first "/" is misinterpreted as division,
// then "/[;" will be interpreted as an incomplete regexp, causing a failing test.
typeof [/return/];
2 changes: 1 addition & 1 deletion tests/Tests/JSMin/JSMinTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public function testFuncOverload() {
}

$input = 'function(s) { return /^[£$€?.]/.test(s); }';
$expected = 'function(s){return /^[£$€?.]/.test(s);}';
$expected = 'function(s){return/^[£$€?.]/.test(s);}';
$this->assertEquals($expected, JSMin::minify($input));
}

Expand Down

0 comments on commit 47cace7

Please sign in to comment.