enhance conditionals (#5542)

This commit is contained in:
Alex Lam S.L
2022-07-07 05:17:23 +01:00
committed by GitHub
parent c8d98f4787
commit d89f0965aa
5 changed files with 554 additions and 62 deletions

View File

@@ -109,6 +109,9 @@ var AST_Node = DEFNODE("Node", "start end", {
start: "[AST_Token] The first token of this node",
end: "[AST_Token] The last token of this node"
},
equals: function(node) {
return this.TYPE == node.TYPE && this._equals(node);
},
walk: function(visitor) {
visitor.visit(this);
},
@@ -231,6 +234,24 @@ AST_Node.disable_validation = function() {
while (restore = restore_transforms.pop()) restore();
};
function all_equals(k, l) {
return k.length == l.length && all(k, function(m, i) {
return m.equals(l[i]);
});
}
function list_equals(s, t) {
return s.length == t.length && all(s, function(u, i) {
return u == t[i];
});
}
function prop_equals(u, v) {
if (u === v) return true;
if (u == null) return v == null;
return u instanceof AST_Node && v instanceof AST_Node && u.equals(v);
}
/* -----[ statements ]----- */
var AST_Statement = DEFNODE("Statement", null, {
@@ -242,6 +263,7 @@ var AST_Statement = DEFNODE("Statement", null, {
var AST_Debugger = DEFNODE("Debugger", null, {
$documentation: "Represents a debugger statement",
_equals: return_true,
}, AST_Statement);
var AST_Directive = DEFNODE("Directive", "quote value", {
@@ -250,6 +272,9 @@ var AST_Directive = DEFNODE("Directive", "quote value", {
quote: "[string?] the original quote character",
value: "[string] The value of this directive as a plain string (it's not an AST_String!)",
},
_equals: function(node) {
return this.value == node.value;
},
_validate: function() {
if (this.quote != null) {
if (typeof this.quote != "string") throw new Error("quote must be string");
@@ -260,7 +285,8 @@ var AST_Directive = DEFNODE("Directive", "quote value", {
}, AST_Statement);
var AST_EmptyStatement = DEFNODE("EmptyStatement", null, {
$documentation: "The empty statement (empty block or simply a semicolon)"
$documentation: "The empty statement (empty block or simply a semicolon)",
_equals: return_true,
}, AST_Statement);
function is_statement(node) {
@@ -291,6 +317,9 @@ var AST_SimpleStatement = DEFNODE("SimpleStatement", "body", {
$propdoc: {
body: "[AST_Node] an expression node (should not be instanceof AST_Statement)",
},
_equals: function(node) {
return this.body.equals(node.body);
},
walk: function(visitor) {
var node = this;
visitor.visit(node, function() {
@@ -342,6 +371,9 @@ var AST_Block = DEFNODE("Block", "body", {
$propdoc: {
body: "[AST_Statement*] an array of statements"
},
_equals: function(node) {
return all_equals(this.body, node.body);
},
walk: function(visitor) {
var node = this;
visitor.visit(node, function() {
@@ -376,6 +408,10 @@ var AST_LabeledStatement = DEFNODE("LabeledStatement", "label", {
$propdoc: {
label: "[AST_Label] a label definition"
},
_equals: function(node) {
return this.label.equals(node.label)
&& this.body.equals(node.body);
},
walk: function(visitor) {
var node = this;
visitor.visit(node, function() {
@@ -417,6 +453,10 @@ var AST_DWLoop = DEFNODE("DWLoop", "condition", {
$propdoc: {
condition: "[AST_Node] the loop condition. Should not be instanceof AST_Statement"
},
_equals: function(node) {
return this.body.equals(node.body)
&& this.condition.equals(node.condition);
},
_validate: function() {
if (this.TYPE == "DWLoop") throw new Error("should not instantiate AST_DWLoop");
must_be_expression(this, "condition");
@@ -431,7 +471,7 @@ var AST_Do = DEFNODE("Do", null, {
node.body.walk(visitor);
node.condition.walk(visitor);
});
}
},
}, AST_DWLoop);
var AST_While = DEFNODE("While", null, {
@@ -442,7 +482,7 @@ var AST_While = DEFNODE("While", null, {
node.condition.walk(visitor);
node.body.walk(visitor);
});
}
},
}, AST_DWLoop);
var AST_For = DEFNODE("For", "init condition step", {
@@ -452,6 +492,12 @@ var AST_For = DEFNODE("For", "init condition step", {
condition: "[AST_Node?] the `for` termination clause, or null if empty",
step: "[AST_Node?] the `for` update clause, or null if empty"
},
_equals: function(node) {
return prop_equals(this.init, node.init)
&& prop_equals(this.condition, node.condition)
&& prop_equals(this.step, node.step)
&& this.body.equals(node.body);
},
walk: function(visitor) {
var node = this;
visitor.visit(node, function() {
@@ -479,6 +525,11 @@ var AST_ForEnumeration = DEFNODE("ForEnumeration", "init object", {
init: "[AST_Node] the assignment target during iteration",
object: "[AST_Node] the object to iterate over"
},
_equals: function(node) {
return this.init.equals(node.init)
&& this.object.equals(node.object)
&& this.body.equals(node.body);
},
walk: function(visitor) {
var node = this;
visitor.visit(node, function() {
@@ -519,6 +570,10 @@ var AST_With = DEFNODE("With", "expression", {
$propdoc: {
expression: "[AST_Node] the `with` expression"
},
_equals: function(node) {
return this.expression.equals(node.expression)
&& this.body.equals(node.body);
},
walk: function(visitor) {
var node = this;
visitor.visit(node, function() {
@@ -621,6 +676,13 @@ var AST_Lambda = DEFNODE("Lambda", "argnames length_read rest safe_ids uses_argu
});
if (this.rest) this.rest.walk(tw);
},
_equals: function(node) {
return prop_equals(this.rest, node.rest)
&& prop_equals(this.name, node.name)
&& prop_equals(this.value, node.value)
&& all_equals(this.argnames, node.argnames)
&& all_equals(this.body, node.body);
},
walk: function(visitor) {
var node = this;
visitor.visit(node, function() {
@@ -831,6 +893,11 @@ var AST_Class = DEFNODE("Class", "extends name properties", {
extends: "[AST_Node?] the super class, or null if not specified",
properties: "[AST_ClassProperty*] array of class properties",
},
_equals: function(node) {
return prop_equals(this.name, node.name)
&& prop_equals(this.extends, node.extends)
&& all_equals(this.properties, node.properties);
},
resolve: function(def_class) {
return def_class ? this : this.parent_scope.resolve();
},
@@ -883,6 +950,12 @@ var AST_ClassProperty = DEFNODE("ClassProperty", "key private static value", {
static: "[boolean] whether this is a static property",
value: "[AST_Node?] property value (AST_Accessor for getters/setters, AST_LambdaExpression for methods, null if not specified for fields)",
},
_equals: function(node) {
return !this.private == !node.private
&& !this.static == !node.static
&& prop_equals(this.key, node.key)
&& prop_equals(this.value, node.value);
},
walk: function(visitor) {
var node = this;
visitor.visit(node, function() {
@@ -959,6 +1032,9 @@ var AST_Exit = DEFNODE("Exit", "value", {
$propdoc: {
value: "[AST_Node?] the value returned or thrown by this statement; could be null for AST_Return"
},
_equals: function(node) {
return prop_equals(this.value, node.value);
},
walk: function(visitor) {
var node = this;
visitor.visit(node, function() {
@@ -989,6 +1065,9 @@ var AST_LoopControl = DEFNODE("LoopControl", "label", {
$propdoc: {
label: "[AST_LabelRef?] the label, or null if none",
},
_equals: function(node) {
return prop_equals(this.label, node.label);
},
walk: function(visitor) {
var node = this;
visitor.visit(node, function() {
@@ -1019,6 +1098,11 @@ var AST_If = DEFNODE("If", "condition alternative", {
condition: "[AST_Node] the `if` condition",
alternative: "[AST_Statement?] the `else` part, or null if not present"
},
_equals: function(node) {
return this.body.equals(node.body)
&& this.condition.equals(node.condition)
&& prop_equals(this.alternative, node.alternative);
},
walk: function(visitor) {
var node = this;
visitor.visit(node, function() {
@@ -1116,6 +1200,10 @@ var AST_Catch = DEFNODE("Catch", "argname", {
$propdoc: {
argname: "[(AST_Destructured|AST_SymbolCatch)?] symbol for the exception, or null if not present",
},
_equals: function(node) {
return prop_equals(this.argname, node.argname)
&& all_equals(this.body, node.body);
},
walk: function(visitor) {
var node = this;
visitor.visit(node, function() {
@@ -1141,6 +1229,9 @@ var AST_Definitions = DEFNODE("Definitions", "definitions", {
$propdoc: {
definitions: "[AST_VarDef*] array of variable definitions"
},
_equals: function(node) {
return all_equals(this.definitions, node.definitions);
},
walk: function(visitor) {
var node = this;
visitor.visit(node, function() {
@@ -1197,6 +1288,10 @@ var AST_VarDef = DEFNODE("VarDef", "name value", {
name: "[AST_Destructured|AST_SymbolVar] name of the variable",
value: "[AST_Node?] initializer, or null of there's no initializer",
},
_equals: function(node) {
return this.name.equals(node.name)
&& prop_equals(this.value, node.value);
},
walk: function(visitor) {
var node = this;
visitor.visit(node, function() {
@@ -1216,6 +1311,9 @@ var AST_ExportDeclaration = DEFNODE("ExportDeclaration", "body", {
$propdoc: {
body: "[AST_DefClass|AST_Definitions|AST_LambdaDefinition] the statement to export",
},
_equals: function(node) {
return this.body.equals(node.body);
},
walk: function(visitor) {
var node = this;
visitor.visit(node, function() {
@@ -1236,6 +1334,9 @@ var AST_ExportDefault = DEFNODE("ExportDefault", "body", {
$propdoc: {
body: "[AST_Node] the default export",
},
_equals: function(node) {
return this.body.equals(node.body);
},
walk: function(visitor) {
var node = this;
visitor.visit(node, function() {
@@ -1257,6 +1358,11 @@ var AST_ExportForeign = DEFNODE("ExportForeign", "aliases keys path quote", {
path: "[string] the path to import module",
quote: "[string?] the original quote character",
},
_equals: function(node) {
return this.path == node.path
&& list_equals(this.aliases, node.aliases)
&& list_equals(this.keys, node.keys);
},
_validate: function() {
if (this.aliases.length != this.keys.length) {
throw new Error("aliases:key length mismatch: " + this.aliases.length + " != " + this.keys.length);
@@ -1280,6 +1386,9 @@ var AST_ExportReferences = DEFNODE("ExportReferences", "properties", {
$propdoc: {
properties: "[AST_SymbolExport*] array of aliases to export",
},
_equals: function(node) {
return all_equals(this.properties, node.properties);
},
walk: function(visitor) {
var node = this;
visitor.visit(node, function() {
@@ -1304,6 +1413,11 @@ var AST_Import = DEFNODE("Import", "all default path properties quote", {
properties: "[(AST_SymbolImport*)?] array of aliases, or null if not specified",
quote: "[string?] the original quote character",
},
_equals: function(node) {
return this.path == node.path
&& prop_equals(this.default, node.default)
&& (this.all ? prop_equals(this.all, node.all) : all_equals(this.properties, node.properties));
},
walk: function(visitor) {
var node = this;
visitor.visit(node, function() {
@@ -1340,6 +1454,10 @@ var AST_DefaultValue = DEFNODE("DefaultValue", "name value", {
name: "[AST_Destructured|AST_SymbolDeclaration] name of the variable",
value: "[AST_Node] value to assign if variable is `undefined`",
},
_equals: function(node) {
return this.name.equals(node.name)
&& this.value.equals(node.value);
},
walk: function(visitor) {
var node = this;
visitor.visit(node, function() {
@@ -1367,6 +1485,11 @@ var AST_Call = DEFNODE("Call", "args expression optional pure terminal", {
pure: "[boolean/S] marker for side-effect-free call expression",
terminal: "[boolean] whether the chain has ended",
},
_equals: function(node) {
return !this.optional == !node.optional
&& this.expression.equals(node.expression)
&& all_equals(this.args, node.args);
},
walk: function(visitor) {
var node = this;
visitor.visit(node, function() {
@@ -1395,6 +1518,9 @@ var AST_Sequence = DEFNODE("Sequence", "expressions", {
$propdoc: {
expressions: "[AST_Node*] array of expressions (at least two)"
},
_equals: function(node) {
return all_equals(this.expressions, node.expressions);
},
walk: function(visitor) {
var node = this;
visitor.visit(node, function() {
@@ -1422,6 +1548,11 @@ var AST_PropAccess = DEFNODE("PropAccess", "expression optional property termina
property: "[AST_Node|string] the property to access. For AST_Dot this is always a plain string, while for AST_Sub it's an arbitrary AST_Node",
terminal: "[boolean] whether the chain has ended",
},
_equals: function(node) {
return !this.optional == !node.optional
&& prop_equals(this.property, node.property)
&& this.expression.equals(node.expression);
},
get_property: function() {
var p = this.property;
if (p instanceof AST_Constant) return p.value;
@@ -1466,6 +1597,9 @@ var AST_Spread = DEFNODE("Spread", "expression", {
$propdoc: {
expression: "[AST_Node] expression to be expanded",
},
_equals: function(node) {
return this.expression.equals(node.expression);
},
walk: function(visitor) {
var node = this;
visitor.visit(node, function() {
@@ -1483,6 +1617,10 @@ var AST_Unary = DEFNODE("Unary", "operator expression", {
operator: "[string] the operator",
expression: "[AST_Node] expression that this unary operator applies to"
},
_equals: function(node) {
return this.operator == node.operator
&& this.expression.equals(node.expression);
},
walk: function(visitor) {
var node = this;
visitor.visit(node, function() {
@@ -1511,6 +1649,11 @@ var AST_Binary = DEFNODE("Binary", "operator left right", {
operator: "[string] the operator",
right: "[AST_Node] right-hand side expression"
},
_equals: function(node) {
return this.operator == node.operator
&& this.left.equals(node.left)
&& this.right.equals(node.right);
},
walk: function(visitor) {
var node = this;
visitor.visit(node, function() {
@@ -1532,6 +1675,11 @@ var AST_Conditional = DEFNODE("Conditional", "condition consequent alternative",
consequent: "[AST_Node]",
alternative: "[AST_Node]"
},
_equals: function(node) {
return this.condition.equals(node.condition)
&& this.consequent.equals(node.consequent)
&& this.alternative.equals(node.alternative);
},
walk: function(visitor) {
var node = this;
visitor.visit(node, function() {
@@ -1573,6 +1721,9 @@ var AST_Await = DEFNODE("Await", "expression", {
$propdoc: {
expression: "[AST_Node] expression with Promise to resolve on",
},
_equals: function(node) {
return this.expression.equals(node.expression);
},
walk: function(visitor) {
var node = this;
visitor.visit(node, function() {
@@ -1590,6 +1741,10 @@ var AST_Yield = DEFNODE("Yield", "expression nested", {
expression: "[AST_Node?] return value for iterator, or null if undefined",
nested: "[boolean] whether to iterate over expression as generator",
},
_equals: function(node) {
return !this.nested == !node.nested
&& prop_equals(this.expression, node.expression);
},
walk: function(visitor) {
var node = this;
visitor.visit(node, function() {
@@ -1612,6 +1767,9 @@ var AST_Array = DEFNODE("Array", "elements", {
$propdoc: {
elements: "[AST_Node*] array of elements"
},
_equals: function(node) {
return all_equals(this.elements, node.elements);
},
walk: function(visitor) {
var node = this;
visitor.visit(node, function() {
@@ -1654,6 +1812,10 @@ var AST_DestructuredArray = DEFNODE("DestructuredArray", "elements", {
$propdoc: {
elements: "[(AST_DefaultValue|AST_Destructured|AST_SymbolDeclaration|AST_SymbolRef)*] array of elements",
},
_equals: function(node) {
return prop_equals(this.rest, node.rest)
&& all_equals(this.elements, node.elements);
},
walk: function(visitor) {
var node = this;
visitor.visit(node, function() {
@@ -1671,6 +1833,10 @@ var AST_DestructuredKeyVal = DEFNODE("DestructuredKeyVal", "key value", {
key: "[string|AST_Node] property name. For computed property this is an AST_Node.",
value: "[AST_DefaultValue|AST_Destructured|AST_SymbolDeclaration|AST_SymbolRef] property value",
},
_equals: function(node) {
return prop_equals(this.key, node.key)
&& this.value.equals(node.value);
},
walk: function(visitor) {
var node = this;
visitor.visit(node, function() {
@@ -1692,6 +1858,10 @@ var AST_DestructuredObject = DEFNODE("DestructuredObject", "properties", {
$propdoc: {
properties: "[AST_DestructuredKeyVal*] array of properties",
},
_equals: function(node) {
return prop_equals(this.rest, node.rest)
&& all_equals(this.properties, node.properties);
},
walk: function(visitor) {
var node = this;
visitor.visit(node, function() {
@@ -1713,6 +1883,9 @@ var AST_Object = DEFNODE("Object", "properties", {
$propdoc: {
properties: "[(AST_ObjectProperty|AST_Spread)*] array of properties"
},
_equals: function(node) {
return all_equals(this.properties, node.properties);
},
walk: function(visitor) {
var node = this;
visitor.visit(node, function() {
@@ -1736,6 +1909,10 @@ var AST_ObjectProperty = DEFNODE("ObjectProperty", "key value", {
key: "[string|AST_Node] property name. For computed property this is an AST_Node.",
value: "[AST_Node] property value. For getters and setters this is an AST_Accessor.",
},
_equals: function(node) {
return prop_equals(this.key, node.key)
&& this.value.equals(node.value);
},
walk: function(visitor) {
var node = this;
visitor.visit(node, function() {
@@ -1790,6 +1967,9 @@ var AST_Symbol = DEFNODE("Symbol", "scope name thedef", {
scope: "[AST_Scope/S] the current scope (not necessarily the definition scope)",
thedef: "[SymbolDef/S] the definition of this symbol"
},
_equals: function(node) {
return this.thedef ? this.thedef === node.thedef : this.name == node.name;
},
_validate: function() {
if (this.TYPE == "Symbol") throw new Error("should not instantiate AST_Symbol");
if (typeof this.name != "string") throw new Error("name must be string");
@@ -1809,6 +1989,10 @@ var AST_SymbolImport = DEFNODE("SymbolImport", "key", {
$propdoc: {
key: "[string] the original `export` name",
},
_equals: function(node) {
return this.name == node.name
&& this.key == node.key;
},
_validate: function() {
if (typeof this.key != "string") throw new Error("key must be string");
},
@@ -1866,6 +2050,10 @@ var AST_SymbolExport = DEFNODE("SymbolExport", "alias", {
$propdoc: {
alias: "[string] the `export` alias",
},
_equals: function(node) {
return this.name == node.name
&& this.alias == node.alias;
},
_validate: function() {
if (typeof this.alias != "string") throw new Error("alias must be string");
},
@@ -1877,6 +2065,7 @@ var AST_LabelRef = DEFNODE("LabelRef", null, {
var AST_ObjectIdentity = DEFNODE("ObjectIdentity", null, {
$documentation: "Base class for `super` & `this`",
_equals: return_true,
_validate: function() {
if (this.TYPE == "ObjectIdentity") throw new Error("should not instantiate AST_ObjectIdentity");
},
@@ -1911,7 +2100,12 @@ var AST_Template = DEFNODE("Template", "expressions strings tag", {
$propdoc: {
expressions: "[AST_Node*] the placeholder expressions",
strings: "[string*] the raw text segments",
tag: "[AST_Node] tag function, or null if absent",
tag: "[AST_Node?] tag function, or null if absent",
},
_equals: function(node) {
return prop_equals(this.tag, node.tag)
&& list_equals(this.strings, node.strings)
&& all_equals(this.expressions, node.expressions);
},
walk: function(visitor) {
var node = this;
@@ -1936,6 +2130,9 @@ var AST_Template = DEFNODE("Template", "expressions strings tag", {
var AST_Constant = DEFNODE("Constant", null, {
$documentation: "Base class for all constants",
_equals: function(node) {
return this.value === node.value;
},
_validate: function() {
if (this.TYPE == "Constant") throw new Error("should not instantiate AST_Constant");
},
@@ -1984,6 +2181,9 @@ var AST_RegExp = DEFNODE("RegExp", "value", {
$propdoc: {
value: "[RegExp] the actual regexp"
},
_equals: function(node) {
return "" + this.value == "" + node.value;
},
_validate: function() {
if (!(this.value instanceof RegExp)) throw new Error("value must be RegExp");
},
@@ -1991,6 +2191,7 @@ var AST_RegExp = DEFNODE("RegExp", "value", {
var AST_Atom = DEFNODE("Atom", null, {
$documentation: "Base class for atoms",
_equals: return_true,
_validate: function() {
if (this.TYPE == "Atom") throw new Error("should not instantiate AST_Atom");
},

View File

@@ -270,10 +270,6 @@ Compressor.prototype.compress = function(node) {
return self;
});
AST_Node.DEFMETHOD("equivalent_to", function(node) {
return this.TYPE == node.TYPE && this.print_to_string() == node.print_to_string();
});
AST_Toplevel.DEFMETHOD("hoist_exports", function(compressor) {
if (!compressor.option("hoist_exports")) return;
var body = this.body, props = [];
@@ -930,7 +926,7 @@ Compressor.prototype.compress = function(node) {
var scan = ld || left instanceof AST_Destructured;
switch (node.operator) {
case "=":
if (left.equivalent_to(right) && !left.has_side_effects(compressor)) {
if (left.equals(right) && !left.has_side_effects(compressor)) {
right.walk(tw);
walk_prop(left);
node.redundant = true;
@@ -2031,7 +2027,7 @@ Compressor.prototype.compress = function(node) {
}
// Cascade compound assignments
if (compound && scan_lhs && can_replace && !stop_if_hit
&& node instanceof AST_Assign && node.operator != "=" && node.left.equivalent_to(lhs)) {
&& node instanceof AST_Assign && node.operator != "=" && node.left.equals(lhs)) {
replaced++;
changed = true;
AST_Node.info("Cascading {node} [{file}:{line},{col}]", {
@@ -2075,7 +2071,7 @@ Compressor.prototype.compress = function(node) {
// Replace variable with assignment when found
var hit_rhs;
if (!(node instanceof AST_SymbolDeclaration)
&& (scan_lhs && lhs.equivalent_to(node)
&& (scan_lhs && lhs.equals(node)
|| scan_rhs && (hit_rhs = scan_rhs(node, this)))) {
if (!can_replace || stop_if_hit && (hit_rhs || !lhs_local || !replace_all)) {
if (!hit_rhs && !value_def) abort = true;
@@ -2420,11 +2416,11 @@ Compressor.prototype.compress = function(node) {
if (node !== parent.init) return true;
}
if (node instanceof AST_Assign) {
return node.operator != "=" && lhs.equivalent_to(node.left);
return node.operator != "=" && lhs.equals(node.left);
}
if (node instanceof AST_Call) {
if (!(lhs instanceof AST_PropAccess)) return false;
if (!lhs.equivalent_to(node.expression)) return false;
if (!lhs.equals(node.expression)) return false;
return !(rvalue instanceof AST_LambdaExpression && !rvalue.contains_this());
}
if (node instanceof AST_Class) return !compressor.has_directive("use strict");
@@ -3102,7 +3098,7 @@ Compressor.prototype.compress = function(node) {
return !circular && rhs_exact_match;
function rhs_exact_match(node) {
return expr.equivalent_to(node);
return expr.equals(node);
}
}
@@ -3400,7 +3396,7 @@ Compressor.prototype.compress = function(node) {
var changed = false;
var parent = compressor.parent();
var self = compressor.self();
var exit, exit_defs, merge_exit;
var exit, merge_exit;
var in_iife = in_lambda && parent && parent.TYPE == "Call" && parent.expression === self;
var chain_if_returns = in_lambda && compressor.option("conditionals") && compressor.option("sequences");
var multiple_if_returns = has_multiple_if_returns(statements);
@@ -3548,7 +3544,6 @@ Compressor.prototype.compress = function(node) {
if (stat instanceof AST_Exit) {
exit = stat;
exit_defs = null;
continue;
}
@@ -3578,30 +3573,15 @@ Compressor.prototype.compress = function(node) {
if (exit.TYPE != ab.TYPE) return false;
var value = ab.value;
if (!value) return false;
var equals = exit.equivalent_to(ab);
var equals = exit.equals(ab);
if (!equals && value instanceof AST_Sequence) {
value = value.tail_node();
if (exit.value && exit.value.equivalent_to(value)) equals = 2;
if (exit.value && exit.value.equals(value)) equals = 2;
}
if (!equals && !exact && exit.value instanceof AST_Sequence) {
if (exit.value.tail_node().equivalent_to(value)) equals = 3;
if (exit.value.tail_node().equals(value)) equals = 3;
}
if (!equals) return false;
if (exit_defs == null) {
exit_defs = new Dictionary();
exit.walk(new TreeWalker(function(node) {
if (node instanceof AST_SymbolRef) exit_defs.set(node.name, node.definition());
}));
if (!exit_defs.size()) exit_defs = false;
}
var abort = false;
if (exit_defs) value.walk(new TreeWalker(function(node) {
if (abort) return true;
if (node instanceof AST_SymbolRef && exit_defs.get(node.name) !== node.definition()) {
return abort = true;
}
}));
return !abort && equals;
return equals;
}
function can_drop_abort(ab) {
@@ -4635,7 +4615,7 @@ Compressor.prototype.compress = function(node) {
if (keep_unary
&& fixed instanceof AST_UnaryPrefix
&& fixed.operator == "+"
&& fixed.expression.equivalent_to(this)) {
&& fixed.expression.equals(this)) {
return false;
}
this.is_number = return_false;
@@ -9456,10 +9436,10 @@ Compressor.prototype.compress = function(node) {
}
function match(cond) {
if (node.equivalent_to(cond)) return true;
if (node.equals(cond)) return true;
if (!(cond instanceof AST_UnaryPrefix)) return false;
if (cond.operator != "!") return false;
if (!node.equivalent_to(cond.expression)) return false;
if (!node.equals(cond.expression)) return false;
negated = true;
return true;
}
@@ -9633,9 +9613,43 @@ Compressor.prototype.compress = function(node) {
self.alternative = null;
return make_node(AST_BlockStatement, self, { body: [ self, body ] }).optimize(compressor);
}
if (self.alternative) {
var body_stats = as_array(self.body);
var body_index = last_index(body_stats);
var alt_stats = as_array(self.alternative);
var alt_index = last_index(alt_stats);
for (var stats = []; body_index >= 0 && alt_index >= 0;) {
var stat = body_stats[body_index];
if (!stat.equals(alt_stats[alt_index])) break;
body_stats.splice(body_index--, 1);
alt_stats.splice(alt_index--, 1);
stats.unshift(stat);
}
if (stats.length > 0) {
self.body = body_stats.length > 0 ? make_node(AST_BlockStatement, self, {
body: body_stats,
}) : make_node(AST_EmptyStatement, self);
self.alternative = alt_stats.length > 0 ? make_node(AST_BlockStatement, self, {
body: alt_stats,
}) : null;
stats.unshift(self);
return make_node(AST_BlockStatement, self, { body: stats }).optimize(compressor);
}
}
if (compressor.option("typeofs")) mark_locally_defined(self.condition, self.body, self.alternative);
return self;
function as_array(node) {
return node instanceof AST_BlockStatement ? node.body : [ node ];
}
function last_index(stats) {
for (var index = stats.length; --index >= 0;) {
if (!is_declaration(stats[index], true)) break;
}
return index;
}
function sequencesize(stat, defuns, var_defs, refs) {
if (stat == null) return [];
if (stat instanceof AST_BlockStatement) {
@@ -9753,7 +9767,7 @@ Compressor.prototype.compress = function(node) {
case 0:
var prev_block = make_node(AST_BlockStatement, prev, prev);
var next_block = make_node(AST_BlockStatement, branch, { body: statements });
if (prev_block.equivalent_to(next_block)) prev.body = [];
if (prev_block.equals(next_block)) prev.body = [];
}
}
if (side_effects.length) {
@@ -11457,7 +11471,7 @@ Compressor.prototype.compress = function(node) {
if (self.left instanceof AST_SymbolRef
&& assign instanceof AST_Assign
&& assign.operator == "="
&& self.left.equivalent_to(assign.left)) {
&& self.left.equals(assign.left)) {
return make_node(AST_Assign, self, {
operator: "=",
left: assign.left,
@@ -11483,7 +11497,7 @@ Compressor.prototype.compress = function(node) {
if ((self.left.is_string(compressor) && self.right.is_string(compressor)) ||
(self.left.is_number(compressor) && self.right.is_number(compressor)) ||
(self.left.is_boolean(compressor) && self.right.is_boolean(compressor)) ||
repeatable(compressor, self.left) && self.left.equivalent_to(self.right)) {
repeatable(compressor, self.left) && self.left.equals(self.right)) {
self.operator = self.operator.slice(0, 2);
}
// XXX: intentionally falling down to the next case
@@ -11530,7 +11544,7 @@ Compressor.prototype.compress = function(node) {
&& (is_undefined(lhs.left, compressor) && self.right.left instanceof AST_Null
|| lhs.left instanceof AST_Null && is_undefined(self.right.left, compressor))
&& !expr.has_side_effects(compressor)
&& expr.equivalent_to(self.right.right)) {
&& expr.equals(self.right.right)) {
lhs.operator = lhs.operator.slice(0, -1);
lhs.left = make_node(AST_Null, self);
return self.left;
@@ -11542,7 +11556,7 @@ Compressor.prototype.compress = function(node) {
if (compressor.option("booleans")) {
var lhs = self.left;
if (lazy_op[self.operator] && !lhs.has_side_effects(compressor)) {
if (lhs.equivalent_to(self.right)) {
if (lhs.equals(self.right)) {
return maintain_this_binding(compressor, parent, compressor.self(), lhs).optimize(compressor);
}
mark_duplicate_condition(compressor, lhs);
@@ -12494,7 +12508,7 @@ Compressor.prototype.compress = function(node) {
exprs.push(self.right);
return make_sequence(self, exprs).optimize(compressor);
}
if (self.left.equivalent_to(self.right) && !self.left.has_side_effects(compressor)) {
if (self.left.equals(self.right) && !self.left.has_side_effects(compressor)) {
return self.right;
}
var exp = self.left.expression;
@@ -12510,7 +12524,7 @@ Compressor.prototype.compress = function(node) {
}
} else if (self.left instanceof AST_SymbolRef && can_drop_symbol(self.left, compressor)) {
var parent;
if (self.operator == "=" && self.left.equivalent_to(self.right)
if (self.operator == "=" && self.left.equals(self.right)
&& !((parent = compressor.parent()) instanceof AST_UnaryPrefix && parent.operator == "delete")) {
return self.right;
}
@@ -12697,13 +12711,13 @@ Compressor.prototype.compress = function(node) {
var alternative = self.alternative;
if (repeatable(compressor, condition)) {
// x ? x : y ---> x || y
if (condition.equivalent_to(consequent)) return make_node(AST_Binary, self, {
if (condition.equals(consequent)) return make_node(AST_Binary, self, {
operator: "||",
left: condition,
right: alternative,
}).optimize(compressor);
// x ? y : x ---> x && y
if (condition.equivalent_to(alternative)) return make_node(AST_Binary, self, {
if (condition.equals(alternative)) return make_node(AST_Binary, self, {
operator: "&&",
left: condition,
right: consequent,
@@ -12720,7 +12734,7 @@ Compressor.prototype.compress = function(node) {
if ((is_eq || consequent === seq_tail)
&& alt_tail instanceof AST_Assign
&& seq_tail.operator == alt_tail.operator
&& seq_tail.left.equivalent_to(alt_tail.left)
&& seq_tail.left.equals(alt_tail.left)
&& (is_eq && seq_tail.left instanceof AST_SymbolRef
|| !condition.has_side_effects(compressor)
&& can_shift_lhs_of_tail(consequent)
@@ -12737,7 +12751,7 @@ Compressor.prototype.compress = function(node) {
}
}
// x ? y : y ---> x, y
if (consequent.equivalent_to(alternative)) return make_sequence(self, [
if (consequent.equals(alternative)) return make_sequence(self, [
condition,
consequent
]).optimize(compressor);
@@ -12751,7 +12765,7 @@ Compressor.prototype.compress = function(node) {
if (consequent instanceof AST_Call
&& alternative.TYPE == consequent.TYPE
&& (arg_index = arg_diff(consequent, alternative)) >= 0
&& consequent.expression.equivalent_to(alternative.expression)
&& consequent.expression.equals(alternative.expression)
&& !condition.has_side_effects(compressor)
&& !consequent.expression.has_side_effects(compressor)) {
var node = consequent.clone();
@@ -12771,7 +12785,7 @@ Compressor.prototype.compress = function(node) {
}
// x ? (y ? a : b) : b ---> x && y ? a : b
if (consequent instanceof AST_Conditional
&& consequent.alternative.equivalent_to(alternative)) {
&& consequent.alternative.equals(alternative)) {
return make_node(AST_Conditional, self, {
condition: make_node(AST_Binary, self, {
left: condition,
@@ -12784,7 +12798,7 @@ Compressor.prototype.compress = function(node) {
}
// x ? (y ? a : b) : a ---> !x || y ? a : b
if (consequent instanceof AST_Conditional
&& consequent.consequent.equivalent_to(alternative)) {
&& consequent.consequent.equals(alternative)) {
return make_node(AST_Conditional, self, {
condition: make_node(AST_Binary, self, {
left: negated,
@@ -12797,7 +12811,7 @@ Compressor.prototype.compress = function(node) {
}
// x ? a : (y ? a : b) ---> x || y ? a : b
if (alternative instanceof AST_Conditional
&& consequent.equivalent_to(alternative.consequent)) {
&& consequent.equals(alternative.consequent)) {
return make_node(AST_Conditional, self, {
condition: make_node(AST_Binary, self, {
left: condition,
@@ -12810,7 +12824,7 @@ Compressor.prototype.compress = function(node) {
}
// x ? b : (y ? a : b) ---> !x && y ? a : b
if (alternative instanceof AST_Conditional
&& consequent.equivalent_to(alternative.alternative)) {
&& consequent.equals(alternative.alternative)) {
return make_node(AST_Conditional, self, {
condition: make_node(AST_Binary, self, {
left: negated,
@@ -12823,7 +12837,7 @@ Compressor.prototype.compress = function(node) {
}
// x ? (a, c) : (b, c) ---> x ? a : b, c
if ((consequent instanceof AST_Sequence || alternative instanceof AST_Sequence)
&& consequent.tail_node().equivalent_to(alternative.tail_node())) {
&& consequent.tail_node().equals(alternative.tail_node())) {
return make_sequence(self, [
make_node(AST_Conditional, self, {
condition: condition,
@@ -12836,7 +12850,7 @@ Compressor.prototype.compress = function(node) {
// x ? y && a : a ---> (!x || y) && a
if (consequent instanceof AST_Binary
&& consequent.operator == "&&"
&& consequent.right.equivalent_to(alternative)) {
&& consequent.right.equals(alternative)) {
return make_node(AST_Binary, self, {
operator: "&&",
left: make_node(AST_Binary, self, {
@@ -12850,7 +12864,7 @@ Compressor.prototype.compress = function(node) {
// x ? y || a : a ---> x && y || a
if (consequent instanceof AST_Binary
&& consequent.operator == "||"
&& consequent.right.equivalent_to(alternative)) {
&& consequent.right.equals(alternative)) {
return make_node(AST_Binary, self, {
operator: "||",
left: make_node(AST_Binary, self, {
@@ -12864,7 +12878,7 @@ Compressor.prototype.compress = function(node) {
// x ? a : y && a ---> (x || y) && a
if (alternative instanceof AST_Binary
&& alternative.operator == "&&"
&& alternative.right.equivalent_to(consequent)) {
&& alternative.right.equals(consequent)) {
return make_node(AST_Binary, self, {
operator: "&&",
left: make_node(AST_Binary, self, {
@@ -12878,7 +12892,7 @@ Compressor.prototype.compress = function(node) {
// x ? a : y || a ---> !x && y || a
if (alternative instanceof AST_Binary
&& alternative.operator == "||"
&& alternative.right.equivalent_to(consequent)) {
&& alternative.right.equals(consequent)) {
return make_node(AST_Binary, self, {
operator: "||",
left: make_node(AST_Binary, self, {
@@ -12974,10 +12988,10 @@ Compressor.prototype.compress = function(node) {
var len = a.length;
if (len != b.length) return -2;
for (var i = 0; i < len; i++) {
if (!a[i].equivalent_to(b[i])) {
if (!a[i].equals(b[i])) {
if (a[i] instanceof AST_Spread !== b[i] instanceof AST_Spread) return -3;
for (var j = i + 1; j < len; j++) {
if (!a[j].equivalent_to(b[j])) return -2;
if (!a[j].equals(b[j])) return -2;
}
return i;
}
@@ -12998,7 +13012,7 @@ Compressor.prototype.compress = function(node) {
if (!(consequent instanceof AST_PropAccess)) return;
var p = consequent.property;
var q = alternative.property;
return (p instanceof AST_Node ? p.equivalent_to(q) : p == q)
return (p instanceof AST_Node ? p.equals(q) : p == q)
&& !(consequent.expression instanceof AST_Super || alternative.expression instanceof AST_Super);
}

View File

@@ -196,6 +196,88 @@ ifs_7: {
}
}
merge_tail_1: {
options = {
conditionals: true,
}
input: {
function f(a) {
var b = "foo";
if (a) {
while (console.log("bar"));
console.log(b);
} else {
while (console.log("baz"));
console.log(b);
}
}
f();
f(42);
}
expect: {
function f(a) {
var b = "foo";
if (a)
while (console.log("bar"));
else
while (console.log("baz"));
console.log(b);
}
f();
f(42);
}
expect_stdout: [
"baz",
"foo",
"bar",
"foo",
]
}
merge_tail_2: {
options = {
conditionals: true,
}
input: {
function f(a) {
var b = "foo";
if (a) {
while (console.log("bar"));
console.log(b);
} else {
c = "baz";
while (console.log(c));
while (console.log("bar"));
console.log(b);
var c;
}
}
f();
f(42);
}
expect: {
function f(a) {
var b = "foo";
if (!a) {
c = "baz";
while (console.log(c));
var c;
}
while (console.log("bar"));
console.log(b);
}
f();
f(42);
}
expect_stdout: [
"baz",
"bar",
"foo",
"bar",
"foo",
]
}
cond_1: {
options = {
conditionals: true,

View File

@@ -142,6 +142,80 @@ if_dead_branch: {
expect_stdout: "undefined"
}
retain_tail_1: {
options = {
conditionals: true,
}
input: {
function f(a) {
var b = "foo";
if (a) {
const b = "bar";
while (console.log("baz"));
console.log(b);
} else {
while (console.log("moo"));
console.log(b);
}
}
f();
f(42);
}
expect: {
function f(a) {
var b = "foo";
if (a) {
const b = "bar";
while (console.log("baz"));
console.log(b);
} else {
while (console.log("moo"));
console.log(b);
}
}
f();
f(42);
}
expect_stdout: true
}
retain_tail_2: {
options = {
conditionals: true,
}
input: {
function f(a) {
var b = "foo";
if (a) {
while (console.log("bar"));
console.log(b);
} else {
const b = "baz";
while (console.log("moo"));
console.log(b);
}
}
f();
f(42);
}
expect: {
function f(a) {
var b = "foo";
if (a) {
while (console.log("bar"));
console.log(b);
} else {
const b = "baz";
while (console.log("moo"));
console.log(b);
}
}
f();
f(42);
}
expect_stdout: true
}
merge_vars_1: {
options = {
merge_vars: true,
@@ -579,6 +653,37 @@ dead_block_after_return: {
expect_stdout: true
}
if_return_3: {
options = {
if_return: true,
}
input: {
var a = "PASS";
function f(b) {
if (console) {
const b = a;
return b;
} else
while (console.log("FAIL 1"));
return b;
}
console.log(f("FAIL 2"));
}
expect: {
var a = "PASS";
function f(b) {
if (console) {
const b = a;
return b;
} else
while (console.log("FAIL 1"));
return b;
}
console.log(f("FAIL 2"));
}
expect_stdout: true
}
do_if_continue_1: {
options = {
if_return: true,

View File

@@ -190,6 +190,96 @@ if_dead_branch: {
node_version: ">=4"
}
retain_tail_1: {
options = {
conditionals: true,
}
input: {
"use strict";
function f(a) {
var b = "foo";
if (a) {
let b = "bar";
while (console.log("baz"));
console.log(b);
} else {
while (console.log("moo"));
console.log(b);
}
}
f();
f(42);
}
expect: {
"use strict";
function f(a) {
var b = "foo";
if (a) {
let b = "bar";
while (console.log("baz"));
console.log(b);
} else {
while (console.log("moo"));
console.log(b);
}
}
f();
f(42);
}
expect_stdout: [
"moo",
"foo",
"baz",
"bar",
]
node_version: ">=4"
}
retain_tail_2: {
options = {
conditionals: true,
}
input: {
"use strict";
function f(a) {
var b = "foo";
if (a) {
while (console.log("bar"));
console.log(b);
} else {
let b = "baz";
while (console.log("moo"));
console.log(b);
}
}
f();
f(42);
}
expect: {
"use strict";
function f(a) {
var b = "foo";
if (a) {
while (console.log("bar"));
console.log(b);
} else {
let b = "baz";
while (console.log("moo"));
console.log(b);
}
}
f();
f(42);
}
expect_stdout: [
"moo",
"baz",
"bar",
"foo",
]
node_version: ">=4"
}
merge_vars_1: {
options = {
merge_vars: true,