aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/ast.js55
-rw-r--r--lib/compress.js65
-rw-r--r--lib/output.js26
-rw-r--r--lib/parse.js156
-rw-r--r--lib/scope.js13
-rw-r--r--lib/transform.js8
-rw-r--r--lib/utils.js8
7 files changed, 265 insertions, 66 deletions
diff --git a/lib/ast.js b/lib/ast.js
index 31281c1e..87d578fc 100644
--- a/lib/ast.js
+++ b/lib/ast.js
@@ -502,10 +502,9 @@ var AST_Toplevel = DEFNODE("Toplevel", "globals", {
}
}, AST_Scope);
-var AST_Lambda = DEFNODE("Lambda", "name argnames length_read uses_arguments", {
+var AST_Lambda = DEFNODE("Lambda", "argnames length_read uses_arguments", {
$documentation: "Base class for functions",
$propdoc: {
- name: "[AST_SymbolDeclaration?] the name of this function",
argnames: "[(AST_Destructured|AST_SymbolFunarg)*] array of function arguments and/or destructured literals",
uses_arguments: "[boolean/S] tells whether this function accesses the arguments array",
},
@@ -541,18 +540,49 @@ var AST_Lambda = DEFNODE("Lambda", "name argnames length_read uses_arguments", {
}, AST_Scope);
var AST_Accessor = DEFNODE("Accessor", null, {
- $documentation: "A setter/getter function. The `name` property is always null.",
+ $documentation: "A getter/setter function",
_validate: function() {
if (this.name != null) throw new Error("name must be null");
},
}, AST_Lambda);
function is_function(node) {
- return node instanceof AST_AsyncFunction || node instanceof AST_Function;
+ return node instanceof AST_Arrow || node instanceof AST_AsyncFunction || node instanceof AST_Function;
}
-var AST_AsyncFunction = DEFNODE("AsyncFunction", "inlined", {
+var AST_Arrow = DEFNODE("Arrow", "inlined value", {
+ $documentation: "An arrow function expression",
+ $propdoc: {
+ value: "[AST_Node?] simple return expression, or null if using function body.",
+ },
+ walk: function(visitor) {
+ var node = this;
+ visitor.visit(node, function() {
+ node.argnames.forEach(function(argname) {
+ argname.walk(visitor);
+ });
+ if (node.value) {
+ node.value.walk(visitor);
+ } else {
+ walk_body(node, visitor);
+ }
+ });
+ },
+ _validate: function() {
+ if (this.name != null) throw new Error("name must be null");
+ if (this.uses_arguments) throw new Error("uses_arguments must be false");
+ if (this.value != null) {
+ must_be_expression(this, "value");
+ if (this.body.length) throw new Error("body must be empty if value exists");
+ }
+ },
+}, AST_Lambda);
+
+var AST_AsyncFunction = DEFNODE("AsyncFunction", "inlined name", {
$documentation: "An asynchronous function expression",
+ $propdoc: {
+ name: "[AST_SymbolLambda?] the name of this function",
+ },
_validate: function() {
if (this.name != null) {
if (!(this.name instanceof AST_SymbolLambda)) throw new Error("name must be AST_SymbolLambda");
@@ -560,8 +590,11 @@ var AST_AsyncFunction = DEFNODE("AsyncFunction", "inlined", {
},
}, AST_Lambda);
-var AST_Function = DEFNODE("Function", "inlined", {
+var AST_Function = DEFNODE("Function", "inlined name", {
$documentation: "A function expression",
+ $propdoc: {
+ name: "[AST_SymbolLambda?] the name of this function",
+ },
_validate: function() {
if (this.name != null) {
if (!(this.name instanceof AST_SymbolLambda)) throw new Error("name must be AST_SymbolLambda");
@@ -573,15 +606,21 @@ function is_defun(node) {
return node instanceof AST_AsyncDefun || node instanceof AST_Defun;
}
-var AST_AsyncDefun = DEFNODE("AsyncDefun", "inlined", {
+var AST_AsyncDefun = DEFNODE("AsyncDefun", "inlined name", {
$documentation: "An asynchronous function definition",
+ $propdoc: {
+ name: "[AST_SymbolDefun] the name of this function",
+ },
_validate: function() {
if (!(this.name instanceof AST_SymbolDefun)) throw new Error("name must be AST_SymbolDefun");
},
}, AST_Lambda);
-var AST_Defun = DEFNODE("Defun", "inlined", {
+var AST_Defun = DEFNODE("Defun", "inlined name", {
$documentation: "A function definition",
+ $propdoc: {
+ name: "[AST_SymbolDefun] the name of this function",
+ },
_validate: function() {
if (!(this.name instanceof AST_SymbolDefun)) throw new Error("name must be AST_SymbolDefun");
},
diff --git a/lib/compress.js b/lib/compress.js
index a46cd42b..ffc19b25 100644
--- a/lib/compress.js
+++ b/lib/compress.js
@@ -49,6 +49,7 @@ function Compressor(options, false_by_default) {
TreeTransformer.call(this, this.before, this.after);
this.options = defaults(options, {
arguments : !false_by_default,
+ arrows : !false_by_default,
assignments : !false_by_default,
booleans : !false_by_default,
collapse_vars : !false_by_default,
@@ -1372,7 +1373,8 @@ merge(Compressor.prototype, {
function is_iife_call(node) {
if (node.TYPE != "Call") return false;
- return is_function(node.expression) || is_iife_call(node.expression);
+ var exp = node.expression;
+ return exp instanceof AST_AsyncFunction || exp instanceof AST_Function || is_iife_call(exp);
}
function is_undeclared_ref(node) {
@@ -3573,12 +3575,20 @@ merge(Compressor.prototype, {
return map;
}
- AST_Lambda.DEFMETHOD("first_statement", function() {
- var body = this.body;
+ function skip_directives(body) {
for (var i = 0; i < body.length; i++) {
var stat = body[i];
if (!(stat instanceof AST_Directive)) return stat;
}
+ }
+ AST_Arrow.DEFMETHOD("first_statement", function() {
+ if (this.value) return make_node(AST_Return, this.value, {
+ value: this.value
+ });
+ return skip_directives(this.body);
+ });
+ AST_Lambda.DEFMETHOD("first_statement", function() {
+ return skip_directives(this.body);
});
function try_evaluate(compressor, node) {
@@ -4170,6 +4180,9 @@ merge(Compressor.prototype, {
def(AST_Statement, function() {
throw new Error("Cannot negate a statement");
});
+ def(AST_Arrow, function() {
+ return basic_negation(this);
+ });
def(AST_AsyncFunction, function() {
return basic_negation(this);
});
@@ -4568,6 +4581,10 @@ merge(Compressor.prototype, {
result = false;
return true;
}
+ if (node instanceof AST_This) {
+ if (scopes.length == 0 && self instanceof AST_Arrow) result = false;
+ return true;
+ }
}));
return result;
});
@@ -4651,6 +4668,28 @@ merge(Compressor.prototype, {
return trim_block(self);
});
+ OPT(AST_Arrow, function(self, compressor) {
+ if (!compressor.option("arrows")) return self;
+ if (self.value) {
+ var value = self.value;
+ if (is_undefined(value, compressor)) {
+ self.value = null;
+ } else if (value instanceof AST_UnaryPrefix && value.operator == "void") {
+ self.body.push(make_node(AST_SimpleStatement, value, {
+ body: value.expression
+ }));
+ self.value = null;
+ }
+ } else if (self.body.length == 1) {
+ var stat = self.body[0];
+ if (stat instanceof AST_Return && stat.value) {
+ self.body.pop();
+ self.value = stat.value;
+ }
+ }
+ return self;
+ });
+
OPT(AST_Function, function(self, compressor) {
self.body = tighten_body(self.body, compressor);
if (compressor.option("inline")) for (var i = 0; i < self.body.length; i++) {
@@ -6316,6 +6355,7 @@ merge(Compressor.prototype, {
})) return this;
return make_sequence(this, values.map(convert_spread));
});
+ def(AST_Arrow, return_null);
def(AST_Assign, function(compressor) {
var left = this.left;
if (left instanceof AST_PropAccess) {
@@ -7702,7 +7742,7 @@ merge(Compressor.prototype, {
}
}
var fn = exp instanceof AST_SymbolRef ? exp.fixed_value() : exp;
- var is_func = fn instanceof AST_Defun || fn instanceof AST_Function;
+ var is_func = fn instanceof AST_Arrow || fn instanceof AST_Defun || fn instanceof AST_Function;
var stat = is_func && fn.first_statement();
var can_inline = is_func
&& compressor.option("inline")
@@ -7772,10 +7812,11 @@ merge(Compressor.prototype, {
}
if (compressor.option("side_effects")
&& all(fn.body, is_empty)
+ && (fn !== exp || fn_name_unused(fn, compressor))
+ && !(fn instanceof AST_Arrow && fn.value)
&& all(fn.argnames, function(argname) {
return !(argname instanceof AST_Destructured);
- })
- && (fn !== exp || fn_name_unused(fn, compressor))) {
+ })) {
var args = self.args.map(function(arg) {
return arg instanceof AST_Spread ? make_node(AST_Array, arg, {
elements: [ arg ],
@@ -7814,9 +7855,11 @@ merge(Compressor.prototype, {
function can_flatten_body(stat) {
var len = fn.body.length;
- if (compressor.option("inline") < 3) {
- return len == 1 && return_value(stat);
+ if (len < 2) {
+ stat = return_value(stat);
+ if (stat) return stat;
}
+ if (compressor.option("inline") < 3) return false;
stat = null;
for (var i = 0; i < len; i++) {
var line = fn.body[i];
@@ -9833,7 +9876,7 @@ merge(Compressor.prototype, {
&& expr instanceof AST_SymbolRef
&& is_arguments(def = expr.definition())
&& prop instanceof AST_Number
- && (fn = expr.scope.resolve()) === find_lambda()
+ && (fn = def.scope) === find_lambda()
&& !(assigned && fn.uses_arguments === "d")) {
var index = prop.value;
if (parent instanceof AST_UnaryPrefix && parent.operator == "delete") {
@@ -9936,6 +9979,7 @@ merge(Compressor.prototype, {
while (p = compressor.parent(i++)) {
if (p instanceof AST_Lambda) {
if (p instanceof AST_Accessor) return;
+ if (p instanceof AST_Arrow) continue;
fn_parent = compressor.parent(i);
return p;
}
@@ -9943,13 +9987,14 @@ merge(Compressor.prototype, {
}
});
+ AST_Arrow.DEFMETHOD("contains_this", return_false);
AST_Scope.DEFMETHOD("contains_this", function() {
var result;
var self = this;
self.walk(new TreeWalker(function(node) {
if (result) return true;
if (node instanceof AST_This) return result = true;
- if (node !== self && node instanceof AST_Scope) return true;
+ if (node !== self && node instanceof AST_Scope && !(node instanceof AST_Arrow)) return true;
}));
return result;
});
diff --git a/lib/output.js b/lib/output.js
index a6d0d0da..7bb27d9b 100644
--- a/lib/output.js
+++ b/lib/output.js
@@ -678,7 +678,7 @@ function OutputStream(options) {
// same goes for an object literal, because otherwise it would be
// interpreted as a block of code.
function needs_parens_obj(output) {
- return !output.has_parens() && first_in_statement(output);
+ return !output.has_parens() && first_in_statement(output, true);
}
PARENS(AST_Object, needs_parens_obj);
@@ -691,6 +691,8 @@ function OutputStream(options) {
var p = output.parent();
// [ 1, (2, 3), 4 ] ==> [ 1, 3, 4 ]
return p instanceof AST_Array
+ // () => (foo, bar)
+ || p instanceof AST_Arrow && p.value === this
// await (foo, bar)
|| p instanceof AST_Await
// 1 + (2, 3) + 4 ==> 8
@@ -798,6 +800,9 @@ function OutputStream(options) {
// !(a = false) → true
if (p instanceof AST_Unary) return true;
}
+ PARENS(AST_Arrow, function(output) {
+ return needs_parens_assign_cond(this, output);
+ });
PARENS(AST_Assign, function(output) {
if (needs_parens_assign_cond(this, output)) return true;
// v8 parser bug => workaround
@@ -985,6 +990,25 @@ function OutputStream(options) {
});
/* -----[ functions ]----- */
+ DEFPRINT(AST_Arrow, function(output) {
+ var self = this;
+ if (self.argnames.length == 1 && self.argnames[0] instanceof AST_SymbolFunarg) {
+ self.argnames[0].print(output);
+ } else output.with_parens(function() {
+ self.argnames.forEach(function(arg, i) {
+ if (i) output.comma();
+ arg.print(output);
+ });
+ });
+ output.space();
+ output.print("=>");
+ output.space();
+ if (self.value) {
+ self.value.print(output);
+ } else {
+ print_braced(self, output, true);
+ }
+ });
function print_lambda(self, output) {
if (self.name) {
output.space();
diff --git a/lib/parse.js b/lib/parse.js
index c65b2f42..f760d0de 100644
--- a/lib/parse.js
+++ b/lib/parse.js
@@ -569,6 +569,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) {
}
if (is_digit(code)) return read_num();
if (PUNC_CHARS[ch]) return token("punc", next());
+ if (looking_at("=>")) return token("punc", next() + next());
if (OPERATOR_CHARS[ch]) return read_operator();
if (code == 92 || !NON_IDENTIFIER_CHARS[ch]) return read_word();
break;
@@ -634,7 +635,7 @@ var PRECEDENCE = function(a, ret) {
["*", "/", "%"]
], {});
-var ATOMIC_START_TOKEN = makePredicate("atom num string regexp name");
+var ATOMIC_START_TOKEN = makePredicate("atom num regexp string");
/* -----[ Parser ]----- */
@@ -741,7 +742,7 @@ function parse($TEXT, options) {
function parenthesised() {
expect("(");
- var exp = expression(true);
+ var exp = expression();
expect(")");
return exp;
}
@@ -769,7 +770,7 @@ function parse($TEXT, options) {
switch (S.token.type) {
case "string":
var dir = S.in_directives;
- var body = expression(true);
+ var body = expression();
if (dir) {
if (body instanceof AST_String) {
var value = body.start.raw.slice(1, -1);
@@ -887,7 +888,7 @@ function parse($TEXT, options) {
if (is("punc", ";")) {
next();
} else if (!can_insert_semicolon()) {
- value = expression(true);
+ value = expression();
semicolon();
}
return new AST_Return({
@@ -905,7 +906,7 @@ function parse($TEXT, options) {
next();
if (has_newline_before(S.token))
croak("Illegal newline after 'throw'");
- var value = expression(true);
+ var value = expression();
semicolon();
return new AST_Throw({
value: value
@@ -956,9 +957,7 @@ function parse($TEXT, options) {
// https://github.com/mishoo/UglifyJS/issues/287
label.references.forEach(function(ref) {
if (ref instanceof AST_Continue) {
- ref = ref.label.start;
- croak("Continue label `" + label.name + "` refers to non-IterationStatement.",
- ref.line, ref.col, ref.pos);
+ token_error(ref.label.start, "Continue label `" + label.name + "` must refer to IterationStatement");
}
});
}
@@ -966,7 +965,7 @@ function parse($TEXT, options) {
}
function simple_statement() {
- var body = expression(true);
+ var body = expression();
semicolon();
return new AST_SimpleStatement({ body: body });
}
@@ -980,7 +979,7 @@ function parse($TEXT, options) {
ldef = find_if(function(l) {
return l.name == label.name;
}, S.labels);
- if (!ldef) croak("Undefined label " + label.name);
+ if (!ldef) token_error(label.start, "Undefined label " + label.name);
label.thedef = ldef;
} else if (S.in_loop == 0) croak(type.TYPE + " not inside a loop or switch");
semicolon();
@@ -999,13 +998,14 @@ function parse($TEXT, options) {
? (next(), let_(true))
: is("keyword", "var")
? (next(), var_(true))
- : expression(true, true);
+ : expression(true);
if (is("operator", "in")) {
if (init instanceof AST_Definitions) {
- if (init.definitions.length > 1)
- croak("Only one variable declaration allowed in for..in loop", init.start.line, init.start.col, init.start.pos);
+ if (init.definitions.length > 1) {
+ token_error(init.start, "Only one variable declaration allowed in for..in loop");
+ }
} else if (!(is_assignable(init) || (init = to_destructured(init)) instanceof AST_Destructured)) {
- croak("Invalid left-hand side in for..in loop", init.start.line, init.start.col, init.start.pos);
+ token_error(init.start, "Invalid left-hand side in for..in loop");
}
next();
return for_in(init);
@@ -1016,9 +1016,9 @@ function parse($TEXT, options) {
function regular_for(init) {
expect(";");
- var test = is("punc", ";") ? null : expression(true);
+ var test = is("punc", ";") ? null : expression();
expect(";");
- var step = is("punc", ")") ? null : expression(true);
+ var step = is("punc", ")") ? null : expression();
expect(")");
return new AST_For({
init : init,
@@ -1029,7 +1029,7 @@ function parse($TEXT, options) {
}
function for_in(init) {
- var obj = expression(true);
+ var obj = expression();
expect(")");
return new AST_ForIn({
init : init,
@@ -1038,6 +1038,71 @@ function parse($TEXT, options) {
});
}
+ function to_funarg(node) {
+ if (node instanceof AST_Array) return new AST_DestructuredArray({
+ start: node.start,
+ elements: node.elements.map(function(node) {
+ return node instanceof AST_Hole ? node : to_funarg(node);
+ }),
+ end: node.end,
+ });
+ if (node instanceof AST_Object) return new AST_DestructuredObject({
+ start: node.start,
+ properties: node.properties.map(function(prop) {
+ if (!(prop instanceof AST_ObjectKeyVal)) token_error(prop.start, "Invalid destructuring assignment");
+ return new AST_DestructuredKeyVal({
+ start: prop.start,
+ key: prop.key,
+ value: to_funarg(prop.value),
+ end: prop.end,
+ });
+ }),
+ end: node.end,
+ });
+ if (node instanceof AST_SymbolRef) return new AST_SymbolFunarg(node);
+ token_error(node.start, "Invalid arrow parameter");
+ }
+
+ function arrow(exprs, start) {
+ var was_async = S.in_async;
+ S.in_async = false;
+ var was_funarg = S.in_funarg;
+ S.in_funarg = S.in_function;
+ var argnames = exprs.map(to_funarg);
+ S.in_funarg = was_funarg;
+ expect("=>");
+ var body, value;
+ var loop = S.in_loop;
+ var labels = S.labels;
+ ++S.in_function;
+ S.in_directives = true;
+ S.input.push_directives_stack();
+ S.in_loop = 0;
+ S.labels = [];
+ if (is("punc", "{")) {
+ body = block_();
+ value = null;
+ if (S.input.has_directive("use strict")) {
+ argnames.forEach(strict_verify_symbol);
+ }
+ } else {
+ body = [];
+ value = maybe_assign();
+ }
+ S.input.pop_directives_stack();
+ --S.in_function;
+ S.in_loop = loop;
+ S.labels = labels;
+ S.in_async = was_async;
+ return new AST_Arrow({
+ start: start,
+ argnames: argnames,
+ body: body,
+ value: value,
+ end: prev(),
+ });
+ }
+
var function_ = function(ctor) {
var was_async = S.in_async;
var name;
@@ -1118,7 +1183,7 @@ function parse($TEXT, options) {
cur = [];
branch = new AST_Case({
start : (tmp = S.token, next(), tmp),
- expression : expression(true),
+ expression : expression(),
body : cur
});
a.push(branch);
@@ -1187,7 +1252,7 @@ function parse($TEXT, options) {
var value = null;
if (is("operator", "=")) {
next();
- value = expression(false, no_in);
+ value = maybe_assign(no_in);
} else if (!no_in && (type === AST_SymbolConst || name instanceof AST_Destructured)) {
croak("Missing initializer in declaration");
}
@@ -1251,9 +1316,6 @@ function parse($TEXT, options) {
function as_atom_node() {
var tok = S.token, ret;
switch (tok.type) {
- case "name":
- ret = _make_symbol(AST_SymbolRef, tok);
- break;
case "num":
ret = new AST_Number({ start: tok, end: tok, value: tok.value });
break;
@@ -1295,7 +1357,11 @@ function parse($TEXT, options) {
switch (start.value) {
case "(":
next();
- var ex = expression(true);
+ if (is("punc", ")")) {
+ next();
+ return arrow([], start);
+ }
+ var ex = expression(false, true);
var len = start.comments_before.length;
[].unshift.apply(ex.start.comments_before, start.comments_before);
start.comments_before.length = 0;
@@ -1318,6 +1384,7 @@ function parse($TEXT, options) {
end.comments_after = ex.end.comments_after;
ex.end = end;
if (ex instanceof AST_Call) mark_pure(ex);
+ if (is("punc", "=>")) return arrow(ex instanceof AST_Sequence ? ex.expressions : [ ex ], start);
return subscripts(ex, allow_calls);
case "[":
return subscripts(array_(), allow_calls);
@@ -1340,6 +1407,11 @@ function parse($TEXT, options) {
func.end = prev();
return subscripts(func, allow_calls);
}
+ if (is("name")) {
+ var sym = _make_symbol(AST_SymbolRef, start);
+ next();
+ return is("punc", "=>") ? arrow([ sym ], start) : subscripts(sym, allow_calls);
+ }
if (ATOMIC_START_TOKEN[S.token.type]) {
return subscripts(as_atom_node(), allow_calls);
}
@@ -1347,14 +1419,14 @@ function parse($TEXT, options) {
};
function expr_list(closing, allow_trailing_comma, allow_empty, parser) {
- if (!parser) parser = expression;
+ if (!parser) parser = maybe_assign;
var first = true, a = [];
while (!is("punc", closing)) {
if (first) first = false; else expect(",");
if (allow_trailing_comma && is("punc", closing)) break;
if (allow_empty && is("punc", ",")) {
a.push(new AST_Hole({ start: S.token, end: S.token }));
- } else if (parser === expression && is("operator", "...")) {
+ } else if (parser === maybe_assign && is("operator", "...")) {
a.push(new AST_Spread({
start: S.token,
expression: (next(), parser()),
@@ -1391,7 +1463,7 @@ function parse($TEXT, options) {
next();
a.push(new AST_Spread({
start: start,
- expression: expression(false),
+ expression: maybe_assign(),
end: prev(),
}));
continue;
@@ -1440,7 +1512,7 @@ function parse($TEXT, options) {
a.push(new AST_ObjectKeyVal({
start: start,
key: key,
- value: expression(false),
+ value: maybe_assign(),
end: prev(),
}));
}
@@ -1463,7 +1535,7 @@ function parse($TEXT, options) {
case "punc":
if (tmp.value != "[") unexpected();
next();
- var key = expression(false);
+ var key = maybe_assign();
expect("]");
return key;
default:
@@ -1490,7 +1562,7 @@ function parse($TEXT, options) {
function strict_verify_symbol(sym) {
if (sym.name == "arguments" || sym.name == "eval")
- croak("Unexpected " + sym.name + " in strict mode", sym.start.line, sym.start.col, sym.start.pos);
+ token_error(sym.start, "Unexpected " + sym.name + " in strict mode");
}
function as_symbol(type, noerror) {
@@ -1580,7 +1652,7 @@ function parse($TEXT, options) {
}
if (is("punc", "[")) {
next();
- var prop = expression(true);
+ var prop = expression();
expect("]");
return subscripts(new AST_Sub({
start : start,
@@ -1629,11 +1701,11 @@ function parse($TEXT, options) {
case "++":
case "--":
if (!is_assignable(expr))
- croak("Invalid use of " + op + " operator", token.line, token.col, token.pos);
+ token_error(token, "Invalid use of " + op + " operator");
break;
case "delete":
if (expr instanceof AST_SymbolRef && S.input.has_directive("use strict"))
- croak("Calling delete on expression not allowed in strict mode", expr.start.line, expr.start.col, expr.start.pos);
+ token_error(expr.start, "Calling delete on expression not allowed in strict mode");
break;
}
return new ctor({ operator: op, expression: expr });
@@ -1679,13 +1751,13 @@ function parse($TEXT, options) {
var expr = expr_ops(no_in);
if (is("operator", "?")) {
next();
- var yes = expression(false);
+ var yes = maybe_assign();
expect(":");
return new AST_Conditional({
start : start,
condition : expr,
consequent : yes,
- alternative : expression(false, no_in),
+ alternative : maybe_assign(no_in),
end : prev()
});
}
@@ -1728,7 +1800,7 @@ function parse($TEXT, options) {
});
}
- var maybe_assign = function(no_in) {
+ function maybe_assign(no_in) {
var start = S.token;
var left = maybe_conditional(no_in), val = S.token.value;
if (is("operator") && ASSIGNMENT[val]) {
@@ -1745,23 +1817,23 @@ function parse($TEXT, options) {
croak("Invalid assignment");
}
return left;
- };
+ }
- var expression = function(commas, no_in) {
+ function expression(no_in, maybe_arrow) {
var start = S.token;
var exprs = [];
while (true) {
exprs.push(maybe_assign(no_in));
- if (!commas || !is("punc", ",")) break;
+ if (!is("punc", ",")) break;
next();
- commas = true;
+ if (maybe_arrow && is("punc", ")") && is_token(peek(), "punc", "=>")) break;
}
return exprs.length == 1 ? exprs[0] : new AST_Sequence({
start : start,
expressions : exprs,
- end : peek()
+ end : prev()
});
- };
+ }
function in_loop(cont) {
++S.in_loop;
@@ -1772,7 +1844,7 @@ function parse($TEXT, options) {
if (options.expression) {
handle_regexp();
- return expression(true);
+ return expression();
}
return function() {
diff --git a/lib/scope.js b/lib/scope.js
index ff0cb58c..a285f919 100644
--- a/lib/scope.js
+++ b/lib/scope.js
@@ -212,7 +212,11 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
argname.walk(tw);
});
in_arg.pop();
- walk_body(node, tw);
+ if (node instanceof AST_Arrow && node.value) {
+ node.value.walk(tw);
+ } else {
+ walk_body(node, tw);
+ }
return true;
}
if (node instanceof AST_LoopControl) {
@@ -249,7 +253,9 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options) {
}
if (!sym) {
sym = self.def_global(node);
- } else if (name == "arguments" && sym.scope instanceof AST_Lambda) {
+ } else if (name == "arguments"
+ && sym.orig[0] instanceof AST_SymbolFunarg
+ && !(sym.scope instanceof AST_Arrow)) {
if (!(tw.parent() instanceof AST_PropAccess)) {
sym.scope.uses_arguments = "d";
} else if (!sym.scope.uses_arguments) {
@@ -360,6 +366,9 @@ AST_BlockScope.DEFMETHOD("init_vars", function(parent_scope) {
AST_Scope.DEFMETHOD("init_vars", function(parent_scope) {
init_scope_vars(this, parent_scope);
});
+AST_Arrow.DEFMETHOD("init_vars", function(parent_scope) {
+ init_scope_vars(this, parent_scope);
+});
AST_Lambda.DEFMETHOD("init_vars", function(parent_scope) {
init_scope_vars(this, parent_scope);
this.uses_arguments = false;
diff --git a/lib/transform.js b/lib/transform.js
index 398cf73d..5372cc59 100644
--- a/lib/transform.js
+++ b/lib/transform.js
@@ -131,6 +131,14 @@ TreeTransformer.prototype = new TreeWalker;
self.argnames = do_list(self.argnames, tw);
self.body = do_list(self.body, tw);
});
+ DEF(AST_Arrow, function(self, tw) {
+ self.argnames = do_list(self.argnames, tw);
+ if (self.value) {
+ self.value = self.value.transform(tw);
+ } else {
+ self.body = do_list(self.body, tw);
+ }
+ });
DEF(AST_Call, function(self, tw) {
self.expression = self.expression.transform(tw);
self.args = do_list(self.args, tw);
diff --git a/lib/utils.js b/lib/utils.js
index 2a0df8c5..286266a2 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -238,13 +238,15 @@ function HOP(obj, prop) {
// return true if the node at the top of the stack (that means the
// innermost node in the current output) is lexically the first in
// a statement.
-function first_in_statement(stack) {
+function first_in_statement(stack, arrow) {
var node = stack.parent(-1);
for (var i = 0, p; p = stack.parent(i++); node = p) {
- if (p.TYPE == "Call") {
- if (p.expression === node) continue;
+ if (p instanceof AST_Arrow) {
+ return arrow && p.value === node;
} else if (p instanceof AST_Binary) {
if (p.left === node) continue;
+ } else if (p.TYPE == "Call") {
+ if (p.expression === node) continue;
} else if (p instanceof AST_Conditional) {
if (p.condition === node) continue;
} else if (p instanceof AST_PropAccess) {