From 05a375fa350f099b60852d6e14e547d755ef6e18 Mon Sep 17 00:00:00 2001 From: Rob Hogan Date: Fri, 17 Feb 2023 16:25:38 +0000 Subject: [PATCH] Don't mutate options object. Fixes #1341 --- lib/minify.js | 25 ++++++++++++++----------- test/mocha/no-mutate-input.js | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 11 deletions(-) create mode 100644 test/mocha/no-mutate-input.js diff --git a/lib/minify.js b/lib/minify.js index 3526268d7..725e3d967 100644 --- a/lib/minify.js +++ b/lib/minify.js @@ -280,10 +280,13 @@ async function minify(files, options, _fs_module) { if (options.format.spidermonkey) { result.ast = toplevel.to_mozilla_ast(); } + let format_options; if (!HOP(options.format, "code") || options.format.code) { - if (!options.format.ast) { + // Make a shallow copy so that we can modify without mutating the user's input. + format_options = {...options.format}; + if (!format_options.ast) { // Destroy stuff to save RAM. (unless the deprecated `ast` option is on) - options.format._destroy_ast = true; + format_options._destroy_ast = true; walk(toplevel, node => { if (node instanceof AST_Scope) { @@ -303,17 +306,17 @@ async function minify(files, options, _fs_module) { if (options.sourceMap.includeSources && files instanceof AST_Toplevel) { throw new Error("original source content unavailable"); } - options.format.source_map = await SourceMap({ + format_options.source_map = await SourceMap({ file: options.sourceMap.filename, orig: options.sourceMap.content, root: options.sourceMap.root, files: options.sourceMap.includeSources ? files : null, }); } - delete options.format.ast; - delete options.format.code; - delete options.format.spidermonkey; - var stream = OutputStream(options.format); + delete format_options.ast; + delete format_options.code; + delete format_options.spidermonkey; + var stream = OutputStream(format_options); toplevel.print(stream); result.code = stream.get(); if (options.sourceMap) { @@ -321,7 +324,7 @@ async function minify(files, options, _fs_module) { configurable: true, enumerable: true, get() { - const map = options.format.source_map.getEncoded(); + const map = format_options.source_map.getEncoded(); return (result.map = options.sourceMap.asObject ? map : JSON.stringify(map)); }, set(value) { @@ -331,7 +334,7 @@ async function minify(files, options, _fs_module) { }); } }); - result.decoded_map = options.format.source_map.getDecoded(); + result.decoded_map = format_options.source_map.getDecoded(); if (options.sourceMap.url == "inline") { var sourceMap = typeof result.map === "object" ? JSON.stringify(result.map) : result.map; result.code += "\n//# sourceMappingURL=data:application/json;charset=utf-8;base64," + to_base64(sourceMap); @@ -346,8 +349,8 @@ async function minify(files, options, _fs_module) { options.nameCache.props = cache_to_json(options.mangle.properties.cache); } } - if (options.format && options.format.source_map) { - options.format.source_map.destroy(); + if (format_options && format_options.source_map) { + format_options.source_map.destroy(); } if (timings) { timings.end = Date.now(); diff --git a/test/mocha/no-mutate-input.js b/test/mocha/no-mutate-input.js new file mode 100644 index 000000000..edb88d124 --- /dev/null +++ b/test/mocha/no-mutate-input.js @@ -0,0 +1,34 @@ +import assert from "assert"; +import { minify } from "../../main.js"; + +describe("no-mutate-input", function() { + + it("does not modify the options object", async function() { + const config = { + format: {}, + sourceMap: true, + }; + + await minify('"foo";', config); + + assert.deepEqual(config, { + format: {}, + sourceMap: true, + }); + }); + + it("does not clobber source maps with a subsequent minification", async function() { + const config = { + format: {}, + sourceMap: true, + }; + + const fooResult = await minify('"foo";', config); + const barResult = await minify('module.exports = "bar";', config); + + const fooMap = fooResult.map; + const barMap = barResult.map; + + assert.notEqual(barMap, fooMap); + }); +});