Various property fixes

* Implement getter/setter with computed value
* Fix parsing getter/setter after static or generator token
* Allow storing expressions for computed expression in AST_SymbolMethod
* Allow get and set in shorthand properties in object literals

Fixes #1094, #1146 and #1221
This commit is contained in:
Anthony Van de Gejuchte
2016-07-06 00:40:28 +02:00
committed by Richard van Velzen
parent 766fafda8b
commit 72a9d799b6
6 changed files with 462 additions and 207 deletions

View File

@@ -887,8 +887,12 @@ function OutputStream(options) {
output.space(); output.space();
} }
} }
if (self.name) { if (self.name instanceof AST_Symbol) {
self.name.print(output); self.name.print(output);
} else if (nokeyword && self.name instanceof AST_Node) {
output.with_square(function() {
self.name.print(output); // Computed method name
});
} }
output.with_parens(function(){ output.with_parens(function(){
self.argnames.forEach(function(arg, i){ self.argnames.forEach(function(arg, i){
@@ -1489,6 +1493,15 @@ function OutputStream(options) {
self.default.print(output) self.default.print(output)
} }
}); });
DEFPRINT(AST_SymbolMethod, function(self, output) {
if (self.name instanceof AST_Node) {
output.with_square(function() {
self.name.print(output);
});
} else {
self._do_print(output);
}
});
DEFPRINT(AST_Undefined, function(self, output){ DEFPRINT(AST_Undefined, function(self, output){
output.print("void 0"); output.print("void 0");
}); });

View File

@@ -1738,7 +1738,7 @@ function parse($TEXT, options) {
if (!options.strict && is("punc", "}")) if (!options.strict && is("punc", "}"))
// allow trailing comma // allow trailing comma
break; break;
var start = S.token; start = S.token;
var type = start.type; var type = start.type;
var name = as_property_name(); var name = as_property_name();
if (type != "string" && type != "num" && !is("punc", ":")) { if (type != "string" && type != "num" && !is("punc", ":")) {
@@ -1747,6 +1747,8 @@ function parse($TEXT, options) {
a.push(concise); a.push(concise);
continue; continue;
} }
} else if (start.value === "*") {
unexpected(prev());
} }
if (type == "punc" && start.value == "[") { if (type == "punc" && start.value == "[") {
@@ -1802,7 +1804,7 @@ function parse($TEXT, options) {
}); });
function class_(KindOfClass) { function class_(KindOfClass) {
var start, method, class_name, name, extends_, a = []; var start, method, class_name, extends_, a = [];
if (S.token.type == "name" && S.token.value != "extends") { if (S.token.type == "name" && S.token.value != "extends") {
class_name = as_symbol(KindOfClass === AST_DefClass ? AST_SymbolDefClass : AST_SymbolClass); class_name = as_symbol(KindOfClass === AST_DefClass ? AST_SymbolDefClass : AST_SymbolClass);
@@ -1822,8 +1824,7 @@ function parse($TEXT, options) {
if (is("punc", ";")) { next(); } // Leading semicolons are okay in class bodies. if (is("punc", ";")) { next(); } // Leading semicolons are okay in class bodies.
while (!is("punc", "}")) { while (!is("punc", "}")) {
start = S.token; start = S.token;
name = as_property_name(); method = concise_method_or_getset(as_property_name(), start, true);
method = concise_method_or_getset(name, start, true);
if (!method) { unexpected(); } if (!method) { unexpected(); }
a.push(method); a.push(method);
if (is("punc", ";")) { next(); } if (is("punc", ";")) { next(); }
@@ -1841,46 +1842,64 @@ function parse($TEXT, options) {
} }
function concise_method_or_getset(name, start, is_class) { function concise_method_or_getset(name, start, is_class) {
var get_ast = function(name, token) {
if (typeof name === "string" || typeof name === "number") {
if (name === "*") {
unexpected(token);
}
return new AST_SymbolMethod({
start: token,
name: name,
end: prev()
});
}
return name;
}
var is_static = false; var is_static = false;
var is_generator = false; var is_generator = false;
if (is_class && name === "static" && !is("punc", "(")) { if (is_class && name === "static" && !is("punc", "(")) {
is_static = true; is_static = true;
name = S.token.value; name = as_property_name();
next();
} }
if (name === "*") { if (name === "*") {
is_generator = true; is_generator = true;
name = S.token.value; name = as_property_name();
next();
} }
if (is("punc", "(")) { if (is("punc", "(")) {
name = get_ast(name, start);
return new AST_ConciseMethod({ return new AST_ConciseMethod({
is_generator: is_generator, is_generator: is_generator,
start : start, start : start,
static : is_static, static : is_static,
name : new AST_SymbolMethod({ name: name }), name : name,
argnames : params_or_seq_().as_params(croak), argnames : params_or_seq_().as_params(croak),
body : _function_body(true, is_generator), body : _function_body(true, is_generator),
end : prev() end : prev()
}); });
} }
if (name == "get") { if (name == "get") {
return new AST_ObjectGetter({ if (!is("punc") || is("punc", "[")) {
start : start, name = get_ast(as_property_name(), prev());
static: is_static, return new AST_ObjectGetter({
key : as_atom_node(), start : start,
value : function_(AST_Accessor), static: is_static,
end : prev() key : name,
}); value : function_(AST_Accessor),
end : prev()
});
}
} }
if (name == "set") { else if (name == "set") {
return new AST_ObjectSetter({ if (!is("punc") || is("punc", "[")) {
start : start, name = get_ast(as_property_name(), prev());
static: is_static, return new AST_ObjectSetter({
key : as_atom_node(), start : start,
value : function_(AST_Accessor), static: is_static,
end : prev() key : name,
}); value : function_(AST_Accessor),
end : prev()
});
}
} }
} }
@@ -1996,19 +2015,22 @@ function parse($TEXT, options) {
var ex = expression(false); var ex = expression(false);
expect("]"); expect("]");
return ex; return ex;
} else unexpected(); } else unexpected(tmp);
case "operator":
if (["*", "delete", "in", "instanceof", "new", "typeof", "void"].indexOf(tmp.value) === -1) {
unexpected(tmp);
}
case "name": case "name":
if (tmp.value === "yield" && S.input.has_directive("use strict") && !is_in_generator()) { if (tmp.value === "yield" && S.input.has_directive("use strict") && !is_in_generator()) {
token_error(tmp, "SyntaxError: Unexpected yield identifier inside strict mode"); token_error(tmp, "SyntaxError: Unexpected yield identifier inside strict mode");
} }
case "string": case "string":
case "num": case "num":
case "operator":
case "keyword": case "keyword":
case "atom": case "atom":
return tmp.value; return tmp.value;
default: default:
unexpected(); unexpected(tmp);
} }
}; };

View File

@@ -47,27 +47,6 @@ regression_assign_arrow_functions: {
} }
} }
computed_property_names: {
input: {
obj({ ["x" + "x"]: 6 });
}
expect_exact: 'obj({["x"+"x"]:6});'
}
shorthand_properties: {
mangle = true;
input: (function() {
var prop = 1;
const value = {prop};
return value;
})();
expect: (function() {
var n = 1;
const r = {prop:n};
return r;
})();
}
typeof_arrow_functions: { typeof_arrow_functions: {
options = { options = {
evaluate: true evaluate: true
@@ -158,72 +137,6 @@ default_values_in_destructurings: {
expect_exact: "function x({a=4,b}){}function x([b,c=12]){}var{x=6,y}=x;var[x,y=6]=x;" expect_exact: "function x({a=4,b}){}function x([b,c=12]){}var{x=6,y}=x;var[x,y=6]=x;"
} }
concise_methods: {
input: {
x = {
foo(a, b) {
return x;
}
}
y = {
foo([{a}]) {
return a;
},
bar(){}
}
}
expect_exact: "x={foo(a,b){return x}};y={foo([{a}]){return a},bar(){}};"
}
concise_methods_and_mangle_props: {
mangle_props = {
regex: /_/
};
input: {
function x() {
obj = {
_foo() { return 1; }
}
}
}
expect: {
function x() {
obj = {
a() { return 1; }
}
}
}
}
concise_generators: {
input: {
x = {
*foo(a, b) {
return x;
}
}
y = {
*foo([{a}]) {
yield a;
},
bar(){}
}
}
expect_exact: "x={*foo(a,b){return x}};y={*foo([{a}]){yield a},bar(){}};"
}
concise_methods_and_keyword_names: {
input: {
x = {
catch() {},
throw() {}
}
}
expect: {
x={catch(){},throw(){}};
}
}
classes: { classes: {
input: { input: {
class SomeClass { class SomeClass {
@@ -309,6 +222,32 @@ classes_can_have_generators: {
} }
} }
classes_can_have_computed_generators: {
input: {
class C4 {
*['constructor']() {}
}
}
expect: {
class C4 {
*['constructor']() {}
}
}
}
classes_can_have_computed_static: {
input: {
class C4 {
static ['constructor']() {}
}
}
expect: {
class C4 {
static ['constructor']() {}
}
}
}
new_target: { new_target: {
input: { input: {
new.target; new.target;

263
test/compress/object.js Normal file
View File

@@ -0,0 +1,263 @@
getter_setter: {
input: {
var get = "bar";
var a = {
get,
set: "foo",
get bar() {
return this.get;
},
get 5() {
return "five";
},
get 0xf55() {
return "f five five";
},
get "five"() {
return 5;
},
set one(value) {
this._one = value;
},
set 9(value) {
this._nine = value;
},
set 0b1010(value) {
this._ten = value;
},
set "eleven"(value) {
this._eleven = value;
}
};
var b = {
get() { return "gift"; },
set: function(code) { return "Storing code " + code; }
};
var c = {
["get"]: "foo",
["set"]: "bar"
};
var d = {
get: "foo",
set: "bar"
};
}
expect: {
var get = "bar";
var a = {
get,
set: "foo",
get bar() {
return this.get;
},
get 5() {
return "five";
},
get 0xf55() {
return "f five five";
},
get "five"() {
return 5;
},
set one(value) {
this._one = value;
},
set 9(value) {
this._nine = value;
},
set 0b1010(value) {
this._ten = value;
},
set "eleven"(value) {
this._eleven = value;
}
};
var b = {
get() { return "gift"; },
set: function(code) { return "Storing code " + code; }
};
var c = {
["get"]: "foo",
["set"]: "bar"
};
var d = {
get: "foo",
set: "bar"
};
}
}
getter_setter_mangler: {
mangle = {}
input: {
function f(get,set) {
return {
get,
set,
get g(){},
set s(n){},
c,
a:1,
m(){}
};
}
}
expect_exact: "function f(t,e){return{get:t,set:e,get g(){},set s(t){},c,a:1,m(){}}}"
}
computed_property_names: {
input: {
obj({ ["x" + "x"]: 6 });
}
expect_exact: 'obj({["x"+"x"]:6});'
}
shorthand_properties: {
mangle = true;
input: (function() {
var prop = 1;
const value = {prop};
return value;
})();
expect: (function() {
var n = 1;
const r = {prop:n};
return r;
})();
}
concise_methods: {
input: {
x = {
foo(a, b) {
return x;
}
}
y = {
foo([{a}]) {
return a;
},
bar(){}
}
}
expect_exact: "x={foo(a,b){return x}};y={foo([{a}]){return a},bar(){}};"
}
concise_methods_with_computed_property: {
options = {
evaluate: true
}
input: {
var foo = {
[Symbol.iterator]() {
return { /* stuff */ }
},
[1 + 2]() {
return 3;
}
}
}
expect: {
var foo = {
[Symbol.iterator]() {
return { /* stuff */ }
},
[3]() {
return 3;
}
}
}
}
concise_methods_and_mangle_props: {
mangle_props = {
regex: /_/
};
input: {
function x() {
obj = {
_foo() { return 1; }
}
}
}
expect: {
function x() {
obj = {
a() { return 1; }
}
}
}
}
concise_generators: {
input: {
x = {
*foo(a, b) {
return x;
}
}
y = {
*foo([{a}]) {
yield a;
},
bar(){}
}
}
expect_exact: "x={*foo(a,b){return x}};y={*foo([{a}]){yield a},bar(){}};"
}
concise_methods_and_keyword_names: {
input: {
x = {
catch() {},
throw() {}
}
}
expect: {
x={catch(){},throw(){}};
}
}
getter_setter_with_computed_value: {
input: {
class C {
get ['a']() {
return 'A';
}
set ['a'](value) {
do_something(a);
}
}
var x = {
get [a.b]() {
return 42;
}
};
class MyArray extends Array {
get [Symbol.species]() {
return Array;
}
}
}
expect: {
class C {
get ['a']() {
return 'A';
}
set ['a'](value) {
do_something(a);
}
}
var x = {
get [a.b]() {
return 42;
}
};
class MyArray extends Array {
get [Symbol.species]() {
return Array;
}
}
}
}

View File

@@ -1,89 +0,0 @@
var UglifyJS = require('../../');
var assert = require("assert");
describe("Getters and setters", function() {
it("Should not accept operator symbols as getter/setter name", function() {
var illegalOperators = [
"++",
"--",
"+",
"-",
"!",
"~",
"&",
"|",
"^",
"*",
"/",
"%",
">>",
"<<",
">>>",
"<",
">",
"<=",
">=",
"==",
"===",
"!=",
"!==",
"?",
"=",
"+=",
"-=",
"/=",
"*=",
"%=",
">>=",
"<<=",
">>>=",
"|=",
"^=",
"&=",
"&&",
"||"
];
var generator = function() {
var results = [];
for (var i in illegalOperators) {
results.push({
code: "var obj = { get " + illegalOperators[i] + "() { return test; }};",
operator: illegalOperators[i],
method: "get"
});
results.push({
code: "var obj = { set " + illegalOperators[i] + "(value) { test = value}};",
operator: illegalOperators[i],
method: "set"
});
}
return results;
};
var testCase = function(data) {
return function() {
UglifyJS.parse(data.code);
};
};
var fail = function(data) {
return function (e) {
return e instanceof UglifyJS.JS_Parse_Error &&
e.message === "SyntaxError: Invalid getter/setter name: " + data.operator;
};
};
var errorMessage = function(data) {
return "Expected but didn't get a syntax error while parsing following line:\n" + data.code;
};
var tests = generator();
for (var i = 0; i < tests.length; i++) {
var test = tests[i];
assert.throws(testCase(test), fail(test), errorMessage(test));
}
});
});

View File

@@ -2,12 +2,12 @@ var Uglify = require("../../");
var assert = require("assert"); var assert = require("assert");
describe("Object", function() { describe("Object", function() {
it ("Should allow objects to have a methodDefinition as property", function() { it("Should allow objects to have a methodDefinition as property", function() {
var code = "var a = {test() {return true;}}"; var code = "var a = {test() {return true;}}";
assert.equal(Uglify.minify(code, {fromString: true}).code, "var a={test(){return!0}};"); assert.equal(Uglify.minify(code, {fromString: true}).code, "var a={test(){return!0}};");
}); });
it ("Should not allow objects to use static keywords like in classes", function() { it("Should not allow objects to use static keywords like in classes", function() {
var code = "{static test() {}}"; var code = "{static test() {}}";
var parse = function() { var parse = function() {
Uglify.parse(code); Uglify.parse(code);
@@ -17,4 +17,111 @@ describe("Object", function() {
} }
assert.throws(parse, expect); assert.throws(parse, expect);
}); });
});
it("Should not allow objects to have static computed properties like in classes", function() {
var code = "var foo = {static [123](){}}";
var parse = function() {
console.log(Uglify.parse(code).body[0].body[0]);
}
var expect = function(e) {
return e instanceof Uglify.JS_Parse_Error;
}
assert.throws(parse, expect);
});
it("Should not accept operator tokens as property/getter/setter name", function() {
var illegalOperators = [
"++",
"--",
"+",
"-",
"!",
"~",
"&",
"|",
"^",
"*",
"/",
"%",
">>",
"<<",
">>>",
"<",
">",
"<=",
">=",
"==",
"===",
"!=",
"!==",
"?",
"=",
"+=",
"-=",
"/=",
"*=",
"%=",
">>=",
"<<=",
">>>=",
"|=",
"^=",
"&=",
"&&",
"||"
];
var generator = function() {
var results = [];
for (var i in illegalOperators) {
results.push({
code: "var obj = { get " + illegalOperators[i] + "() { return test; }};",
operator: illegalOperators[i],
method: "get"
});
results.push({
code: "var obj = { set " + illegalOperators[i] + "(value) { test = value}};",
operator: illegalOperators[i],
method: "set"
});
results.push({
code: "var obj = { " + illegalOperators[i] + ': "123"};',
operator: illegalOperators[i],
method: "key"
});
results.push({
code: "var obj = { " + illegalOperators[i] + "(){ return test; }};",
operator: illegalOperators[i],
method: "method"
});
}
return results;
};
var testCase = function(data) {
return function() {
Uglify.parse(data.code);
};
};
var fail = function(data) {
return function (e) {
return e instanceof Uglify.JS_Parse_Error && (
e.message === "SyntaxError: Unexpected token: operator (" + data.operator + ")" ||
(e.message === "SyntaxError: Unterminated regular expression" && data.operator[0] === "/") ||
(e.message === "SyntaxError: Unexpected token: punc (()" && data.operator === "*")
);
};
};
var errorMessage = function(data) {
return "Expected but didn't get a syntax error while parsing following line:\n" + data.code;
};
var tests = generator();
for (var i = 0; i < tests.length; i++) {
var test = tests[i];
assert.throws(testCase(test), fail(test), errorMessage(test));
}
});
});