improve compatibility with use strict (#5440)

This commit is contained in:
Alex Lam S.L 2022-05-14 05:15:54 +01:00 committed by GitHub
parent 8946c87011
commit e31bbe329a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 441 additions and 37 deletions

View File

@ -827,6 +827,9 @@ var AST_Class = DEFNODE("Class", "extends name properties", {
extends: "[AST_Node?] the super class, or null if not specified", extends: "[AST_Node?] the super class, or null if not specified",
properties: "[AST_ClassProperty*] array of class properties", properties: "[AST_ClassProperty*] array of class properties",
}, },
resolve: function(def_class) {
return def_class ? this : this.parent_scope.resolve();
},
walk: function(visitor) { walk: function(visitor) {
var node = this; var node = this;
visitor.visit(node, function() { visitor.visit(node, function() {
@ -851,9 +854,6 @@ var AST_DefClass = DEFNODE("DefClass", null, {
$propdoc: { $propdoc: {
name: "[AST_SymbolDefClass] the name of this class", name: "[AST_SymbolDefClass] the name of this class",
}, },
resolve: function(def_class) {
return def_class ? this : this.parent_scope.resolve();
},
_validate: function() { _validate: function() {
if (!(this.name instanceof AST_SymbolDefClass)) throw new Error("name must be AST_SymbolDefClass"); if (!(this.name instanceof AST_SymbolDefClass)) throw new Error("name must be AST_SymbolDefClass");
}, },
@ -1837,7 +1837,7 @@ var AST_Label = DEFNODE("Label", "references", {
initialize: function() { initialize: function() {
this.references = []; this.references = [];
this.thedef = this; this.thedef = this;
} },
}, AST_Symbol); }, AST_Symbol);
var AST_SymbolRef = DEFNODE("SymbolRef", "fixed in_arg redef", { var AST_SymbolRef = DEFNODE("SymbolRef", "fixed in_arg redef", {
@ -2039,16 +2039,21 @@ TreeWalker.prototype = {
return this.stack[this.stack.length - 2 - (n || 0)]; return this.stack[this.stack.length - 2 - (n || 0)];
}, },
push: function(node) { push: function(node) {
if (node instanceof AST_Lambda) { var value;
if (node instanceof AST_Class) {
this.directives = Object.create(this.directives);
value = "use strict";
} else if (node instanceof AST_Directive) {
value = node.value;
} else if (node instanceof AST_Lambda) {
this.directives = Object.create(this.directives); this.directives = Object.create(this.directives);
} else if (node instanceof AST_Directive && !this.directives[node.value]) {
this.directives[node.value] = node;
} }
if (value && !this.directives[value]) this.directives[value] = node;
this.stack.push(node); this.stack.push(node);
}, },
pop: function() { pop: function() {
var node = this.stack.pop(); var node = this.stack.pop();
if (node instanceof AST_Lambda) { if (node instanceof AST_Class || node instanceof AST_Lambda) {
this.directives = Object.getPrototypeOf(this.directives); this.directives = Object.getPrototypeOf(this.directives);
} }
}, },

View File

@ -2552,8 +2552,7 @@ Compressor.prototype.compress = function(node) {
&& all(iife.args, function(arg) { && all(iife.args, function(arg) {
return !(arg instanceof AST_Spread); return !(arg instanceof AST_Spread);
})) { })) {
var fn_strict = compressor.has_directive("use strict"); var fn_strict = fn.in_strict_mode() && !fn.parent_scope.resolve(true).in_strict_mode();
if (fn_strict && !member(fn_strict, fn.body)) fn_strict = false;
var has_await = is_async(fn) ? function(node) { var has_await = is_async(fn) ? function(node) {
return node instanceof AST_Symbol && node.name == "await"; return node instanceof AST_Symbol && node.name == "await";
} : function(node) { } : function(node) {
@ -4120,6 +4119,25 @@ Compressor.prototype.compress = function(node) {
&& !(compressor && node.expression.has_side_effects(compressor)); && !(compressor && node.expression.has_side_effects(compressor));
} }
// in_strict_mode()
// return true if scope executes in Strict Mode
(function(def) {
def(AST_Class, return_this);
def(AST_Scope, function() {
var body = this.body;
for (var i = 0; i < body.length; i++) {
var stat = body[i];
if (!(stat instanceof AST_Directive)) break;
if (stat.value == "use strict") return true;
}
var parent = this.parent_scope;
if (!parent) return false;
return parent.resolve(true).in_strict_mode();
});
})(function(node, func) {
node.DEFMETHOD("in_strict_mode", func);
});
// is_truthy() // is_truthy()
// return true if `!!node === true` // return true if `!!node === true`
(function(def) { (function(def) {
@ -9882,6 +9900,10 @@ Compressor.prototype.compress = function(node) {
return safe; return safe;
} }
function safe_from_strict_mode(fn, compressor) {
return fn.in_strict_mode() || !compressor.has_directive("use strict");
}
OPT(AST_Call, function(self, compressor) { OPT(AST_Call, function(self, compressor) {
var exp = self.expression; var exp = self.expression;
var terminated = trim_optional_chain(self, compressor); var terminated = trim_optional_chain(self, compressor);
@ -10206,7 +10228,10 @@ Compressor.prototype.compress = function(node) {
} }
return true; return true;
}) && !(fn.rest instanceof AST_Destructured && has_arg_refs(fn, fn.rest)); }) && !(fn.rest instanceof AST_Destructured && has_arg_refs(fn, fn.rest));
var can_inline = can_drop && compressor.option("inline") && !self.is_expr_pure(compressor); var can_inline = can_drop
&& compressor.option("inline")
&& !self.is_expr_pure(compressor)
&& (exp === fn || safe_from_strict_mode(fn, compressor));
if (can_inline && stat instanceof AST_Return) { if (can_inline && stat instanceof AST_Return) {
var value = stat.value; var value = stat.value;
if (exp === fn if (exp === fn
@ -11813,8 +11838,9 @@ Compressor.prototype.compress = function(node) {
} else if (fixed.name && fixed.name.definition() !== def) { } else if (fixed.name && fixed.name.definition() !== def) {
single_use = false; single_use = false;
} else if (fixed.parent_scope !== self.scope || is_funarg(def)) { } else if (fixed.parent_scope !== self.scope || is_funarg(def)) {
single_use = fixed.is_constant_expression(self.scope); if (!safe_from_strict_mode(fixed, compressor)) {
if (single_use == "f") { single_use = false;
} else if ((single_use = fixed.is_constant_expression(self.scope)) == "f") {
var scope = self.scope; var scope = self.scope;
do { do {
if (scope instanceof AST_LambdaDefinition || scope instanceof AST_LambdaExpression) { if (scope instanceof AST_LambdaDefinition || scope instanceof AST_LambdaExpression) {

View File

@ -636,8 +636,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
next_token.add_directive = function(directive) { next_token.add_directive = function(directive) {
S.directive_stack[S.directive_stack.length - 1].push(directive); S.directive_stack[S.directive_stack.length - 1].push(directive);
if (S.directives[directive]) S.directives[directive]++; S.directives[directive] = (S.directives[directive] || 0) + 1;
else S.directives[directive] = 1;
} }
next_token.push_directives_stack = function() { next_token.push_directives_stack = function() {
@ -1340,8 +1339,6 @@ function parse($TEXT, options) {
var loop = S.in_loop; var loop = S.in_loop;
var labels = S.labels; var labels = S.labels;
++S.in_function; ++S.in_function;
S.in_directives = true;
S.input.push_directives_stack();
S.in_loop = 0; S.in_loop = 0;
S.labels = []; S.labels = [];
if (is("punc", "{")) { if (is("punc", "{")) {
@ -1352,8 +1349,6 @@ function parse($TEXT, options) {
handle_regexp(); handle_regexp();
value = maybe_assign(); value = maybe_assign();
} }
var is_strict = S.input.has_directive("use strict");
S.input.pop_directives_stack();
--S.in_function; --S.in_function;
S.in_loop = loop; S.in_loop = loop;
S.labels = labels; S.labels = labels;
@ -1367,7 +1362,7 @@ function parse($TEXT, options) {
value: value, value: value,
end: prev(), end: prev(),
}); });
if (is_strict) node.each_argname(strict_verify_symbol); if (S.input.has_directive("use strict")) node.each_argname(strict_verify_symbol);
return node; return node;
} }
@ -1412,7 +1407,7 @@ function parse($TEXT, options) {
name: name, name: name,
argnames: argnames, argnames: argnames,
rest: argnames.rest || null, rest: argnames.rest || null,
body: body body: body,
}); });
if (is_strict) { if (is_strict) {
if (name) strict_verify_symbol(name); if (name) strict_verify_symbol(name);

View File

@ -742,6 +742,38 @@ collapse_rhs_static: {
node_version: ">=12" node_version: ">=12"
} }
inline_non_strict: {
options = {
reduce_funcs: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
function f(a) {
return a.p = "PASS";
}
class A {
g() {
return f(42);
}
}
console.log(new A().g());
}
expect: {
function f(a) {
return a.p = "PASS";
}
console.log(new class {
g() {
return f(42);
}
}().g());
}
expect_stdout: "PASS"
node_version: ">=6"
}
self_comparison: { self_comparison: {
options = { options = {
booleans: true, booleans: true,
@ -1758,12 +1790,14 @@ issue_4962_1: {
})(function g() {}); })(function g() {});
} }
expect: { expect: {
(function g() {}), (function() {
void class { function f() {
static c = function() {
while (console.log(typeof g)); while (console.log(typeof g));
}(); }
}; (class {
static c = f();
});
})(function g() {});
} }
expect_stdout: "undefined" expect_stdout: "undefined"
node_version: ">=12" node_version: ">=12"
@ -1796,6 +1830,37 @@ issue_4962_1_strict: {
node_version: ">=12" node_version: ">=12"
} }
issue_4962_1_strict_direct: {
options = {
ie: true,
inline: true,
reduce_vars: true,
unused: true,
}
input: {
(function() {
function f() {
"use strict";
while (console.log(typeof g));
}
class A {
static p = f();
}
})(function g() {});
}
expect: {
(function g() {}),
void class {
static c = function() {
"use strict";
while (console.log(typeof g));
}();
};
}
expect_stdout: "undefined"
node_version: ">=12"
}
issue_4962_2: { issue_4962_2: {
options = { options = {
ie: true, ie: true,
@ -1815,8 +1880,11 @@ issue_4962_2: {
} }
expect: { expect: {
console.log(function f() {}(function g() { console.log(function f() {}(function g() {
function h() {
f;
}
(class { (class {
static c = f; static c = h();
}); });
})); }));
} }
@ -1852,6 +1920,69 @@ issue_4962_2_strict: {
node_version: ">=12" node_version: ">=12"
} }
issue_4962_2_strict_direct: {
options = {
ie: true,
inline: true,
reduce_vars: true,
unused: true,
}
input: {
console.log(function f() {}(function g() {
function h() {
"use strict";
f;
}
class A {
static p = h();
}
}, typeof g));
}
expect: {
console.log(function f() {}(function g() {
(class {
static c = function() {
"use strict";
f;
}();
});
}));
}
expect_stdout: "undefined"
node_version: ">=12"
}
issue_4962_2_strict_direct_inline: {
options = {
directives: true,
ie: true,
inline: true,
passes: 2,
reduce_vars: true,
unused: true,
}
input: {
console.log(function f() {}(function g() {
function h() {
"use strict";
f;
}
class A {
static p = h();
}
}, typeof g));
}
expect: {
console.log(function f() {}(function g() {
(class {
static c = f;
});
}));
}
expect_stdout: "undefined"
node_version: ">=12"
}
issue_4982_1: { issue_4982_1: {
options = { options = {
dead_code: true, dead_code: true,
@ -2541,7 +2672,7 @@ issue_5387: {
node_version: ">=4" node_version: ">=4"
} }
issue_5389: { issue_5389_1: {
options = { options = {
collapse_vars: true, collapse_vars: true,
toplevel: true, toplevel: true,
@ -2572,6 +2703,37 @@ issue_5389: {
node_version: ">=12" node_version: ">=12"
} }
issue_5389_2: {
options = {
collapse_vars: true,
toplevel: true,
}
input: {
function log(m, n) {
console.log(m, n);
}
var a = log;
var A = class {
[a = "FAIL"] = a = "PASS";
};
var b = new A();
log(a, b.FAIL);
}
expect: {
function log(m, n) {
console.log(m, n);
}
var a = log;
var A;
var b = new class {
[a = "FAIL"] = a = "PASS";
}();
log(a, b.FAIL);
}
expect_stdout: "PASS PASS"
node_version: ">=12"
}
issue_5436: { issue_5436: {
options = { options = {
merge_vars: true, merge_vars: true,

View File

@ -8398,3 +8398,228 @@ issue_5409: {
} }
expect_stdout: "undefined" expect_stdout: "undefined"
} }
mixed_mode_inline_1: {
options = {
directives: true,
inline: true,
reduce_funcs: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
function f() {
return this;
}
console.log(function() {
return f();
}() ? "PASS" : "FAIL");
}
expect: {
console.log(function() {
return this;
}() ? "PASS" : "FAIL");
}
expect_stdout: "PASS"
}
mixed_mode_inline_1_strict: {
options = {
directives: true,
inline: true,
reduce_funcs: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
"use strict";
function f() {
return this;
}
console.log(function() {
return f();
}() ? "FAIL" : "PASS");
}
expect: {
"use strict";
console.log(function() {
return this;
}() ? "FAIL" : "PASS");
}
expect_stdout: "PASS"
}
mixed_mode_inline_2: {
options = {
directives: true,
inline: true,
reduce_funcs: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
function f() {
"use strict";
return this;
}
console.log(function() {
return f();
}() ? "FAIL" : "PASS");
}
expect: {
console.log(function() {
"use strict";
return this;
}() ? "FAIL" : "PASS");
}
expect_stdout: "PASS"
}
mixed_mode_inline_2_strict: {
options = {
directives: true,
inline: true,
reduce_funcs: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
"use strict";
function f() {
"use strict";
return this;
}
console.log(function() {
return f();
}() ? "FAIL" : "PASS");
}
expect: {
"use strict";
console.log(function() {
return this;
}() ? "FAIL" : "PASS");
}
expect_stdout: "PASS"
}
mixed_mode_inline_3: {
options = {
directives: true,
inline: true,
reduce_funcs: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
function f() {
return this;
}
console.log(function() {
"use strict";
return f();
}() ? "PASS" : "FAIL");
}
expect: {
function f() {
return this;
}
console.log(function() {
"use strict";
return f();
}() ? "PASS" : "FAIL");
}
expect_stdout: "PASS"
}
mixed_mode_inline_3_strict: {
options = {
directives: true,
inline: true,
reduce_funcs: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
"use strict";
function f() {
return this;
}
console.log(function() {
"use strict";
return f();
}() ? "FAIL" : "PASS");
}
expect: {
"use strict";
console.log(function() {
return this;
}() ? "FAIL" : "PASS");
}
expect_stdout: "PASS"
}
mixed_mode_inline_4: {
options = {
directives: true,
inline: true,
reduce_funcs: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
function f() {
"use strict";
return this;
}
console.log(function() {
"use strict";
return f();
}() ? "FAIL" : "PASS");
}
expect: {
console.log(function() {
"use strict";
return function() {
"use strict";
return this;
}();
}() ? "FAIL" : "PASS");
}
expect_stdout: "PASS"
}
mixed_mode_inline_4_strict: {
options = {
directives: true,
inline: true,
reduce_funcs: true,
reduce_vars: true,
toplevel: true,
unused: true,
}
input: {
"use strict";
function f() {
"use strict";
return this;
}
console.log(function() {
"use strict";
return f();
}() ? "FAIL" : "PASS");
}
expect: {
"use strict";
console.log(function() {
return this;
}() ? "FAIL" : "PASS");
}
expect_stdout: "PASS"
}

View File

@ -2534,15 +2534,6 @@ for (var round = 1; round <= num_iterations; round++) {
} }
// ignore difference in error message caused by Temporal Dead Zone // ignore difference in error message caused by Temporal Dead Zone
if (!ok && original_erred && uglify_erred && original_result.name == "ReferenceError" && uglify_result.name == "ReferenceError") ok = true; if (!ok && original_erred && uglify_erred && original_result.name == "ReferenceError" && uglify_result.name == "ReferenceError") ok = true;
// ignore difference due to implicit strict-mode in `class`
if (!ok && /\bclass\b/.test(original_code)) {
var original_strict = run_code('"use strict";\n' + original_code, toplevel);
if (uglify_erred && /^(Syntax|Type)Error$/.test(uglify_result.name)) {
ok = sandbox.is_error(original_strict);
} else {
ok = sandbox.same_stdout(original_strict, uglify_result);
}
}
// ignore difference in error message caused by `import` symbol redeclaration // ignore difference in error message caused by `import` symbol redeclaration
if (!ok && original_erred && uglify_erred && /\bimport\b/.test(original_code)) { if (!ok && original_erred && uglify_erred && /\bimport\b/.test(original_code)) {
if (is_error_redeclaration(original_result) && is_error_redeclaration(uglify_result)) ok = true; if (is_error_redeclaration(original_result) && is_error_redeclaration(uglify_result)) ok = true;