From 681885762dea27319c63d63b6403d5c3143b2f66 Mon Sep 17 00:00:00 2001 From: Steve Clay Date: Fri, 20 Sep 2013 20:09:51 -0400 Subject: [PATCH] Fixes #73: Handles regex literals directly following return/typeof --- min/lib/JSMin.php | 34 +++++++++----- min_unit_tests/_test_files/js/regexes.js | 6 ++- min_unit_tests/_test_files/js/regexes.min.js | 2 +- min_unit_tests/test_JSMin.php | 48 ++++++++++++++------ 4 files changed, 61 insertions(+), 29 deletions(-) diff --git a/min/lib/JSMin.php b/min/lib/JSMin.php index c84dd84..453eff6 100644 --- a/min/lib/JSMin.php +++ b/min/lib/JSMin.php @@ -272,23 +272,33 @@ class JSMin { // we obviously aren't dividing return true; } - if ($this->a === ' ' || $this->a === "\n") { - $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; } diff --git a/min_unit_tests/_test_files/js/regexes.js b/min_unit_tests/_test_files/js/regexes.js index ef10ad9..57e083e 100644 --- a/min_unit_tests/_test_files/js/regexes.js +++ b/min_unit_tests/_test_files/js/regexes.js @@ -17,4 +17,8 @@ x = / [/] /; (2) -/ foo; \ No newline at end of file +/ foo; + +function(){return/foo/}; + +function(){return typeof/foo/}; diff --git a/min_unit_tests/_test_files/js/regexes.min.js b/min_unit_tests/_test_files/js/regexes.min.js index 934039a..d3c80a6 100644 --- a/min_unit_tests/_test_files/js/regexes.min.js +++ b/min_unit_tests/_test_files/js/regexes.min.js @@ -1,3 +1,3 @@ function testIssue74(){return /'/;} !function(s){return /^[£$€?.]/.test(s);}();typeof -/ ' /;x=/ [/] /;1/foo;(2)/foo; \ No newline at end of file +/ ' /;x=/ [/] /;1/foo;(2)/foo;function(){return/foo/};function(){return typeof/foo/}; \ No newline at end of file diff --git a/min_unit_tests/test_JSMin.php b/min_unit_tests/test_JSMin.php index ef743c4..ef5d114 100644 --- a/min_unit_tests/test_JSMin.php +++ b/min_unit_tests/test_JSMin.php @@ -55,21 +55,39 @@ function test_JSMin() echo "\n---Output: " .countBytes($minOutput). " bytes\n\n{$minOutput}\n\n"; echo "---Expected: " .countBytes($minExpected). " bytes\n\n{$minExpected}\n\n"; echo "---Source: " .countBytes($src). " bytes\n\n{$src}\n\n\n"; - - // only test exceptions on this page - test_JSMin_exception('"Hello' - ,'Unterminated String' - ,'JSMin_UnterminatedStringException' - ,"JSMin: Unterminated String at byte 6: \"Hello"); - test_JSMin_exception("return /regexp\n}" - ,'Unterminated RegExp' - ,'JSMin_UnterminatedRegExpException' - ,"JSMin: Unterminated RegExp at byte 15: /regexp\n"); - test_JSMin_exception("/* Comment " - ,'Unterminated Comment' - ,'JSMin_UnterminatedCommentException' - ,"JSMin: Unterminated comment at byte 11: /* Comment "); } + + test_JSMin_exception('"Hello' + ,'Unterminated String' + ,'JSMin_UnterminatedStringException' + ,"JSMin: Unterminated String at byte 6: \"Hello"); + + test_JSMin_exception("return /regexp\n}" + ,'Unterminated RegExp' + ,'JSMin_UnterminatedRegExpException' + ,"JSMin: Unterminated RegExp at byte 15: /regexp\n"); + test_JSMin_exception("return/regexp\n}" + ,'Unterminated RegExp' + ,'JSMin_UnterminatedRegExpException' + ,"JSMin: Unterminated RegExp at byte 14: /regexp\n"); + test_JSMin_exception(";return/regexp\n}" + ,'Unterminated RegExp' + ,'JSMin_UnterminatedRegExpException' + ,"JSMin: Unterminated RegExp at byte 15: /regexp\n"); + test_JSMin_exception(";return /regexp\n}" + ,'Unterminated RegExp' + ,'JSMin_UnterminatedRegExpException' + ,"JSMin: Unterminated RegExp at byte 16: /regexp\n"); + + test_JSMin_exception("typeof/regexp\n}" + ,'Unterminated RegExp' + ,'JSMin_UnterminatedRegExpException' + ,"JSMin: Unterminated RegExp at byte 14: /regexp\n"); + + test_JSMin_exception("/* Comment " + ,'Unterminated Comment' + ,'JSMin_UnterminatedCommentException' + ,"JSMin: Unterminated comment at byte 11: /* Comment "); } function test_JSMin_exception($js, $label, $expClass, $expMessage) { @@ -82,7 +100,7 @@ function test_JSMin_exception($js, $label, $expClass, $expMessage) { } $passed = assertTrue($eClass === $expClass && $eMsg === $expMessage, 'JSMin : throw on ' . $label); - if (! $passed && __FILE__ === realpath($_SERVER['SCRIPT_FILENAME'])) { + if (! $passed && isset($e) && (__FILE__ === realpath($_SERVER['SCRIPT_FILENAME']))) { echo "\n ---" , $e, "\n\n"; } }