From: kzc Date: Tue, 21 Feb 2017 06:24:18 +0000 (+0800) Subject: Support marking a call as pure X-Git-Url: https://git.ndcode.org/public/gitweb.cgi?a=commitdiff_plain;h=1e51586996ae4fdac68a8ea597c20ab170809c43;p=UglifyJS.git 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. Depends on `side_effects` option. `[#@]__PURE__` hint will be removed from comment when pure call is dropped. fixes #1261 closes #1448 --- diff --git a/lib/compress.js b/lib/compress.js index 4dfcdcfb..95e9c1b3 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -1360,6 +1360,22 @@ merge(Compressor.prototype, { }); }); + AST_Call.DEFMETHOD("has_pure_annotation", function(compressor) { + if (!compressor.option("side_effects")) return false; + if (this.pure !== undefined) return this.pure; + var pure = false; + var comments, last_comment; + if (this.start + && (comments = this.start.comments_before) + && comments.length + && /[@#]__PURE__/.test((last_comment = comments[comments.length - 1]).value)) { + compressor.warn("Dropping __PURE__ call [{file}:{line},{col}]", this.start); + last_comment.value = last_comment.value.replace(/[@#]__PURE__/g, ' '); + pure = true; + } + return this.pure = pure; + }); + // determine if expression has side effects (function(def){ def(AST_Node, return_true); @@ -1369,7 +1385,7 @@ merge(Compressor.prototype, { def(AST_This, return_false); def(AST_Call, function(compressor){ - if (compressor.pure_funcs(this)) return true; + if (!this.has_pure_annotation(compressor) && compressor.pure_funcs(this)) return true; for (var i = this.args.length; --i >= 0;) { if (this.args[i].has_side_effects(compressor)) return true; @@ -1904,7 +1920,7 @@ merge(Compressor.prototype, { def(AST_Constant, return_null); def(AST_This, return_null); def(AST_Call, function(compressor, first_in_statement){ - if (compressor.pure_funcs(this)) return this; + if (!this.has_pure_annotation(compressor) && compressor.pure_funcs(this)) return this; var args = trim(this.args, compressor, first_in_statement); return args && AST_Seq.from_array(args); }); diff --git a/test/compress/issue-1261.js b/test/compress/issue-1261.js new file mode 100644 index 00000000..dfbe2100 --- /dev/null +++ b/test/compress/issue-1261.js @@ -0,0 +1,118 @@ +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(); + } + 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]", + ] +} + +pure_function_calls_toplevel: { + 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, + toplevel : true, + } + input: { + // pure top-level IIFE will be dropped + // @__PURE__ - comment + (function() { + console.log("iife0"); + })(); + + // pure top-level IIFE assigned to unreferenced var will 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: { + baz(), quux(); + a.b(), f.g(); + } + expect_warnings: [ + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:79,8]", + "WARN: Dropping side-effect-free statement [test/compress/issue-1261.js:79,8]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:92,37]", + "WARN: Dropping unused variable iife2 [test/compress/issue-1261.js:92,16]", + "WARN: Dropping side-effect-free statement [test/compress/issue-1261.js:90,8]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:100,8]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:101,31]", + "WARN: Dropping __PURE__ call [test/compress/issue-1261.js:84,33]", + "WARN: Dropping unused variable iife1 [test/compress/issue-1261.js:84,12]", + ] +} 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();"); + }); + }); + });