diff --git a/README.md b/README.md index a3a1e344..e7d03916 100644 --- a/README.md +++ b/README.md @@ -285,6 +285,21 @@ of mangled property names. Using the name cache is not necessary if you compress all your files in a single call to UglifyJS. +#### Debugging property name mangling + +You can also pass `--mangle-props-debug` in order to mangle property names +without completely obscuring them. For example the property `o.foo` +would mangle to `o._$foo$_` with this option. This allows property mangling +of a large codebase while still being able to debug the code and identify +where mangling is breaking things. + +You can also pass a custom suffix using `--mangle-props-debug=XYZ`. This would then +mangle `o.foo` to `o._$foo$XYZ_`. You can change this each time you compile a +script to identify how a property got mangled. One technique is to pass a +random number on every compile to simulate mangling changing with different +inputs (e.g. as you update the input script with new properties), and to help +identify mistakes like writing mangled keys to storage. + ## Compressor options You need to pass `--compress` (`-c`) to enable the compressor. Optionally @@ -639,15 +654,22 @@ To generate a source map with the fromString option, you can also use an object: ```javascript var result = UglifyJS.minify({"file1.js": "var a = function () {};"}, { outSourceMap: "out.js.map", + outFileName: "out.js", fromString: true }); ``` Note that the source map is not saved in a file, it's just returned in -`result.map`. The value passed for `outSourceMap` is only used to set the -`file` attribute in the source map (see [the spec][sm-spec]). You can set -option `sourceMapInline` to be `true` and source map will be appended to -code. +`result.map`. The value passed for `outSourceMap` is only used to set +`//# sourceMappingURL=out.js.map` in `result.code`. The value of +`outFileName` is only used to set `file` attribute in source map file. + +The `file` attribute in the source map (see [the spec][sm-spec]) will +use `outFileName` firstly, if it's falsy, then will be deduced from +`outSourceMap` (by removing `'.map'`). + +You can set option `sourceMapInline` to be `true` and source map will +be appended to code. You can also specify sourceRoot property to be included in source map: ```javascript @@ -752,6 +774,7 @@ Other options: - `regex` — Pass a RegExp to only mangle certain names (maps to the `--mangle-regex` CLI arguments option) - `ignore_quoted` – Only mangle unquoted property names (maps to the `--mangle-props 2` CLI arguments option) + - `debug` – Mangle names with the original name still present (maps to the `--mangle-props-debug` CLI arguments option). Defaults to `false`. Pass an empty string to enable, or a non-empty string to set the suffix. We could add more options to `UglifyJS.minify` — if you need additional functionality please suggest! diff --git a/bin/uglifyjs b/bin/uglifyjs index ce2e9411..d5025827 100755 --- a/bin/uglifyjs +++ b/bin/uglifyjs @@ -98,6 +98,7 @@ You need to pass an argument to this option to specify the name that your module .string("beautify") .string("m") .string("mangle") + .string("mangle-props-debug") .string("c") .string("compress") .string("d") @@ -419,7 +420,8 @@ async.eachLimit(files, 1, function (file, cb) { cache : cache, only_cache : !ARGS.mangle_props, regex : regex, - ignore_quoted : ARGS.mangle_props == 2 + ignore_quoted : ARGS.mangle_props == 2, + debug : typeof ARGS.mangle_props_debug === "undefined" ? false : ARGS.mangle_props_debug }); writeNameCache("props", cache); })(); diff --git a/lib/compress.js b/lib/compress.js index 69cfbe0b..387b87bb 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -932,7 +932,7 @@ merge(Compressor.prototype, { (function (def){ var unary_bool = [ "!", "delete" ]; var binary_bool = [ "in", "instanceof", "==", "!=", "===", "!==", "<", "<=", ">=", ">" ]; - def(AST_Node, function(){ return false }); + def(AST_Node, return_false); def(AST_UnaryPrefix, function(){ return member(this.operator, unary_bool); }); @@ -950,8 +950,8 @@ merge(Compressor.prototype, { def(AST_Seq, function(){ return this.cdr.is_boolean(); }); - def(AST_True, function(){ return true }); - def(AST_False, function(){ return true }); + def(AST_True, return_true); + def(AST_False, return_true); })(function(node, func){ node.DEFMETHOD("is_boolean", func); }); @@ -1228,11 +1228,11 @@ merge(Compressor.prototype, { // determine if expression has side effects (function(def){ - def(AST_Node, function(compressor){ return true }); + def(AST_Node, return_true); - def(AST_EmptyStatement, function(compressor){ return false }); - def(AST_Constant, function(compressor){ return false }); - def(AST_This, function(compressor){ return false }); + def(AST_EmptyStatement, return_false); + def(AST_Constant, return_false); + def(AST_This, return_false); def(AST_Call, function(compressor){ var pure = compressor.option("pure_funcs"); @@ -1252,15 +1252,15 @@ merge(Compressor.prototype, { def(AST_SimpleStatement, function(compressor){ return this.body.has_side_effects(compressor); }); - def(AST_Defun, function(compressor){ return true }); - def(AST_Function, function(compressor){ return false }); - def(AST_Class, function(compressor){ return false }); - def(AST_DefClass, function(compressor){ return true }); + def(AST_Defun, return_true); + def(AST_Function, return_false); + def(AST_Class, return_false); + def(AST_DefClass, return_true); def(AST_Binary, function(compressor){ return this.left.has_side_effects(compressor) || this.right.has_side_effects(compressor); }); - def(AST_Assign, function(compressor){ return true }); + def(AST_Assign, return_true); def(AST_Conditional, function(compressor){ return this.condition.has_side_effects(compressor) || this.consequent.has_side_effects(compressor) diff --git a/lib/output.js b/lib/output.js index 83b3253b..3a21a945 100644 --- a/lib/output.js +++ b/lib/output.js @@ -45,6 +45,20 @@ var EXPECT_DIRECTIVE = /^$|[;{][\s\n]*$/; +function is_some_comments(comment) { + var text = comment.value; + var type = comment.type; + if (type == "comment2") { + // multiline comment + return /@preserve|@license|@cc_on/i.test(text); + } + return type == "comment5"; +} + +function is_comment5(comment) { + return comment.type == "comment5"; +} + function OutputStream(options) { options = defaults(options, { @@ -81,46 +95,30 @@ function OutputStream(options) { options.shorthand = options.ecma > 5; // Convert comment option to RegExp if neccessary and set up comments filter - if (typeof options.comments === "string" && /^\/.*\/[a-zA-Z]*$/.test(options.comments)) { - var regex_pos = options.comments.lastIndexOf("/"); - options.comments = new RegExp( - options.comments.substr(1, regex_pos - 1), - options.comments.substr(regex_pos + 1) - ); - } - if (options.comments instanceof RegExp) { - options.comments = (function(f) { - return function(comment) { - return comment.type == "comment5" || f.test(comment.value); - } - })(options.comments); - } - else if (typeof options.comments === "function") { - options.comments = (function(f) { - return function(comment) { - return comment.type == "comment5" || f(this, comment); - } - })(options.comments); - } - else if (options.comments === "some") { - options.comments = function(comment) { - var text = comment.value; - var type = comment.type; - if (type == "comment2") { - // multiline comment - return /@preserve|@license|@cc_on/i.test(text); - } - return type == "comment5"; + var comment_filter = options.shebang ? is_comment5 : return_false; // Default case, throw all comments away except shebangs + if (options.comments) { + var comments = options.comments; + if (typeof options.comments === "string" && /^\/.*\/[a-zA-Z]*$/.test(options.comments)) { + var regex_pos = options.comments.lastIndexOf("/"); + comments = new RegExp( + options.comments.substr(1, regex_pos - 1), + options.comments.substr(regex_pos + 1) + ); } - } - else if (options.comments){ // NOTE includes "all" option - options.comments = function() { - return true; + if (comments instanceof RegExp) { + comment_filter = function(comment) { + return comment.type == "comment5" || comments.test(comment.value); + }; } - } else { - // Falsy case, so reject all comments, except shebangs - options.comments = function(comment) { - return comment.type == "comment5"; + else if (typeof comments === "function") { + comment_filter = function(comment) { + return comment.type == "comment5" || comments(this, comment); + }; + } + else if (comments === "some") { + comment_filter = is_some_comments; + } else { // NOTE includes "all" option + comment_filter = return_true; } } @@ -470,6 +468,7 @@ function OutputStream(options) { with_square : with_square, add_mapping : add_mapping, option : function(opt) { return options[opt] }, + comment_filter : comment_filter, line : function() { return current_line }, col : function() { return current_col }, pos : function() { return current_pos }, @@ -552,7 +551,7 @@ function OutputStream(options) { })); } - comments = comments.filter(output.option("comments"), self); + comments = comments.filter(output.comment_filter, self); // Keep single line comments after nlb, after nlb if (!output.option("beautify") && comments.length > 0 && diff --git a/lib/propmangle.js b/lib/propmangle.js index 4187db12..44902bca 100644 --- a/lib/propmangle.js +++ b/lib/propmangle.js @@ -83,7 +83,8 @@ function mangle_properties(ast, options) { cache : null, only_cache : false, regex : null, - ignore_quoted : false + ignore_quoted : false, + debug : false }); var reserved = options.reserved; @@ -101,6 +102,15 @@ function mangle_properties(ast, options) { var regex = options.regex; var ignore_quoted = options.ignore_quoted; + // note debug is either false (disabled), or a string of the debug suffix to use (enabled). + // note debug may be enabled as an empty string, which is falsey. Also treat passing 'true' + // the same as passing an empty string. + var debug = (options.debug !== false); + var debug_name_suffix; + if (debug) { + debug_name_suffix = (options.debug === true ? "" : options.debug); + } + var names_to_mangle = []; var unmangleable = []; var ignored = {}; @@ -202,9 +212,25 @@ function mangle_properties(ast, options) { var mangled = cache.props.get(name); if (!mangled) { - do { - mangled = base54(++cache.cname); - } while (!can_mangle(mangled)); + if (debug) { + // debug mode: use a prefix and suffix to preserve readability, e.g. o.foo -> o._$foo$NNN_. + var debug_mangled = "_$" + name + "$" + debug_name_suffix + "_"; + + if (can_mangle(debug_mangled) && !(ignore_quoted && debug_mangled in ignored)) { + mangled = debug_mangled; + } + } + + // either debug mode is off, or it is on and we could not use the mangled name + if (!mangled) { + // note can_mangle() does not check if the name collides with the 'ignored' set + // (filled with quoted properties when ignore_quoted set). Make sure we add this + // check so we don't collide with a quoted name. + do { + mangled = base54(++cache.cname); + } while (!can_mangle(mangled) || (ignore_quoted && mangled in ignored)); + } + cache.props.set(name, mangled); } return mangled; diff --git a/lib/sourcemap.js b/lib/sourcemap.js index 3714027e..0be16bfc 100644 --- a/lib/sourcemap.js +++ b/lib/sourcemap.js @@ -60,7 +60,7 @@ function SourceMap(options) { var orig_map = options.orig && new MOZ_SourceMap.SourceMapConsumer(options.orig); if (orig_map && Array.isArray(options.orig.sources)) { - options.orig.sources.forEach(function(source) { + orig_map._sources.toArray().forEach(function(source) { var sourceContent = orig_map.sourceContentFor(source, true); if (sourceContent) { generator.setSourceContent(source, sourceContent); diff --git a/lib/utils.js b/lib/utils.js index 8ef61936..d0a21ac9 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -112,6 +112,8 @@ function merge(obj, ext) { }; function noop() {}; +function return_false() { return false; } +function return_true() { return true; } var MAP = (function(){ function MAP(a, f, backwards) { diff --git a/test/compress/issue-1321.js b/test/compress/issue-1321.js new file mode 100644 index 00000000..2b6f17bf --- /dev/null +++ b/test/compress/issue-1321.js @@ -0,0 +1,54 @@ +issue_1321_no_debug: { + mangle_props = { + ignore_quoted: true + } + input: { + var x = {}; + x.foo = 1; + x["a"] = 2 * x.foo; + console.log(x.foo, x["a"]); + } + expect: { + var x = {}; + x.b = 1; + x["a"] = 2 * x.b; + console.log(x.b, x["a"]); + } +} + +issue_1321_debug: { + mangle_props = { + ignore_quoted: true, + debug: "" + } + input: { + var x = {}; + x.foo = 1; + x["_$foo$_"] = 2 * x.foo; + console.log(x.foo, x["_$foo$_"]); + } + expect: { + var x = {}; + x.a = 1; + x["_$foo$_"] = 2 * x.a; + console.log(x.a, x["_$foo$_"]); + } +} + +issue_1321_with_quoted: { + mangle_props = { + ignore_quoted: false + } + input: { + var x = {}; + x.foo = 1; + x["a"] = 2 * x.foo; + console.log(x.foo, x["a"]); + } + expect: { + var x = {}; + x.a = 1; + x["b"] = 2 * x.a; + console.log(x.a, x["b"]); + } +} diff --git a/test/compress/properties.js b/test/compress/properties.js index 7ef6a014..5598b45d 100644 --- a/test/compress/properties.js +++ b/test/compress/properties.js @@ -143,6 +143,98 @@ mangle_unquoted_properties: { } } +mangle_debug: { + mangle_props = { + debug: "" + }; + input: { + a.foo = "bar"; + x = { baz: "ban" }; + } + expect: { + a._$foo$_ = "bar"; + x = { _$baz$_: "ban" }; + } +} + +mangle_debug_true: { + mangle_props = { + debug: true + }; + input: { + a.foo = "bar"; + x = { baz: "ban" }; + } + expect: { + a._$foo$_ = "bar"; + x = { _$baz$_: "ban" }; + } +} + +mangle_debug_suffix: { + mangle_props = { + debug: "XYZ" + }; + input: { + a.foo = "bar"; + x = { baz: "ban" }; + } + expect: { + a._$foo$XYZ_ = "bar"; + x = { _$baz$XYZ_: "ban" }; + } +} + +mangle_debug_suffix_ignore_quoted: { + options = { + properties: false + } + mangle_props = { + ignore_quoted: true, + debug: "XYZ", + reserved: [] + } + beautify = { + beautify: false, + quote_style: 3, + keep_quoted_props: true, + } + input: { + a.top = 1; + function f1() { + a["foo"] = "bar"; + a.color = "red"; + a.stuff = 2; + x = {"bar": 10, size: 7}; + a.size = 9; + } + function f2() { + a.foo = "bar"; + a['color'] = "red"; + x = {bar: 10, size: 7}; + a.size = 9; + a.stuff = 3; + } + } + expect: { + a._$top$XYZ_ = 1; + function f1() { + a["foo"] = "bar"; + a.color = "red"; + a._$stuff$XYZ_ = 2; + x = {"bar": 10, _$size$XYZ_: 7}; + a._$size$XYZ_ = 9; + } + function f2() { + a.foo = "bar"; + a['color'] = "red"; + x = {bar: 10, _$size$XYZ_: 7}; + a._$size$XYZ_ = 9; + a._$stuff$XYZ_ = 3; + } + } +} + first_256_chars_as_properties: { beautify = { ascii_only: true, diff --git a/test/compress/screw-ie8.js b/test/compress/screw-ie8.js index 527aea04..0a4e2323 100644 --- a/test/compress/screw-ie8.js +++ b/test/compress/screw-ie8.js @@ -15,4 +15,116 @@ dont_screw: { input: f("\v"); expect_exact: 'f("\\x0B");'; -} \ No newline at end of file +} + +do_screw_try_catch: { + options = { screw_ie8: true }; + mangle = { screw_ie8: true }; + beautify = { screw_ie8: true }; + input: { + good = function(e){ + return function(error){ + try{ + e() + } catch(e) { + error(e) + } + } + }; + } + expect: { + good = function(n){ + return function(t){ + try{ + n() + } catch(n) { + t(n) + } + } + }; + } +} + +dont_screw_try_catch: { + options = { screw_ie8: false }; + mangle = { screw_ie8: false }; + beautify = { screw_ie8: false }; + input: { + bad = function(e){ + return function(error){ + try{ + e() + } catch(e) { + error(e) + } + } + }; + } + expect: { + bad = function(n){ + return function(n){ + try{ + t() + } catch(t) { + n(t) + } + } + }; + } +} + +do_screw_try_catch_undefined: { + options = { screw_ie8: true }; + mangle = { screw_ie8: true }; + beautify = { screw_ie8: true }; + input: { + function a(b){ + try { + throw 'Stuff'; + } catch (undefined) { + console.log('caught: ' + undefined); + } + console.log('undefined is ' + undefined); + return b === undefined; + }; + } + expect: { + function a(o){ + try{ + throw "Stuff" + } catch (o) { + console.log("caught: "+o) + } + console.log("undefined is " + void 0); + return void 0===o + } + } +} + +dont_screw_try_catch_undefined: { + options = { screw_ie8: false }; + mangle = { screw_ie8: false }; + beautify = { screw_ie8: false }; + input: { + function a(b){ + try { + throw 'Stuff'; + } catch (undefined) { + console.log('caught: ' + undefined); + } + console.log('undefined is ' + undefined); + return b === undefined; + }; + } + expect: { + function a(o){ + try{ + throw "Stuff" + } catch (n) { + console.log("caught: "+n) + } + console.log("undefined is " + void 0); + return void 0===o + } + } +} diff --git a/test/mocha/comment-filter.js b/test/mocha/comment-filter.js index 79162755..01580c87 100644 --- a/test/mocha/comment-filter.js +++ b/test/mocha/comment-filter.js @@ -2,7 +2,7 @@ var UglifyJS = require('../../'); var assert = require("assert"); describe("comment filters", function() { - it("Should be able to filter comments by passing regex", function() { + it("Should be able to filter comments by passing regexp", function() { var ast = UglifyJS.parse("/*!test1*/\n/*test2*/\n//!test3\n//test4\ntest7\n-->!test8"); assert.strictEqual(ast.print_to_string({comments: /^!/}), "/*!test1*/\n//!test3\n//!test6\n//!test8\n"); }); @@ -62,4 +62,14 @@ describe("comment filters", function() { var ast = UglifyJS.parse("#!foo\n//foo\n/*@preserve*/\n/* please hide me */"); assert.strictEqual(ast.print_to_string({comments: "some"}), "#!foo\n/*@preserve*/\n"); }); + + it("Should have no problem on multiple calls", function() { + const options = { + comments: /ok/ + }; + + assert.strictEqual(UglifyJS.parse("/* ok */ function a(){}").print_to_string(options), "/* ok */function a(){}"); + assert.strictEqual(UglifyJS.parse("/* ok */ function a(){}").print_to_string(options), "/* ok */function a(){}"); + assert.strictEqual(UglifyJS.parse("/* ok */ function a(){}").print_to_string(options), "/* ok */function a(){}"); + }); }); diff --git a/test/mocha/input-sourcemaps.js b/test/mocha/input-sourcemaps.js index 30ee5b92..d5284e3c 100644 --- a/test/mocha/input-sourcemaps.js +++ b/test/mocha/input-sourcemaps.js @@ -3,32 +3,55 @@ var assert = require("assert"); var SourceMapConsumer = require("source-map").SourceMapConsumer; describe("input sourcemaps", function() { - var transpiled = '"use strict";\n\n' + - 'var foo = function foo(x) {\n return "foo " + x;\n};\n' + - 'console.log(foo("bar"));\n\n' + - '//# sourceMappingURL=bundle.js.map'; + var transpilemap, map; - var transpilemap = { - "version": 3, - "sources": ["index.js"], - "names": [], - "mappings": ";;AAAA,IAAI,MAAM,SAAN,GAAM;AAAA,SAAK,SAAS,CAAd;AAAA,CAAV;AACA,QAAQ,GAAR,CAAY,IAAI,KAAJ,CAAZ", - "file": "bundle.js", - "sourcesContent": ["let foo = x => \"foo \" + x;\nconsole.log(foo(\"bar\"));"] - }; + function getMap() { + return { + "version": 3, + "sources": ["index.js"], + "names": [], + "mappings": ";;AAAA,IAAI,MAAM,SAAN,GAAM;AAAA,SAAK,SAAS,CAAd;AAAA,CAAV;AACA,QAAQ,GAAR,CAAY,IAAI,KAAJ,CAAZ", + "file": "bundle.js", + "sourcesContent": ["let foo = x => \"foo \" + x;\nconsole.log(foo(\"bar\"));"] + }; + } - var result = Uglify.minify(transpiled, { - fromString: true, - inSourceMap: transpilemap, - outSourceMap: true + function prepareMap(sourceMap) { + var transpiled = '"use strict";\n\n' + + 'var foo = function foo(x) {\n return "foo " + x;\n};\n' + + 'console.log(foo("bar"));\n\n' + + '//# sourceMappingURL=bundle.js.map'; + + transpilemap = sourceMap || getMap(); + + var result = Uglify.minify(transpiled, { + fromString: true, + inSourceMap: transpilemap, + outSourceMap: true + }); + + map = new SourceMapConsumer(result.map); + } + + beforeEach(function () { + prepareMap(); }); - var map = new SourceMapConsumer(result.map); it("Should copy over original sourcesContent", function() { assert.equal(map.sourceContentFor("index.js"), transpilemap.sourcesContent[0]); }); - it("Final sourcemap should not have invalid mappings from inputSourceMap (issue #882) ", function() { + it("Should copy sourcesContent if sources are relative", function () { + var relativeMap = getMap(); + relativeMap.sources = ['./index.js']; + + prepareMap(relativeMap); + assert.notEqual(map.sourcesContent, null); + assert.equal(map.sourcesContent.length, 1); + assert.equal(map.sourceContentFor("index.js"), transpilemap.sourcesContent[0]); + }); + + it("Final sourcemap should not have invalid mappings from inputSourceMap (issue #882)", function() { // The original source has only 2 lines, check that mappings don't have more lines var msg = "Mapping should not have higher line number than the original file had"; diff --git a/test/mocha/minify-file-map.js b/test/mocha/minify-file-map.js index aa42d25a..169e730e 100644 --- a/test/mocha/minify-file-map.js +++ b/test/mocha/minify-file-map.js @@ -7,10 +7,18 @@ describe("Input file as map", function() { '/scripts/foo.js': 'var foo = {"x": 1, y: 2, \'z\': 3};' }; var result = Uglify.minify(jsMap, {fromString: true, outSourceMap: true}); - + var map = JSON.parse(result.map); assert.strictEqual(result.code, 'var foo={x:1,y:2,z:3};'); assert.deepEqual(map.sources, ['/scripts/foo.js']); + assert.strictEqual(map.file, undefined); + + result = Uglify.minify(jsMap, {fromString: true, outFileName: 'out.js'}); + assert.strictEqual(result.map, null); + + result = Uglify.minify(jsMap, {fromString: true, outFileName: 'out.js', outSourceMap: true}); + map = JSON.parse(result.map); + assert.strictEqual(map.file, 'out.js'); }); it("Should accept array of objects and strings", function() { @@ -19,7 +27,7 @@ describe("Input file as map", function() { 'var bar = 15;' ]; var result = Uglify.minify(jsSeq, {fromString: true, outSourceMap: true}); - + var map = JSON.parse(result.map); assert.strictEqual(result.code, 'var foo={x:1,y:2,z:3},bar=15;'); assert.strictEqual(map.sources[0], '/scripts/foo.js'); @@ -31,7 +39,7 @@ describe("Input file as map", function() { 'var bar = 15;' ]; var result = Uglify.minify(jsSeq, {fromString: true, outSourceMap: true, sourceMapIncludeSources: true}); - + var map = JSON.parse(result.map); assert.strictEqual(result.code, 'var foo={x:1,y:2,z:3},bar=15;'); assert.deepEqual(map.sourcesContent, ['var foo = {"x": 1, y: 2, \'z\': 3};', 'var bar = 15;']); diff --git a/test/mocha/minify.js b/test/mocha/minify.js index ce5e8497..70cf73ae 100644 --- a/test/mocha/minify.js +++ b/test/mocha/minify.js @@ -63,13 +63,14 @@ describe("minify", function() { describe("inSourceMap", function() { it("Should read the given string filename correctly when sourceMapIncludeSources is enabled (#1236)", function() { var result = Uglify.minify('./test/input/issue-1236/simple.js', { - outSourceMap: "simple.js.min.map", + outSourceMap: "simple.min.js.map", inSourceMap: "./test/input/issue-1236/simple.js.map", sourceMapIncludeSources: true }); var map = JSON.parse(result.map); + assert.equal(map.file, 'simple.min.js'); assert.equal(map.sourcesContent.length, 1); assert.equal(map.sourcesContent[0], 'let foo = x => "foo " + x;\nconsole.log(foo("bar"));'); diff --git a/test/mocha/screw-ie8.js b/test/mocha/screw-ie8.js new file mode 100644 index 00000000..28b092e9 --- /dev/null +++ b/test/mocha/screw-ie8.js @@ -0,0 +1,23 @@ +var assert = require("assert"); +var uglify = require("../../"); + +describe("screw-ie8", function () { + it("Should be able to minify() with undefined as catch parameter in a try...catch statement", function () { + assert.strictEqual( + uglify.minify( + "function a(b){\ + try {\ + throw 'Stuff';\ + } catch (undefined) {\ + console.log('caught: ' + undefined);\ + }\ + console.log('undefined is ' + undefined);\ + return b === undefined;\ + };", { + fromString: true + } + ).code, + 'function a(o){try{throw"Stuff"}catch(o){console.log("caught: "+o)}return console.log("undefined is "+void 0),void 0===o}' + ); + }); +}); diff --git a/test/run-tests.js b/test/run-tests.js index 4063870a..8fb93c83 100755 --- a/test/run-tests.js +++ b/test/run-tests.js @@ -117,7 +117,7 @@ function run_compress_tests() { input = U.mangle_properties(input, test.mangle_props); } var output = cmp.compress(input); - output.figure_out_scope(); + output.figure_out_scope(test.mangle); if (test.mangle) { output.compute_char_frequency(test.mangle); output.mangle_names(test.mangle); diff --git a/tools/node.js b/tools/node.js index a16169b1..0b2d5197 100644 --- a/tools/node.js +++ b/tools/node.js @@ -41,6 +41,7 @@ exports.minify = function(files, options) { options = UglifyJS.defaults(options, { spidermonkey : false, outSourceMap : null, + outFileName : null, sourceRoot : null, inSourceMap : null, sourceMapUrl : null, @@ -120,7 +121,8 @@ exports.minify = function(files, options) { } if (options.outSourceMap || options.sourceMapInline) { output.source_map = UglifyJS.SourceMap({ - file: options.outSourceMap, + // prefer outFileName, otherwise use outSourceMap without .map suffix + file: options.outFileName || (typeof options.outSourceMap === 'string' ? options.outSourceMap.replace(/\.map$/i, '') : null), orig: inMap, root: options.sourceRoot });