var AST_ObjectProperty = DEFNODE("ObjectProperty", "key value", {
$documentation: "Base class for literal object properties",
$propdoc: {
- key: "[string|AST_SymbolAccessor] property name. For ObjectKeyVal this is a string. For getters and setters this is an AST_SymbolAccessor.",
- value: "[AST_Node] property value. For getters and setters this is an AST_Accessor."
+ 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.",
},
walk: function(visitor) {
var node = this;
visitor.visit(node, function() {
+ if (node.key instanceof AST_Node) node.key.walk(visitor);
node.value.walk(visitor);
});
- }
+ },
+ _validate: function() {
+ if (typeof this.key != "string") {
+ if (!(this.key instanceof AST_Node)) throw new Error("key must be string or AST_Node");
+ must_be_expression(this, "key");
+ }
+ if (!(this.value instanceof AST_Node)) throw new Error("value must be AST_Node");
+ },
});
-var AST_ObjectKeyVal = DEFNODE("ObjectKeyVal", "quote", {
+var AST_ObjectKeyVal = DEFNODE("ObjectKeyVal", null, {
$documentation: "A key: value object property",
- $propdoc: {
- quote: "[string] the original quote character"
- },
_validate: function() {
- if (typeof this.key != "string") throw new Error("key must be string");
must_be_expression(this, "value");
},
}, AST_ObjectProperty);
var AST_ObjectSetter = DEFNODE("ObjectSetter", null, {
$documentation: "An object setter property",
_validate: function() {
- if (!(this.key instanceof AST_SymbolAccessor)) throw new Error("key must be AST_SymbolAccessor");
if (!(this.value instanceof AST_Accessor)) throw new Error("value must be AST_Accessor");
},
}, AST_ObjectProperty);
var AST_ObjectGetter = DEFNODE("ObjectGetter", null, {
$documentation: "An object getter property",
_validate: function() {
- if (!(this.key instanceof AST_SymbolAccessor)) throw new Error("key must be AST_SymbolAccessor");
if (!(this.value instanceof AST_Accessor)) throw new Error("value must be AST_Accessor");
},
}, AST_ObjectProperty);
},
});
-var AST_SymbolAccessor = DEFNODE("SymbolAccessor", null, {
- $documentation: "The name of a property accessor (setter/getter function)"
-}, AST_Symbol);
-
var AST_SymbolDeclaration = DEFNODE("SymbolDeclaration", "init", {
$documentation: "A declaration symbol (symbol in var, function name or argument, symbol in catch)",
}, AST_Symbol);
}
} else if (expr instanceof AST_Object) {
expr.properties.forEach(function(prop) {
- if (prop instanceof AST_ObjectKeyVal) {
- hit_stack.push(prop);
- extract_candidates(prop.value);
- hit_stack.pop();
- }
+ hit_stack.push(prop);
+ if (prop.key instanceof AST_Node) extract_candidates(prop.key);
+ if (prop instanceof AST_ObjectKeyVal) extract_candidates(prop.value);
+ hit_stack.pop();
});
} else if (expr instanceof AST_Sequence) {
expr.expressions.forEach(extract_candidates);
if (parent instanceof AST_Exit) return node;
if (parent instanceof AST_If) return node;
if (parent instanceof AST_IterationStatement) return node;
- if (parent instanceof AST_ObjectKeyVal) return node;
+ if (parent instanceof AST_ObjectProperty) return node;
if (parent instanceof AST_PropAccess) return node;
if (parent instanceof AST_Sequence) {
return (parent.tail_node() === node ? find_stop : find_stop_unused)(parent, level + 1);
if (parent.condition !== node) return node;
return find_stop_value(parent, level + 1);
}
- if (parent instanceof AST_ObjectKeyVal) {
+ if (parent instanceof AST_ObjectProperty) {
var obj = scanner.parent(level + 1);
return all(obj.properties, function(prop) {
return prop instanceof AST_ObjectKeyVal;
if (parent instanceof AST_Exit) return find_stop_unused(parent, level + 1);
if (parent instanceof AST_If) return find_stop_unused(parent, level + 1);
if (parent instanceof AST_IterationStatement) return node;
- if (parent instanceof AST_ObjectKeyVal) {
+ if (parent instanceof AST_ObjectProperty) {
var obj = scanner.parent(level + 1);
return all(obj.properties, function(prop) {
return prop instanceof AST_ObjectKeyVal;
if (prop instanceof AST_Node) break;
prop = "" + prop;
var diff = prop == "__proto__" || compressor.has_directive("use strict") ? function(node) {
- return node.key != prop && node.key.name != prop;
+ return typeof node.key == "string" && node.key != prop;
} : function(node) {
- return node.key.name != prop;
+ if (node instanceof AST_ObjectGetter || node instanceof AST_ObjectSetter) {
+ return typeof node.key == "string" && node.key != prop;
+ }
+ return true;
};
if (!all(value.properties, diff)) break;
value.properties.push(make_node(AST_ObjectKeyVal, node, {
def(AST_Lambda, return_false);
def(AST_Null, return_true);
def(AST_Object, function(compressor) {
- if (!is_strict(compressor)) return false;
- for (var i = this.properties.length; --i >=0;)
- if (this.properties[i].value instanceof AST_Accessor) return true;
- return false;
+ return is_strict(compressor) && !all(this.properties, function(prop) {
+ return prop instanceof AST_ObjectKeyVal;
+ });
});
def(AST_Sequence, function(compressor) {
return this.tail_node()._dot_throw(compressor);
var val = {};
for (var i = 0; i < this.properties.length; i++) {
var prop = this.properties[i];
+ if (!(prop instanceof AST_ObjectKeyVal)) return this;
var key = prop.key;
- if (key instanceof AST_Symbol) key = key.name;
+ if (key instanceof AST_Node) {
+ key = key._eval(compressor, ignore_side_effects, cached, depth);
+ if (key === prop.key) return this;
+ }
if (prop.value instanceof AST_Function) {
if (typeof Object.prototype[key] == "function") return this;
continue;
return any(this.properties, compressor);
});
def(AST_ObjectProperty, function(compressor) {
- return this.value.has_side_effects(compressor);
+ return this.key instanceof AST_Node && this.key.has_side_effects(compressor)
+ || this.value.has_side_effects(compressor);
});
def(AST_Sub, function(compressor) {
return this.expression.may_throw_on_access(compressor)
return any(this.properties, compressor);
});
def(AST_ObjectProperty, function(compressor) {
- return this.value.may_throw(compressor);
+ return this.key instanceof AST_Node && this.key.may_throw(compressor)
+ || this.value.may_throw(compressor);
});
def(AST_Return, function(compressor) {
return this.value && this.value.may_throw(compressor);
return all(this.properties);
});
def(AST_ObjectProperty, function() {
- return this.value.is_constant_expression();
+ return typeof this.key == "string" && this.value.is_constant_expression();
});
def(AST_Unary, function() {
return this.expression.is_constant_expression();
return right instanceof AST_Object
&& right.properties.length > 0
&& all(right.properties, function(prop) {
- return prop instanceof AST_ObjectKeyVal;
+ return prop instanceof AST_ObjectKeyVal && typeof prop.key == "string";
})
&& all(def.references, function(ref) {
return ref.fixed_value() === right;
return safe_to_drop(this, compressor) ? null : this;
});
def(AST_Object, function(compressor, first_in_statement) {
- var values = trim(this.properties, compressor, first_in_statement);
+ var exprs = [];
+ this.properties.forEach(function(prop) {
+ if (prop.key instanceof AST_Node) exprs.push(prop.key);
+ exprs.push(prop.value);
+ });
+ var values = trim(exprs, compressor, first_in_statement);
return values && make_sequence(this, values);
});
- def(AST_ObjectProperty, function(compressor, first_in_statement) {
- return this.value.drop_side_effect_free(compressor, first_in_statement);
- });
def(AST_Sequence, function(compressor, first_in_statement) {
var expressions = trim(this.expressions, compressor, first_in_statement);
if (!expressions) return null;
var props = expr.properties;
for (var i = props.length; --i >= 0;) {
var prop = props[i];
- if ("" + prop.key == key) {
- if (!all(props, function(prop) {
- return prop instanceof AST_ObjectKeyVal;
- })) break;
- if (!safe_to_flatten(prop.value, compressor)) break;
- return make_node(AST_Sub, this, {
- expression: make_node(AST_Array, expr, {
- elements: props.map(function(prop) {
- return prop.value;
- })
- }),
- property: make_node(AST_Number, this, {
- value: i
+ if (prop.key != key) continue;
+ if (!all(props, function(prop) {
+ return prop instanceof AST_ObjectKeyVal && typeof prop.key == "string";
+ })) break;
+ if (!safe_to_flatten(prop.value, compressor)) break;
+ return make_node(AST_Sub, this, {
+ expression: make_node(AST_Array, expr, {
+ elements: props.map(function(prop) {
+ return prop.value;
})
- });
- }
+ }),
+ property: make_node(AST_Number, this, {
+ value: i
+ })
+ });
}
}
});
var keys = new Dictionary();
var values = [];
self.properties.forEach(function(prop) {
- if (typeof prop.key != "string") {
- flush();
- values.push(prop);
- return;
+ if (prop.key instanceof AST_Node) {
+ var key = prop.key.evaluate(compressor);
+ if (key !== prop.key) prop.key = "" + key;
}
- if (prop.value.has_side_effects(compressor)) {
+ if (prop instanceof AST_ObjectKeyVal && typeof prop.key == "string") {
+ if (prop.value.has_side_effects(compressor)) flush();
+ keys.add(prop.key, prop.value);
+ } else {
flush();
+ values.push(prop);
}
- keys.add(prop.key, prop.value);
});
flush();
- if (self.properties.length != values.length) {
- return make_node(AST_Object, self, {
- properties: values
- });
- }
+ if (self.properties.length != values.length) return make_node(AST_Object, self, {
+ properties: values
+ });
return self;
function flush() {
value : from_moz(M.value)
};
if (M.kind == "init") return new AST_ObjectKeyVal(args);
- args.key = new AST_SymbolAccessor({
- name: args.key
- });
args.value = new AST_Accessor(args.value);
if (M.kind == "get") return new AST_ObjectGetter(args);
if (M.kind == "set") return new AST_ObjectSetter(args);
def_to_moz(AST_ObjectProperty, function To_Moz_Property(M) {
var key = {
type: "Literal",
- value: M.key instanceof AST_SymbolAccessor ? M.key.name : M.key
+ value: M.key
};
var kind;
if (M instanceof AST_ObjectKeyVal) {
// (false, true) ? (a = 10, b = 20) : (c = 30)
// ==> 20 (side effect, set a := 10 and b := 20)
|| p instanceof AST_Conditional
+ // { [(1, 2)]: 3 }[2] ==> 3
// { foo: (1, 2) }.foo ==> 2
|| p instanceof AST_ObjectProperty
// (1, {foo:2}).foo or (1, {foo:2})["foo"] ==> 2
else print_braced_empty(this, output);
});
- function print_property_name(key, quote, output) {
- if (output.option("quote_keys")) {
+ function print_property_key(self, output) {
+ var key = self.key;
+ if (key instanceof AST_Node) {
+ output.with_square(function() {
+ key.print(output);
+ });
+ } else if (output.option("quote_keys")) {
output.print_string(key);
} else if ("" + +key == key && key >= 0) {
output.print(make_num(key));
- } else if (RESERVED_WORDS[key] ? !output.option("ie8") : is_identifier_string(key)) {
- if (quote && output.option("keep_quoted_props")) {
- output.print_string(key, quote);
+ } else {
+ var quote = self.start && self.start.quote;
+ if (RESERVED_WORDS[key] ? !output.option("ie8") : is_identifier_string(key)) {
+ if (quote && output.option("keep_quoted_props")) {
+ output.print_string(key, quote);
+ } else {
+ output.print_name(key);
+ }
} else {
- output.print_name(key);
+ output.print_string(key, quote);
}
- } else {
- output.print_string(key, quote);
}
}
DEFPRINT(AST_ObjectKeyVal, function(output) {
var self = this;
- print_property_name(self.key, self.quote, output);
+ print_property_key(self, output);
output.colon();
self.value.print(output);
});
var self = this;
output.print(type);
output.space();
- print_property_name(self.key.name, self.quote, output);
+ print_property_key(self, output);
self.value._codegen(output, true);
};
}
output.add_mapping(this.start);
});
- DEFMAP([
- AST_ObjectGetter,
- AST_ObjectSetter,
- ], function(output) {
- output.add_mapping(this.start, this.key.name);
- });
-
DEFMAP([ AST_ObjectProperty ], function(output) {
- output.add_mapping(this.start, this.key);
+ if (typeof this.key == "string") output.add_mapping(this.start, this.key);
});
})();
// allow trailing comma
if (!options.strict && is("punc", "}")) break;
var start = S.token;
- var type = start.type;
- var name = as_property_name();
+ var key = as_property_key();
if (is("punc", "(")) {
var func_start = S.token;
var func = function_(AST_Function);
func.end = prev();
a.push(new AST_ObjectKeyVal({
start: start,
- quote: start.quote,
- key: "" + name,
+ key: key,
value: func,
end: prev(),
}));
continue;
}
- if (!is("punc", ":") && type == "name") switch (name) {
+ if (!is("punc", ":") && start.type == "name") switch (key) {
case "get":
a.push(new AST_ObjectGetter({
start: start,
- key: new AST_SymbolAccessor({
- start: S.token,
- name: "" + as_property_name(),
- end: prev(),
- }),
+ key: as_property_key(),
value: create_accessor(),
end: prev(),
}));
case "set":
a.push(new AST_ObjectSetter({
start: start,
- key: new AST_SymbolAccessor({
- start: S.token,
- name: "" + as_property_name(),
- end: prev(),
- }),
+ key: as_property_key(),
value: create_accessor(),
end: prev(),
}));
default:
a.push(new AST_ObjectKeyVal({
start: start,
- quote: start.quote,
- key: "" + name,
+ key: key,
value: _make_symbol(AST_SymbolRef, start),
end: prev(),
}));
expect(":");
a.push(new AST_ObjectKeyVal({
start: start,
- quote: start.quote,
- key: "" + name,
+ key: key,
value: expression(false),
end: prev(),
}));
return new AST_Object({ properties: a });
});
- function as_property_name() {
+ function as_property_key() {
var tmp = S.token;
switch (tmp.type) {
case "operator":
case "keyword":
case "atom":
next();
- return tmp.value;
+ return "" + tmp.value;
+ case "punc":
+ if (tmp.value != "[") unexpected();
+ next();
+ var key = expression(false);
+ expect("]");
+ return key;
default:
unexpected();
}
function reserve_quoted_keys(ast, reserved) {
ast.walk(new TreeWalker(function(node) {
- if (node instanceof AST_ObjectKeyVal) {
- if (node.quote) add(node.key);
+ if (node instanceof AST_ObjectProperty) {
+ if (node.start && node.start.quote) add(node.key);
} else if (node instanceof AST_Sub) {
addStrings(node.property, add);
}
}
} else if (node instanceof AST_Dot) {
add(node.property);
- } else if (node instanceof AST_ObjectKeyVal) {
- add(node.key);
} else if (node instanceof AST_ObjectProperty) {
- // setter or getter, since KeyVal is handled above
- add(node.key.name);
+ if (typeof node.key == "string") add(node.key);
} else if (node instanceof AST_Sub) {
addStrings(node.property, add);
}
}
} else if (node instanceof AST_Dot) {
node.property = mangle(node.property);
- } else if (node instanceof AST_ObjectKeyVal) {
- node.key = mangle(node.key);
} else if (node instanceof AST_ObjectProperty) {
- // setter or getter
- node.key.name = mangle(node.key.name);
+ if (typeof node.key == "string") node.key = mangle(node.key);
} else if (node instanceof AST_Sub) {
if (!options.keep_quoted) mangleStrings(node.property);
}
self.properties = do_list(self.properties, tw);
});
DEF(AST_ObjectProperty, function(self, tw) {
+ if (self.key instanceof AST_Node) self.key = self.key.transform(tw);
self.value = self.value.transform(tw);
});
})(function(node, descend) {
"8 7 8",
]
}
+
+evaluate_computed_key: {
+ options = {
+ evaluate: true,
+ objects: true,
+ }
+ input: {
+ console.log({
+ ["foo" + "bar"]: "PASS",
+ }.foobar);
+ }
+ expect: {
+ console.log({
+ foobar: "PASS",
+ }.foobar);
+ }
+ expect_stdout: "PASS"
+ node_version: ">=4"
+}
+
+keep_computed_key: {
+ options = {
+ side_effects: true,
+ }
+ input: {
+ ({
+ [console.log("PASS")]: 42,
+ });
+ }
+ expect: {
+ console.log("PASS");
+ }
+ expect_stdout: "PASS"
+ node_version: ">=4"
+}
it("Should give correct positions for accessors", function() {
// location 0 1 2 3 4
// 01234567890123456789012345678901234567890123456789
- var ast = UglifyJS.parse("var obj = { get latest() { return undefined; } }");
+ var ast = UglifyJS.parse("var obj = { get [prop]() { return undefined; } }");
// test all AST_ObjectProperty tokens are set as expected
var found = false;
ast.walk(new UglifyJS.TreeWalker(function(node) {
found = true;
assert.equal(node.start.pos, 12);
assert.equal(node.end.endpos, 46);
- assert(node.key instanceof UglifyJS.AST_SymbolAccessor);
- assert.equal(node.key.start.pos, 16);
- assert.equal(node.key.end.endpos, 22);
+ assert(node.key instanceof UglifyJS.AST_SymbolRef);
+ assert.equal(node.key.start.pos, 17);
+ assert.equal(node.key.end.endpos, 21);
assert(node.value instanceof UglifyJS.AST_Accessor);
assert.equal(node.value.start.pos, 22);
assert.equal(node.value.end.endpos, 46);
return key;
}
-function createObjectFunction(type, recurmax, stmtDepth, canThrow) {
+function createObjectKey(recurmax, stmtDepth, canThrow) {
+ return rng(10) ? KEYS[rng(KEYS.length)] : "[" + createExpression(recurmax, NO_COMMA, stmtDepth, canThrow) + "]";
+}
+
+function createObjectFunction(recurmax, stmtDepth, canThrow) {
var namesLenBefore = VAR_NAMES.length;
var s;
createBlockVariables(recurmax, stmtDepth, canThrow, function(defns) {
- switch (type) {
- case "get":
+ switch (rng(3)) {
+ case 0:
s = [
- "get " + getDotKey() + "(){",
+ "get " + createObjectKey(recurmax, stmtDepth, canThrow) + "(){",
strictMode(),
defns(),
_createStatements(2, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth),
"},",
];
break;
- case "set":
- var prop1 = getDotKey();
+ case 1:
+ var prop1 = createObjectKey(recurmax, stmtDepth, canThrow);
var prop2;
do {
prop2 = getDotKey();
break;
default:
s = [
- type + "(" + createParams(NO_DUPLICATE) + "){",
+ createObjectKey(recurmax, stmtDepth, canThrow) + "(" + createParams(NO_DUPLICATE) + "){",
strictMode(),
defns(),
_createStatements(3, recurmax, canThrow, CANNOT_BREAK, CANNOT_CONTINUE, CAN_RETURN, stmtDepth),
function createObjectLiteral(recurmax, stmtDepth, canThrow) {
recurmax--;
var obj = ["({"];
- for (var i = rng(6); --i >= 0;) switch (rng(50)) {
+ for (var i = rng(6); --i >= 0;) switch (rng(30)) {
case 0:
- obj.push(createObjectFunction("get", recurmax, stmtDepth, canThrow));
+ obj.push(createObjectFunction(recurmax, stmtDepth, canThrow));
break;
case 1:
- obj.push(createObjectFunction("set", recurmax, stmtDepth, canThrow));
- break;
- case 2:
- obj.push(createObjectFunction(KEYS[rng(KEYS.length)], recurmax, stmtDepth, canThrow));
- break;
- case 3:
obj.push(getVarName() + ",");
break;
default:
- obj.push(KEYS[rng(KEYS.length)] + ":(" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "),");
+ obj.push(createObjectKey(recurmax, stmtDepth, canThrow) + ":(" + createExpression(recurmax, COMMA_OK, stmtDepth, canThrow) + "),");
break;
}
obj.push("})");