From 58036cd0cc05561b86ad059ce738f0a30cbb266b Mon Sep 17 00:00:00 2001 From: kzc Date: Sat, 28 Jan 2017 20:37:04 -0500 Subject: [PATCH 1/4] Support marking a call as pure A function call or IIFE with an immediately preceding comment containing `@__PURE__` or `#__PURE__` is deemed to be a side-effect-free pure function call and can potentially be dropped. --- lib/compress.js | 14 ++++++++++ test/compress/issue-1261.js | 51 +++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 test/compress/issue-1261.js diff --git a/lib/compress.js b/lib/compress.js index 4e45df92..0c6e4c31 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1273,7 +1273,21 @@ merge(Compressor.prototype, { def(AST_Constant, return_false); def(AST_This, return_false); + var pure_regex = /[@#]__PURE__/; + function has_pure_annotation(node) { + if (node.pure !== undefined) return node.pure; + var pure = false; + if (node.start + && node.start.comments_before + && node.start.comments_before.length + && pure_regex.test(node.start.comments_before[node.start.comments_before.length - 1].value)) { + pure = true; + } + return node.pure = pure; + } + def(AST_Call, function(compressor){ + if (has_pure_annotation(this)) return false; var pure = compressor.option("pure_funcs"); if (!pure) return true; if (typeof pure == "function") return pure(this); diff --git a/test/compress/issue-1261.js b/test/compress/issue-1261.js new file mode 100644 index 00000000..beb00949 --- /dev/null +++ b/test/compress/issue-1261.js @@ -0,0 +1,51 @@ +pure_function_calls: { + options = { + evaluate : true, + conditionals : true, + comparisons : true, + side_effects : true, + booleans : true, + unused : true, + if_return : true, + join_vars : true, + cascade : true, + negate_iife : true, + } + input: { + // pure top-level IIFE will be dropped + // @__PURE__ - comment + (function() { + console.log("iife0"); + })(); + + // pure top-level IIFE assigned to unreferenced var will not be dropped + var iife1 = /*@__PURE__*/(function() { + console.log("iife1"); + function iife1() {} + return iife1; + })(); + + (function(){ + // pure IIFE in function scope assigned to unreferenced var will be dropped + var iife2 = /*#__PURE__*/(function() { + console.log("iife2"); + function iife2() {} + return iife2; + })(); + })(); + + // comment #__PURE__ comment + bar(), baz(), quux(); + a.b(), /* @__PURE__ */ c.d.e(), f.g(); + } + expect: { + var iife1 = function() { + console.log("iife1"); + function iife1() {} + return iife1; + }(); + + baz(), quux(); + a.b(), f.g(); + } +} From d35b92cff6c8a6c5cd8acd21bacfd0193e13c6ba Mon Sep 17 00:00:00 2001 From: kzc Date: Mon, 20 Feb 2017 21:49:47 -0500 Subject: [PATCH 2/4] remove [#@]__PURE__ hint from comment when pure call dropped --- lib/compress.js | 15 ++++++++++----- test/compress/issue-1261.js | 9 +++++++++ test/mocha/minify.js | 15 +++++++++++++++ 3 files changed, 34 insertions(+), 5 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 0c6e4c31..5e4c483a 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1273,21 +1273,26 @@ merge(Compressor.prototype, { def(AST_Constant, return_false); def(AST_This, return_false); - var pure_regex = /[@#]__PURE__/; + var pure_regex = /[@#]__PURE__/g; function has_pure_annotation(node) { if (node.pure !== undefined) return node.pure; var pure = false; + var comments, last_comment; if (node.start - && node.start.comments_before - && node.start.comments_before.length - && pure_regex.test(node.start.comments_before[node.start.comments_before.length - 1].value)) { + && (comments = node.start.comments_before) + && comments.length + && pure_regex.test((last_comment = comments[comments.length - 1]).value)) { + last_comment.value = last_comment.value.replace(pure_regex, ' '); pure = true; } return node.pure = pure; } def(AST_Call, function(compressor){ - if (has_pure_annotation(this)) return false; + if (has_pure_annotation(this)) { + compressor.warn("Dropping __PURE__ call [{file}:{line},{col}]", this.start); + return false; + } var pure = compressor.option("pure_funcs"); if (!pure) return true; if (typeof pure == "function") return pure(this); diff --git a/test/compress/issue-1261.js b/test/compress/issue-1261.js index beb00949..eedf4d4f 100644 --- a/test/compress/issue-1261.js +++ b/test/compress/issue-1261.js @@ -48,4 +48,13 @@ pure_function_calls: { baz(), quux(); a.b(), f.g(); } + expect_warnings: [ + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:17,8]", + "WARN: Dropping side-effect-free statement [test/compress/issue-1261.js:17,8]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:30,37]", + "WARN: Dropping unused variable iife2 [test/compress/issue-1261.js:30,16]", + "WARN: Dropping side-effect-free statement [test/compress/issue-1261.js:28,8]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:38,8]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:39,31]" + ] } diff --git a/test/mocha/minify.js b/test/mocha/minify.js index 70cf73ae..8fe1565f 100644 --- a/test/mocha/minify.js +++ b/test/mocha/minify.js @@ -95,4 +95,19 @@ describe("minify", function() { assert.strictEqual(code, "var a=function(n){return n};"); }); }); + + describe("#__PURE__", function() { + it("should drop #__PURE__ hint after use", function() { + var result = Uglify.minify('//@__PURE__ comment1 #__PURE__ comment2\n foo(), bar();', { + fromString: true, + output: { + comments: "all", + beautify: false, + } + }); + var code = result.code; + assert.strictEqual(code, "// comment1 comment2\nbar();"); + }); + }); + }); From 883ce8148b4398dad05734c14d690de0104ed9d1 Mon Sep 17 00:00:00 2001 From: kzc Date: Mon, 20 Feb 2017 22:41:10 -0500 Subject: [PATCH 3/4] have [@#]__PURE__ regex work with node 0.10.x --- lib/compress.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/compress.js b/lib/compress.js index 5e4c483a..e9591590 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1273,7 +1273,6 @@ merge(Compressor.prototype, { def(AST_Constant, return_false); def(AST_This, return_false); - var pure_regex = /[@#]__PURE__/g; function has_pure_annotation(node) { if (node.pure !== undefined) return node.pure; var pure = false; @@ -1281,8 +1280,8 @@ merge(Compressor.prototype, { if (node.start && (comments = node.start.comments_before) && comments.length - && pure_regex.test((last_comment = comments[comments.length - 1]).value)) { - last_comment.value = last_comment.value.replace(pure_regex, ' '); + && /[@#]__PURE__/.test((last_comment = comments[comments.length - 1]).value)) { + last_comment.value = last_comment.value.replace(/[@#]__PURE__/g, ' '); pure = true; } return node.pure = pure; From b943c0d43899fda8ba170da97e661c66d42a5439 Mon Sep 17 00:00:00 2001 From: kzc Date: Tue, 21 Feb 2017 00:30:11 -0500 Subject: [PATCH 4/4] have `#__PURE__` hint only work when compress `side_effects` option enabled --- lib/compress.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/compress.js b/lib/compress.js index e9591590..7f3574de 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1288,7 +1288,7 @@ merge(Compressor.prototype, { } def(AST_Call, function(compressor){ - if (has_pure_annotation(this)) { + if (compressor.option("side_effects") && has_pure_annotation(this)) { compressor.warn("Dropping __PURE__ call [{file}:{line},{col}]", this.start); return false; }