var assert = require("assert"); var fs = require("fs"); var UglifyJS = require("../node"); function read(path) { return fs.readFileSync(path, "utf8"); } function source_map(code) { var result = UglifyJS.minify(code, { compress: false, mangle: false, sourceMap: true, }); if (result.error) throw result.error; return JSON.parse(result.map); } function get_map() { 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\"));"] }; } function prepare_map(sourceMap) { var code = [ '"use strict";', "", "var foo = function foo(x) {", ' return "foo " + x;', "};", 'console.log(foo("bar"));', "", "//# sourceMappingURL=bundle.js.map", ].join("\n"); var result = UglifyJS.minify(code, { sourceMap: { content: sourceMap, includeSources: true, } }); if (result.error) throw result.error; return JSON.parse(result.map); } describe("sourcemaps", function() { it("Should give correct version", function() { var map = source_map("var x = 1 + 1;"); assert.strictEqual(map.version, 3); assert.deepEqual(map.names, [ "x" ]); }); it("Should give correct names", function() { var map = source_map([ "({", " get enabled() {", " return 3;", " },", " set enabled(x) {", " ;", " }", "});", ].join("\n")); assert.deepEqual(map.names, [ "enabled", "x" ]); }); it("Should work with sourceMap.names=true", function() { var result = UglifyJS.minify([ "var obj = {", " p: a,", " q: b", "};", ].join("\n"), { compress: false, mangle: false, sourceMap: { names: true, }, }); if (result.error) throw result.error; var map = JSON.parse(result.map); assert.deepEqual(map.names, [ "obj", "p", "a", "q", "b" ]); }); it("Should work with sourceMap.names=false", function() { var result = UglifyJS.minify([ "var obj = {", " p: a,", " q: b", "};", ].join("\n"), { compress: false, mangle: false, sourceMap: { names: false, }, }); if (result.error) throw result.error; var map = JSON.parse(result.map); assert.deepEqual(map.names, []); }); it("Should mark class properties", function() { var result = UglifyJS.minify([ "class A {", " static P = 42", " set #q(v) {}", "}", ].join("\n"), { sourceMap: true, }); if (result.error) throw result.error; assert.strictEqual(result.code, "class A{static P=42;set#q(s){}}"); assert.strictEqual(result.map, '{"version":3,"sources":["0"],"names":["A","P","#q","v"],"mappings":"MAAMA,EACFC,SAAW,GACXC,MAAOC"}'); }); it("Should mark array/object literals", function() { var result = UglifyJS.minify([ "var obj = {};", "obj.wat([]);", ].join("\n"), { sourceMap: true, toplevel: true, }); if (result.error) throw result.error; assert.strictEqual(result.code, "({}).wat([]);"); assert.strictEqual(result.map, '{"version":3,"sources":["0"],"names":["wat"],"mappings":"CAAU,IACNA,IAAI"}'); }); it("Should give correct sourceRoot", function() { var code = "console.log(42);"; var result = UglifyJS.minify(code, { sourceMap: { root: "//foo.bar/", }, }); if (result.error) throw result.error; assert.strictEqual(result.code, code); assert.strictEqual(result.map, '{"version":3,"sourceRoot":"//foo.bar/","sources":["0"],"names":["console","log"],"mappings":"AAAAA,QAAQC,IAAI"}'); }); it("Should produce same source map with DOS or UNIX line endings", function() { var code = [ 'console.log("\\', 'hello",', '"world");', ]; var dos = UglifyJS.minify(code.join("\r\n"), { sourceMap: true, }); if (dos.error) throw dos.error; var unix = UglifyJS.minify(code.join("\n"), { sourceMap: true, }); if (unix.error) throw unix.error; assert.strictEqual(dos.map, unix.map); }); describe("inSourceMap", function() { it("Should read the given string filename correctly when sourceMapIncludeSources is enabled", function() { var result = UglifyJS.minify(read("test/input/issue-1236/simple.js"), { sourceMap: { content: read("test/input/issue-1236/simple.js.map"), filename: "simple.min.js", includeSources: true } }); if (result.error) throw result.error; 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"));'); }); it("Should process inline source map", function() { var result = UglifyJS.minify(read("test/input/issue-520/input.js"), { compress: { toplevel: true }, sourceMap: { content: "inline", includeSources: true, url: "inline" } }); if (result.error) throw result.error; assert.strictEqual(result.code + "\n", read("test/input/issue-520/output.js")); }); it("Should warn for missing inline source map", function() { var result = UglifyJS.minify(read("test/input/issue-1323/sample.js"), { mangle: false, sourceMap: { content: "inline" }, warnings: true, }); assert.strictEqual(result.code, "var bar=function(bar){return bar};"); assert.deepEqual(result.warnings, [ "WARN: inline source map not found: 0" ]); }); it("Should handle multiple input and inline source map", function() { var result = UglifyJS.minify([ read("test/input/issue-520/input.js"), read("test/input/issue-1323/sample.js"), ], { sourceMap: { content: "inline", url: "inline", }, warnings: true, }); if (result.error) throw result.error; assert.strictEqual(result.code, [ "var Foo=function(){console.log(3)};new Foo;var bar=function(o){return o};", "//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInN0ZGluIiwiMSJdLCJuYW1lcyI6WyJGb28iLCJjb25zb2xlIiwibG9nIiwiYmFyIl0sIm1hcHBpbmdzIjoiQUFBQSxJQUFNQSxJQUFJLFdBQWdCQyxRQUFRQyxJQUFJLElBQVMsSUFBSUYsSUNBbkQsSUFBSUcsSUFDQSxTQUFjQSxHQUNWLE9BQU9BIn0=", ].join("\n")); assert.deepEqual(result.warnings, [ "WARN: inline source map not found: 1" ]); }); it("Should drop source contents for includeSources=false", function() { var result = UglifyJS.minify(read("test/input/issue-520/input.js"), { compress: false, mangle: false, sourceMap: { content: "inline", includeSources: true, }, }); if (result.error) throw result.error; var map = JSON.parse(result.map); assert.strictEqual(map.sourcesContent.length, 1); result = UglifyJS.minify(result.code, { compress: false, mangle: false, sourceMap: { content: result.map, }, }); if (result.error) throw result.error; map = JSON.parse(result.map); assert.ok(!("sourcesContent" in map)); }); it("Should parse the correct sourceMappingURL", function() { var result = UglifyJS.minify(read("test/input/issue-3294/input.js"), { compress: { toplevel: true }, sourceMap: { content: "inline", includeSources: true, url: "inline" } }); if (result.error) throw result.error; assert.strictEqual(result.code + "\n", read("test/input/issue-3294/output.js")); }); it("Should work in presence of unrecognised annotations", function() { var result = UglifyJS.minify(read("test/input/issue-3441/input.js"), { compress: false, mangle: false, sourceMap: { content: "inline", }, }); if (result.error) throw result.error; assert.strictEqual(result.code, '(function(){console.log("hello")}).call(this);'); assert.strictEqual(result.map, '{"version":3,"sources":["main.coffee"],"names":["console","log"],"mappings":"CAAA,WAAAA,QAAQC,IAAI"}'); }); it("Should not overwrite existing sourcesContent", function() { var result = UglifyJS.minify({ "in.js": [ '"use strict";', "", "var _window$foo = window.foo,", " a = _window$foo[0],", " b = _window$foo[1];", ].join("\n"), }, { compress: false, mangle: false, sourceMap: { content: { version: 3, sources: [ "in.js" ], names: [ "window", "foo", "a", "b", ], mappings: ";;kBAAaA,MAAM,CAACC,G;IAAfC,C;IAAGC,C", file: "in.js", sourcesContent: [ "let [a, b] = window.foo;\n" ], }, includeSources: true, }, }); if (result.error) throw result.error; assert.strictEqual(result.code, '"use strict";var _window$foo=window.foo,a=_window$foo[0],b=_window$foo[1];'); assert.strictEqual(result.map, '{"version":3,"sources":["in.js"],"sourcesContent":["let [a, b] = window.foo;\\n"],"names":["window","foo","a","b"],"mappings":"6BAAaA,OAAOC,IAAfC,E,eAAGC,E"}'); }); }); describe("sourceMapInline", function() { it("Should append source map to output js when sourceMapInline is enabled", function() { var result = UglifyJS.minify('var a = function(foo) { return foo; };', { sourceMap: { url: "inline" } }); if (result.error) throw result.error; var code = result.code; assert.strictEqual(code, "var a=function(n){return n};\n" + "//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIjAiXSwibmFtZXMiOlsiYSIsImZvbyJdLCJtYXBwaW5ncyI6IkFBQUEsSUFBSUEsRUFBSSxTQUFTQyxHQUFPLE9BQU9BIn0="); }); it("Should not append source map to output js when sourceMapInline is not enabled", function() { var result = UglifyJS.minify('var a = function(foo) { return foo; };'); if (result.error) throw result.error; var code = result.code; assert.strictEqual(code, "var a=function(n){return n};"); }); it("Should work with max_line_len", function() { var result = UglifyJS.minify(read("test/input/issue-505/input.js"), { compress: { directives: false, }, output: { max_line_len: 20 }, sourceMap: { url: "inline" } }); if (result.error) throw result.error; assert.strictEqual(result.code, read("test/input/issue-505/output.js")); }); it("Should work with unicode characters", function() { var code = [ "var tëst = '→unicøde←';", "alert(tëst);", ].join("\n"); var result = UglifyJS.minify(code, { sourceMap: { includeSources: true, url: "inline", } }); if (result.error) throw result.error; var map = JSON.parse(result.map); assert.strictEqual(map.sourcesContent.length, 1); assert.strictEqual(map.sourcesContent[0], code); var encoded = result.code.slice(result.code.lastIndexOf(",") + 1); map = JSON.parse(UglifyJS.to_ascii(encoded)); assert.strictEqual(map.sourcesContent.length, 1); assert.strictEqual(map.sourcesContent[0], code); result = UglifyJS.minify(result.code, { sourceMap: { content: "inline", includeSources: true, } }); if (result.error) throw result.error; map = JSON.parse(result.map); assert.strictEqual(map.names.length, 2); assert.strictEqual(map.names[0], "tëst"); assert.strictEqual(map.names[1], "alert"); }); }); describe("input sourcemaps", function() { it("Should not modify input source map", function() { var orig = get_map(); var original = JSON.stringify(orig); var map = prepare_map(orig); assert.strictEqual(JSON.stringify(orig), original); }); it("Should copy over original sourcesContent", function() { var orig = get_map(); var map = prepare_map(orig); assert.strictEqual(map.sources.length, 1); assert.strictEqual(map.sources[0], "index.js"); assert.strictEqual(map.sourcesContent.length, 1); assert.equal(map.sourcesContent[0], orig.sourcesContent[0]); }); it("Should copy sourcesContent if sources are relative", function() { var relativeMap = get_map(); relativeMap.sources = ['./index.js']; var map = prepare_map(relativeMap); assert.strictEqual(map.sources.length, 1); assert.strictEqual(map.sources[0], "./index.js"); assert.strictEqual(map.sourcesContent.length, 1); assert.equal(map.sourcesContent[0], relativeMap.sourcesContent[0]); }); it("Should not have invalid mappings from inputSourceMap", function() { var map = prepare_map(get_map()); // 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"; var lines = map.mappings.split(/;/); assert.ok(lines.length <= 2, msg); var indices = [ 0, 0, 1, 0, 0]; lines.forEach(function(segments) { indices[0] = 0; segments.split(/,/).forEach(function(segment) { UglifyJS.vlq_decode(indices, segment); assert.ok(indices[2] <= 2, msg); }); }); }); }); });