From 3e99aabd175401ab7490ac1a1b955a69c7b3ef2d 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 | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 11 deletions(-) create mode 100644 test/mocha/no-mutate-input.js diff --git a/lib/minify.js b/lib/minify.js index 3526268d7..9892a62fd 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 formatOptions; 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. + formatOptions = {...options.format}; + if (!formatOptions.ast) { // Destroy stuff to save RAM. (unless the deprecated `ast` option is on) - options.format._destroy_ast = true; + formatOptions._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({ + formatOptions.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 formatOptions.ast; + delete formatOptions.code; + delete formatOptions.spidermonkey; + var stream = OutputStream(formatOptions); 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 = formatOptions.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 = formatOptions.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 (formatOptions && formatOptions.source_map) { + formatOptions.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..eb7f06883 --- /dev/null +++ b/test/mocha/no-mutate-input.js @@ -0,0 +1,33 @@ +import assert from "assert"; +import { minify } from "../../main.js"; + +describe("no-mutate-input", function() { + + it("does not modify the options object", async function() { + const originalConfig = { + format: {}, + sourceMap: true, + }; + const config = {...originalConfig}; + + await minify('"foo";', config); + + assert.deepEqual(originalConfig, config); + }); + + it("does not clobber source maps with a subsequent minification", async function() { + const originalConfig = { + format: {}, + sourceMap: true, + }; + const config = {...originalConfig}; + + 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); + }); +});