| Index: trunk/phase3/tests/qunit/index.html |
| — | — | @@ -79,6 +79,7 @@ |
| 80 | 80 | <script src="data/testrunner.js"></script> |
| 81 | 81 | |
| 82 | 82 | <!-- QUnit: Load test suites (maintain the same order as above please) --> |
| | 83 | + <script src="suites/resources/mediawiki/mediawiki.jscompat.test.js"></script> |
| 83 | 84 | <script src="suites/resources/mediawiki/mediawiki.test.js"></script> |
| 84 | 85 | <script src="suites/resources/mediawiki/mediawiki.user.test.js"></script> |
| 85 | 86 | |
| Index: trunk/phase3/tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js |
| — | — | @@ -0,0 +1,35 @@ |
| | 2 | +/* Some misc JavaScript compatibility tests, just to make sure the environments we run in are consistent */ |
| | 3 | + |
| | 4 | +module( 'mediawiki.jscompat' ); |
| | 5 | + |
| | 6 | +test( 'Variable with Unicode letter in name', function() { |
| | 7 | + expect(3); |
| | 8 | + var orig = "some token"; |
| | 9 | + var ŝablono = orig; |
| | 10 | + deepEqual( ŝablono, orig, 'ŝablono' ); |
| | 11 | + deepEqual( \u015dablono, orig, '\\u015dablono' ); |
| | 12 | + deepEqual( \u015Dablono, orig, '\\u015Dablono' ); |
| | 13 | +}); |
| | 14 | + |
| | 15 | +/* |
| | 16 | +// Not that we need this. ;) |
| | 17 | +// This fails on IE 6-8 |
| | 18 | +// Works on IE 9, Firefox 6, Chrome 14 |
| | 19 | +test( 'Keyword workaround: "if" as variable name using Unicode escapes', function() { |
| | 20 | + var orig = "another token"; |
| | 21 | + \u0069\u0066 = orig; |
| | 22 | + deepEqual( \u0069\u0066, orig, '\\u0069\\u0066' ); |
| | 23 | +}); |
| | 24 | +*/ |
| | 25 | + |
| | 26 | +/* |
| | 27 | +// Not that we need this. ;) |
| | 28 | +// This fails on IE 6-9 |
| | 29 | +// Works on Firefox 6, Chrome 14 |
| | 30 | +test( 'Keyword workaround: "if" as member variable name using Unicode escapes', function() { |
| | 31 | + var orig = "another token"; |
| | 32 | + var foo = {}; |
| | 33 | + foo.\u0069\u0066 = orig; |
| | 34 | + deepEqual( foo.\u0069\u0066, orig, 'foo.\\u0069\\u0066' ); |
| | 35 | +}); |
| | 36 | +*/ |
| Property changes on: trunk/phase3/tests/qunit/suites/resources/mediawiki/mediawiki.jscompat.test.js |
| ___________________________________________________________________ |
| Added: svn:eol-style |
| 1 | 37 | + native |
| Index: trunk/phase3/tests/phpunit/includes/libs/JavaScriptMinifierTest.php |
| — | — | @@ -78,6 +78,12 @@ |
| 79 | 79 | |
| 80 | 80 | // newline insertion after 1000 chars: break after the "++", not before |
| 81 | 81 | array( str_repeat( ';', 996 ) . "if(x++);", str_repeat( ';', 996 ) . "if(x++\n);" ), |
| | 82 | + |
| | 83 | + // Unicode letter characters should pass through ok in identifiers (bug 31187) |
| | 84 | + array( "var KaŝSkatolVal = {}", 'var KaŝSkatolVal={}'), |
| | 85 | + // And also per spec unicode char escape values should work in identifiers, |
| | 86 | + // as long as it's a valid char. In future it might get normalized. |
| | 87 | + array( "var Ka\\u015dSkatolVal = {}", 'var Ka\\u015dSkatolVal={}'), |
| 82 | 88 | ); |
| 83 | 89 | } |
| 84 | 90 | |
| Index: trunk/phase3/includes/libs/jsminplus.php |
| — | — | @@ -2029,13 +2029,55 @@ |
| 2030 | 2030 | break; |
| 2031 | 2031 | |
| 2032 | 2032 | default: |
| 2033 | | - // FIXME: add support for unicode and unicode escape sequence \uHHHH |
| 2034 | | - if (preg_match('/^[$\w]+/', $input, $match)) |
| | 2033 | + // Fast path for identifiers: word chars followed by whitespace or various other tokens. |
| | 2034 | + // Note we don't need to exclude digits in the first char, as they've already been found |
| | 2035 | + // above. |
| | 2036 | + if (!preg_match('/^[$\w]+(?=[\s\/\|\^\&<>\+\-\*%=!.;,\?:~\[\]\{\}\(\)@])/', $input, $match)) |
| 2035 | 2037 | { |
| 2036 | | - $tt = in_array($match[0], $this->keywords) ? $match[0] : TOKEN_IDENTIFIER; |
| | 2038 | + // Character classes per ECMA-262 edition 5.1 section 7.6 |
| | 2039 | + // Per spec, must accept Unicode 3.0, *may* accept later versions. |
| | 2040 | + // We'll take whatever PCRE understands, which should be more recent. |
| | 2041 | + $identifierStartChars = "\\p{L}\\p{Nl}" . # UnicodeLetter |
| | 2042 | + "\$" . |
| | 2043 | + "_"; |
| | 2044 | + $identifierPartChars = $identifierStartChars . |
| | 2045 | + "\\p{Mn}\\p{Mc}" . # UnicodeCombiningMark |
| | 2046 | + "\\p{Nd}" . # UnicodeDigit |
| | 2047 | + "\\p{Pc}"; # UnicodeConnectorPunctuation |
| | 2048 | + $unicodeEscape = "\\\\u[0-9A-F-a-f]{4}"; |
| | 2049 | + $identifierRegex = "/^" . |
| | 2050 | + "(?:[$identifierStartChars]|$unicodeEscape)" . |
| | 2051 | + "(?:[$identifierPartChars]|$unicodeEscape)*" . |
| | 2052 | + "/uS"; |
| | 2053 | + if (preg_match($identifierRegex, $input, $match)) |
| | 2054 | + { |
| | 2055 | + if (strpos($match[0], '\\') !== false) { |
| | 2056 | + // Per ECMA-262 edition 5.1, section 7.6 escape sequences should behave as if they were |
| | 2057 | + // the original chars, but only within the boundaries of the identifier. |
| | 2058 | + $decoded = preg_replace_callback('/\\\\u([0-9A-Fa-f]{4})/', |
| | 2059 | + array(__CLASS__, 'unicodeEscapeCallback'), |
| | 2060 | + $match[0]); |
| | 2061 | + |
| | 2062 | + // Since our original regex didn't de-escape the originals, we need to check for validity again. |
| | 2063 | + // No need to worry about token boundaries, as anything outside the identifier is illegal! |
| | 2064 | + if (!preg_match("/^[$identifierStartChars][$identifierPartChars]*$/u", $decoded)) { |
| | 2065 | + throw $this->newSyntaxError('Illegal token'); |
| | 2066 | + } |
| | 2067 | + |
| | 2068 | + // Per spec it _ought_ to work to use these escapes for keywords words as well... |
| | 2069 | + // but IE rejects them as invalid, while Firefox and Chrome treat them as identifiers |
| | 2070 | + // that don't match the keyword. |
| | 2071 | + if (in_array($decoded, $this->keywords)) { |
| | 2072 | + throw $this->newSyntaxError('Illegal token'); |
| | 2073 | + } |
| | 2074 | + |
| | 2075 | + // TODO: save the decoded form for output? |
| | 2076 | + } |
| | 2077 | + } |
| | 2078 | + else |
| | 2079 | + throw $this->newSyntaxError('Illegal token'); |
| 2037 | 2080 | } |
| 2038 | | - else |
| 2039 | | - throw $this->newSyntaxError('Illegal token'); |
| | 2081 | + $tt = in_array($match[0], $this->keywords) ? $match[0] : TOKEN_IDENTIFIER; |
| 2040 | 2082 | } |
| 2041 | 2083 | } |
| 2042 | 2084 | |
| — | — | @@ -2073,6 +2115,11 @@ |
| 2074 | 2116 | { |
| 2075 | 2117 | return new Exception('Parse error: ' . $m . ' in file \'' . $this->filename . '\' on line ' . $this->lineno); |
| 2076 | 2118 | } |
| | 2119 | + |
| | 2120 | + public static function unicodeEscapeCallback($m) |
| | 2121 | + { |
| | 2122 | + return html_entity_decode('&#x' . $m[1]. ';', ENT_QUOTES, 'UTF-8'); |
| | 2123 | + } |
| 2077 | 2124 | } |
| 2078 | 2125 | |
| 2079 | 2126 | class JSToken |