diff --git a/lib/output.js b/lib/output.js index 7ab562c6..390b87ce 100644 --- a/lib/output.js +++ b/lib/output.js @@ -118,7 +118,7 @@ function OutputStream(options) { var current_pos = 0; var OUTPUT = ""; - function to_ascii(str, identifier) { + var to_utf8 = options.ascii_only ? function(str, identifier) { return str.replace(/[\ud800-\udbff][\udc00-\udfff]|[\u0000-\u001f\u007f-\uffff]/g, function(ch) { var code = get_full_char_code(ch, 0).toString(16); @@ -139,6 +139,12 @@ function OutputStream(options) { return "\\u" + code; } }); + } : function(str) { + return str.replace(/[\ud800-\udbff](?![\udc00-\udfff])/g, function(ch) { + return "\\u" + ch.charCodeAt(0).toString(16); + }).replace(/(^|[^\ud800-\udbff])([\udc00-\udfff])/g, function(match, prefix, ch) { + return prefix + "\\u" + ch.charCodeAt(0).toString(16); + }); }; function make_string(str, quote) { @@ -172,7 +178,7 @@ function OutputStream(options) { function quote_template() { return '`' + str.replace(/`/g, '\\`') + '`'; } - if (options.ascii_only) str = to_ascii(str); + str = to_utf8(str); if (quote === "`") return quote_template(); switch (options.quote_style) { case 1: @@ -198,8 +204,7 @@ function OutputStream(options) { function make_name(name) { name = name.toString(); - if (options.ascii_identifiers) - name = to_ascii(name, true); + name = to_utf8(name, true); return name; }; @@ -461,7 +466,7 @@ function OutputStream(options) { last : function() { return last }, semicolon : semicolon, force_semicolon : force_semicolon, - to_ascii : to_ascii, + to_utf8 : to_utf8, print_name : function(name) { print(make_name(name)) }, print_string : function(str, quote, escape_directive) { var encoded = encode_string(str, quote); @@ -1713,9 +1718,7 @@ function OutputStream(options) { if (regexp.raw_source) { str = "/" + regexp.raw_source + str.slice(str.lastIndexOf("/")); } - if (output.option("ascii_only")) { - str = output.to_ascii(str); - } + str = output.to_utf8(str); output.print(str); var p = output.parent(); if (p instanceof AST_Binary && /^in/.test(p.operator) && p.left === self) diff --git a/package.json b/package.json index 3ff52057..4e53e07f 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "homepage": "https://github.com/mishoo/UglifyJS2/tree/harmony", "author": "Mihai Bazon (http://lisperator.net/)", "license": "BSD-2-Clause", - "version": "3.0.25", + "version": "3.0.26", "engines": { "node": ">=0.8.0" }, @@ -26,12 +26,12 @@ "LICENSE" ], "dependencies": { - "commander": "~2.9.0", + "commander": "~2.11.0", "source-map": "~0.5.1" }, "devDependencies": { - "acorn": "~5.0.3", - "mocha": "~2.3.4", + "acorn": "~5.1.1", + "mocha": "~3.4.2", "semver": "~5.3.0" }, "scripts": { diff --git a/test/compress/unicode.js b/test/compress/unicode.js index d5853cc6..b2c8e9e9 100644 --- a/test/compress/unicode.js +++ b/test/compress/unicode.js @@ -117,4 +117,44 @@ non_escape_2_half_escape2: { var µþ = "µþ"; } expect_exact: 'var µþ="\\xb5\\xfe";' -} \ No newline at end of file +} + +issue_2242_1: { + beautify = { + ascii_only: false, + } + input: { + console.log("\ud83d", "\ude00", "\ud83d\ude00", "\ud83d@\ude00"); + } + expect_exact: 'console.log("\\ud83d","\\ude00","\ud83d\ude00","\\ud83d@\\ude00");' +} + +issue_2242_2: { + beautify = { + ascii_only: true, + } + input: { + console.log("\ud83d", "\ude00", "\ud83d\ude00", "\ud83d@\ude00"); + } + expect_exact: 'console.log("\\ud83d","\\ude00","\\ud83d\\ude00","\\ud83d@\\ude00");' +} + +issue_2242_3: { + options = { + evaluate: false, + } + input: { + console.log("\ud83d" + "\ude00", "\ud83d" + "@" + "\ude00"); + } + expect_exact: 'console.log("\\ud83d"+"\\ude00","\\ud83d"+"@"+"\\ude00");' +} + +issue_2242_4: { + options = { + evaluate: true, + } + input: { + console.log("\ud83d" + "\ude00", "\ud83d" + "@" + "\ude00"); + } + expect_exact: 'console.log("\ud83d\ude00","\\ud83d@\\ude00");' +} diff --git a/test/mocha/string-literal.js b/test/mocha/string-literal.js index fde6db59..d2eb6a80 100644 --- a/test/mocha/string-literal.js +++ b/test/mocha/string-literal.js @@ -78,4 +78,41 @@ describe("String literals", function() { assert.equal(UglifyJS.parse('"use strict";"\\08"').print_to_string(), '"use strict";"\\08";'); assert.equal(UglifyJS.parse('"use strict";"\\09"').print_to_string(), '"use strict";"\\09";'); }); + + it("Should not unescape unpaired surrogates", function() { + var code = []; + for (var i = 0; i <= 0xF; i++) { + code.push("\\u000" + i.toString(16)); + } + for (;i <= 0xFF; i++) { + code.push("\\u00" + i.toString(16)); + } + for (;i <= 0xFFF; i++) { + code.push("\\u0" + i.toString(16)); + } + for (; i <= 0xFFFF; i++) { + code.push("\\u" + i.toString(16)); + } + code = '"' + code.join() + '"'; + var normal = UglifyJS.minify(code, { + compress: false, + mangle: false, + output: { + ascii_only: false + } + }); + if (normal.error) throw normal.error; + assert.ok(code.length > normal.code.length); + assert.strictEqual(eval(code), eval(normal.code)); + var ascii = UglifyJS.minify(code, { + compress: false, + mangle: false, + output: { + ascii_only: false + } + }); + if (ascii.error) throw ascii.error; + assert.ok(code.length > ascii.code.length); + assert.strictEqual(eval(code), eval(ascii.code)); + }); });