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
This commit is contained in:
@@ -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
|
// determine if expression has side effects
|
||||||
(function(def){
|
(function(def){
|
||||||
def(AST_Node, return_true);
|
def(AST_Node, return_true);
|
||||||
@@ -1369,7 +1385,7 @@ merge(Compressor.prototype, {
|
|||||||
def(AST_This, return_false);
|
def(AST_This, return_false);
|
||||||
|
|
||||||
def(AST_Call, function(compressor){
|
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;) {
|
for (var i = this.args.length; --i >= 0;) {
|
||||||
if (this.args[i].has_side_effects(compressor))
|
if (this.args[i].has_side_effects(compressor))
|
||||||
return true;
|
return true;
|
||||||
@@ -1904,7 +1920,7 @@ merge(Compressor.prototype, {
|
|||||||
def(AST_Constant, return_null);
|
def(AST_Constant, return_null);
|
||||||
def(AST_This, return_null);
|
def(AST_This, return_null);
|
||||||
def(AST_Call, function(compressor, first_in_statement){
|
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);
|
var args = trim(this.args, compressor, first_in_statement);
|
||||||
return args && AST_Seq.from_array(args);
|
return args && AST_Seq.from_array(args);
|
||||||
});
|
});
|
||||||
|
|||||||
118
test/compress/issue-1261.js
Normal file
118
test/compress/issue-1261.js
Normal file
@@ -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]",
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -95,4 +95,19 @@ describe("minify", function() {
|
|||||||
assert.strictEqual(code, "var a=function(n){return n};");
|
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();");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user