From 5f7cb6939c1d9006f208ffe804fef64aade0ee23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Santos?= Date: Tue, 27 Oct 2015 00:40:46 +0000 Subject: [PATCH 1/8] Starting ES6 classes --- lib/ast.js | 12 +++++ lib/output.js | 31 ++++++++++++ lib/parse.js | 106 ++++++++++++++++++++++++++++----------- test/compress/harmony.js | 19 +++++++ 4 files changed, 140 insertions(+), 28 deletions(-) diff --git a/lib/ast.js b/lib/ast.js index 8dadf21c..a80ea76c 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -955,6 +955,14 @@ var AST_ObjectGetter = DEFNODE("ObjectGetter", null, { $documentation: "An object getter property", }, AST_ObjectProperty); +var AST_Class = DEFNODE("Class", "name extends", { + $propdoc: { + name: "[AST_SymbolClassName?] optional class name.", + extends: "[AST_Node]? optional parent class", + }, + $documentation: "An ES6 class", +}, AST_Object); + var AST_Symbol = DEFNODE("Symbol", "scope name thedef", { $propdoc: { name: "[string] name of this symbol", @@ -999,6 +1007,10 @@ var AST_SymbolLambda = DEFNODE("SymbolLambda", null, { $documentation: "Symbol naming a function expression", }, AST_SymbolDeclaration); +var AST_SymbolClassName = DEFNODE("SymbolClassName", null, { + $documentation: "Symbol naming a class's name. Lexically scoped to the class." +}, AST_SymbolDeclaration); + var AST_SymbolCatch = DEFNODE("SymbolCatch", null, { $documentation: "Symbol naming the exception in catch", }, AST_SymbolDeclaration); diff --git a/lib/output.js b/lib/output.js index bdab47c6..e597184f 100644 --- a/lib/output.js +++ b/lib/output.js @@ -502,6 +502,12 @@ function OutputStream(options) { return first_in_statement(output); }); + // Not a class, though. It can be alone in a statement although + // it extends from AST_Object. + PARENS(AST_Class, function() { + return false; + }); + PARENS([ AST_Unary, AST_Undefined ], function(output){ var p = output.parent(); return p instanceof AST_PropAccess && p.expression === this; @@ -1188,6 +1194,31 @@ function OutputStream(options) { }); else output.print("{}"); }); + DEFPRINT(AST_Class, function(self, output){ + output.print("class"); + output.space(); + if (self.name) { + self.name.print(output); + output.space(); + } + if (self.extends) { + output.print("extends"); + output.space(); + self.extends.print(output); + output.space(); + } + if (self.properties.length > 0) output.with_block(function(){ + self.properties.forEach(function(prop, i){ + if (i) { + output.newline(); + } + output.indent(); + prop.print(output); + }); + output.newline(); + }); + else output.print("{}"); + }); DEFPRINT(AST_ObjectKeyVal, function(self, output){ var key = self.key; var quote = self.quote; diff --git a/lib/parse.js b/lib/parse.js index 4ed32863..9af773d5 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -44,9 +44,9 @@ "use strict"; -var KEYWORDS = 'break case catch const continue debugger default delete do else finally for function if in instanceof new return switch throw try typeof var let void while with'; +var KEYWORDS = 'break case catch class const continue debugger default delete do else extends finally for function if in instanceof new return switch throw try typeof var let void while with'; var KEYWORDS_ATOM = 'false null true'; -var RESERVED_WORDS = 'abstract boolean byte char class double enum export extends final float goto implements import int interface long native package private protected public short static super synchronized this throws transient volatile yield' +var RESERVED_WORDS = 'abstract boolean byte char double enum export final float goto implements import int interface long native package private protected public short static super synchronized this throws transient volatile yield' + " " + KEYWORDS_ATOM + " " + KEYWORDS; var KEYWORDS_BEFORE_EXPRESSION = 'return new delete throw else case'; @@ -855,6 +855,9 @@ function parse($TEXT, options) { case "for": return for_(); + case "class": + return class_(); + case "function": return function_(AST_Defun); @@ -1374,6 +1377,13 @@ function parse($TEXT, options) { func.end = prev(); return subscripts(func, allow_calls); } + if (is("keyword", "class")) { + next(); + var cls = class_(); + cls.start = start; + cls.end = prev(); + return subscripts(cls, allow_calls); + } if (ATOMIC_START_TOKEN[S.token.type]) { return subscripts(as_atom_node(), allow_calls); } @@ -1446,32 +1456,9 @@ function parse($TEXT, options) { var type = start.type; var name = as_property_name(); if (type != "string" && type != "num" && !is("punc", ":")) { - if (is("punc", "(")) { - a.push(new AST_ConciseMethod({ - start : start, - name : new AST_SymbolMethod({ name: name }), - argnames : params_or_seq_().as_params(croak), - body : _function_body(true), - end : prev() - })) - continue; - } - if (name == "get") { - a.push(new AST_ObjectGetter({ - start : start, - key : as_atom_node(), - value : function_(AST_Accessor), - end : prev() - })); - continue; - } - if (name == "set") { - a.push(new AST_ObjectSetter({ - start : start, - key : as_atom_node(), - value : function_(AST_Accessor), - end : prev() - })); + var concise = concise_method_or_getset(name, start); + if (concise) { + a.push(concise); continue; } } @@ -1510,6 +1497,69 @@ function parse($TEXT, options) { return new AST_Object({ properties: a }) }); + function class_() { + var start, method, class_name, name, extends_, a = []; + + if (S.token.type == "name" && S.token.value != "extends") { + class_name = as_symbol(AST_SymbolClassName) + } + + if (S.token.value == "extends") { + next(); + extends_ = expression(true); + } + + expect("{"); + + if (is("punc", ";")) { next(); } // Leading semicolons are okay in class bodies. + while (!is("punc", "}")) { + start = S.token; + name = as_property_name(); + method = concise_method_or_getset(name, start); + if (!method) { croak(); } + a.push(method); + if (is("punc", ";")) { next(); } + } + + next(); + + return new AST_Class({ + start: start, + name: class_name, + extends: extends_, + properties: a, + end: prev(), + }); + } + + function concise_method_or_getset(name, start) { + if (is("punc", "(")) { + return new AST_ConciseMethod({ + start : start, + name : new AST_SymbolMethod({ name: name }), + argnames : params_or_seq_().as_params(croak), + body : _function_body(true), + end : prev() + }); + } + if (name == "get") { + return new AST_ObjectGetter({ + start : start, + key : as_atom_node(), + value : function_(AST_Accessor), + end : prev() + }); + } + if (name == "set") { + return new AST_ObjectSetter({ + start : start, + key : as_atom_node(), + value : function_(AST_Accessor), + end : prev() + }); + } + } + function as_property_name() { var tmp = S.token; next(); diff --git a/test/compress/harmony.js b/test/compress/harmony.js index 731976f3..95e9bd54 100644 --- a/test/compress/harmony.js +++ b/test/compress/harmony.js @@ -181,6 +181,25 @@ concise_methods_and_keyword_names: { } } +classes: { + input: { + class SomeClass { + constructor() { + }; + foo() {}; + }; + class NoSemi { + constructor(...args) { + } + foo() {} + }; + class ChildClass extends SomeClass {}; + var asExpression = class AsExpression {}; + var nameless = class {}; + } + expect_exact: "class SomeClass{constructor(){}foo(){}}class NoSemi{constructor(...args){}foo(){}}class ChildClass extends SomeClass{}var asExpression=class AsExpression{};var nameless=class{};" +} + number_literals: { input: { 0b1001; From 9ffed2bea6ff4cf2e487781adaee2ee7944e2d07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Santos?= Date: Tue, 27 Oct 2015 00:51:47 +0000 Subject: [PATCH 2/8] static properties --- lib/ast.js | 15 ++++++++++++--- lib/output.js | 16 +++++++++++++++- lib/parse.js | 9 +++++++++ test/compress/harmony.js | 14 ++++++++++++++ 4 files changed, 50 insertions(+), 4 deletions(-) diff --git a/lib/ast.js b/lib/ast.js index a80ea76c..9e2f3cee 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -473,7 +473,10 @@ var AST_Arrow = DEFNODE("Arrow", null, { $documentation: "An ES6 Arrow function ((a) => b)" }, AST_Lambda); -var AST_ConciseMethod = DEFNODE("ConciseMethod", null, { +var AST_ConciseMethod = DEFNODE("ConciseMethod", "static", { + $propdoc: { + static: "[boolean] whether this method is static (classes only)", + }, $documentation: "An ES6 concise method inside an object or class" }, AST_Lambda); @@ -947,11 +950,17 @@ var AST_ObjectSymbol = DEFNODE("ObjectSymbol", "symbol", { } }, AST_ObjectProperty); -var AST_ObjectSetter = DEFNODE("ObjectSetter", null, { +var AST_ObjectSetter = DEFNODE("ObjectSetter", "static", { + $propdoc: { + static: "[boolean] whether this is a static setter (classes only)" + }, $documentation: "An object setter property", }, AST_ObjectProperty); -var AST_ObjectGetter = DEFNODE("ObjectGetter", null, { +var AST_ObjectGetter = DEFNODE("ObjectGetter", "static", { + $propdoc: { + static: "[boolean] whether this is a static getter (classes only)" + }, $documentation: "An object getter property", }, AST_ObjectProperty); diff --git a/lib/output.js b/lib/output.js index e597184f..e744788f 100644 --- a/lib/output.js +++ b/lib/output.js @@ -775,9 +775,11 @@ function OutputStream(options) { var self = this; if (!nokeyword) { output.print("function"); + if (self.name) { + output.space(); + } } if (self.name) { - output.space(); self.name.print(output); } output.with_parens(function(){ @@ -839,6 +841,10 @@ function OutputStream(options) { if (needs_parens) { output.print(")") } }); DEFPRINT(AST_ConciseMethod, function(self, output){ + if (self.static) { + output.print("static"); + output.space(); + } self._do_print(output, true /* do not print "function" */); }); @@ -1238,12 +1244,20 @@ function OutputStream(options) { self.value.print(output); }); DEFPRINT(AST_ObjectSetter, function(self, output){ + if (self.static) { + output.print("static"); + output.space(); + } output.print("set"); output.space(); self.key.print(output); self.value._do_print(output, true); }); DEFPRINT(AST_ObjectGetter, function(self, output){ + if (self.static) { + output.print("static"); + output.space(); + } output.print("get"); output.space(); self.key.print(output); diff --git a/lib/parse.js b/lib/parse.js index 9af773d5..5aee6017 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -1533,9 +1533,16 @@ function parse($TEXT, options) { } function concise_method_or_getset(name, start) { + var is_static = false; + if (name === "static" && !is("punc", "(")) { + is_static = true; + name = S.token.value; + next(); + } if (is("punc", "(")) { return new AST_ConciseMethod({ start : start, + static : is_static, name : new AST_SymbolMethod({ name: name }), argnames : params_or_seq_().as_params(croak), body : _function_body(true), @@ -1545,6 +1552,7 @@ function parse($TEXT, options) { if (name == "get") { return new AST_ObjectGetter({ start : start, + static: is_static, key : as_atom_node(), value : function_(AST_Accessor), end : prev() @@ -1553,6 +1561,7 @@ function parse($TEXT, options) { if (name == "set") { return new AST_ObjectSetter({ start : start, + static: is_static, key : as_atom_node(), value : function_(AST_Accessor), end : prev() diff --git a/test/compress/harmony.js b/test/compress/harmony.js index 95e9bd54..e1b3078a 100644 --- a/test/compress/harmony.js +++ b/test/compress/harmony.js @@ -200,6 +200,20 @@ classes: { expect_exact: "class SomeClass{constructor(){}foo(){}}class NoSemi{constructor(...args){}foo(){}}class ChildClass extends SomeClass{}var asExpression=class AsExpression{};var nameless=class{};" } +class_statics: { + input: { + x = class { + static staticMethod() {} + static get foo() {} + static set bar() {} + static() { /* "static" can be a method name! */ } + get() { /* "get" can be a method name! */ } + set() { /* "set" can be a method name! */ } + } + } + expect_exact: "x=class{static staticMethod(){}static get foo(){}static set bar(){}static(){}get(){}set(){}};" +} + number_literals: { input: { 0b1001; From 364d20f8fb8291525057451a491fe2924bd1f072 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Santos?= Date: Fri, 20 Nov 2015 18:09:27 +0000 Subject: [PATCH 3/8] Add mangle = { ...mangleopts } option to tests. --- test/run-tests.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/run-tests.js b/test/run-tests.js index 1d0de8ca..6d7f7244 100755 --- a/test/run-tests.js +++ b/test/run-tests.js @@ -104,6 +104,9 @@ function run_compress_tests() { } var output = input.transform(cmp); output.figure_out_scope(); + if (test.mangle) { + output.mangle_names(test.mangle); + } output = make_code(output, false); if (expect != output) { log("!!! failed\n---INPUT---\n{input}\n---OUTPUT---\n{output}\n---EXPECTED---\n{expected}\n\n", { From bb6b3a773af0e2682689c738744226cf57d2340c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Santos?= Date: Fri, 20 Nov 2015 19:34:10 +0000 Subject: [PATCH 4/8] Make AST_Class inherit AST_Scope instead of AST_Object This is one of those days I'd love to use multiple inheritance. An AST_Class has lots of common with AST_Object, but unfortunately `instanceof AST_Scope` is used very, very much, and a class has its name inside its own special pocket scope. This compels me to make AST_Class inherit Scope instead. It looks like, although there is much in common with AST_Object, `instanceof AST_Object` seldom are made, perhaps because it is less often necessary to traverse an object than a scope. --- lib/ast.js | 18 ++++++++++++++++-- lib/output.js | 6 ------ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/lib/ast.js b/lib/ast.js index 9e2f3cee..b78a85b2 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -964,13 +964,27 @@ var AST_ObjectGetter = DEFNODE("ObjectGetter", "static", { $documentation: "An object getter property", }, AST_ObjectProperty); -var AST_Class = DEFNODE("Class", "name extends", { +var AST_Class = DEFNODE("Class", "name extends properties", { $propdoc: { name: "[AST_SymbolClassName?] optional class name.", extends: "[AST_Node]? optional parent class", + properties: "[AST_ObjectProperty*] array of properties" }, $documentation: "An ES6 class", -}, AST_Object); + _walk: function(visitor) { + return visitor._visit(this, function(){ + if (this.name) { + this.name._walk(visitor); + } + if (this.extends) { + this.extends._walk(visitor); + } + this.properties.forEach(function(prop){ + prop._walk(visitor); + }); + }); + }, +}, AST_Scope); var AST_Symbol = DEFNODE("Symbol", "scope name thedef", { $propdoc: { diff --git a/lib/output.js b/lib/output.js index e744788f..d1ebdeee 100644 --- a/lib/output.js +++ b/lib/output.js @@ -502,12 +502,6 @@ function OutputStream(options) { return first_in_statement(output); }); - // Not a class, though. It can be alone in a statement although - // it extends from AST_Object. - PARENS(AST_Class, function() { - return false; - }); - PARENS([ AST_Unary, AST_Undefined ], function(output){ var p = output.parent(); return p instanceof AST_PropAccess && p.expression === this; From 69da8e53e022911b1f1b4b3785e694ce60c11c5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Santos?= Date: Sat, 21 Nov 2015 09:17:32 +0000 Subject: [PATCH 5/8] Separate class expressions from class declarations and their symbols like defuns --- lib/ast.js | 16 ++++++++++++++-- lib/parse.js | 14 +++++++++----- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/lib/ast.js b/lib/ast.js index b78a85b2..faae55fd 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -966,7 +966,7 @@ var AST_ObjectGetter = DEFNODE("ObjectGetter", "static", { var AST_Class = DEFNODE("Class", "name extends properties", { $propdoc: { - name: "[AST_SymbolClassName?] optional class name.", + name: "[AST_SymbolClass|AST_SymbolDefClass?] optional class name.", extends: "[AST_Node]? optional parent class", properties: "[AST_ObjectProperty*] array of properties" }, @@ -986,6 +986,14 @@ var AST_Class = DEFNODE("Class", "name extends properties", { }, }, AST_Scope); +var AST_DefClass = DEFNODE("DefClass", null, { + $documentation: "A class definition", +}, AST_Class); + +var AST_ClassExpression = DEFNODE("ClassExpression", null, { + $documentation: "A class expression." +}, AST_Class); + var AST_Symbol = DEFNODE("Symbol", "scope name thedef", { $propdoc: { name: "[string] name of this symbol", @@ -1030,7 +1038,11 @@ var AST_SymbolLambda = DEFNODE("SymbolLambda", null, { $documentation: "Symbol naming a function expression", }, AST_SymbolDeclaration); -var AST_SymbolClassName = DEFNODE("SymbolClassName", null, { +var AST_SymbolDefClass = DEFNODE("SymbolDefClass", null, { + $documentation: "Symbol naming a class's name in a class declaration. Lexically scoped to its containing scope, and accessible within the class." +}, AST_SymbolDeclaration); + +var AST_SymbolClass = DEFNODE("SymbolClass", null, { $documentation: "Symbol naming a class's name. Lexically scoped to the class." }, AST_SymbolDeclaration); diff --git a/lib/parse.js b/lib/parse.js index 5aee6017..9341ad39 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -856,7 +856,7 @@ function parse($TEXT, options) { return for_(); case "class": - return class_(); + return class_(AST_DefClass); case "function": return function_(AST_Defun); @@ -1379,7 +1379,7 @@ function parse($TEXT, options) { } if (is("keyword", "class")) { next(); - var cls = class_(); + var cls = class_(AST_ClassExpression); cls.start = start; cls.end = prev(); return subscripts(cls, allow_calls); @@ -1497,11 +1497,15 @@ function parse($TEXT, options) { return new AST_Object({ properties: a }) }); - function class_() { + function class_(KindOfClass) { var start, method, class_name, name, extends_, a = []; if (S.token.type == "name" && S.token.value != "extends") { - class_name = as_symbol(AST_SymbolClassName) + class_name = as_symbol(KindOfClass === AST_DefClass ? AST_SymbolDefClass : AST_SymbolClass); + } + + if (KindOfClass === AST_DefClass && !class_name) { + croak(); } if (S.token.value == "extends") { @@ -1523,7 +1527,7 @@ function parse($TEXT, options) { next(); - return new AST_Class({ + return new KindOfClass({ start: start, name: class_name, extends: extends_, From 425613b0d2ab8dfa5c3f4cff5d9e1c5a26bbbca6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Santos?= Date: Sat, 21 Nov 2015 12:20:20 +0000 Subject: [PATCH 6/8] mangle class names --- lib/scope.js | 16 ++++++++++++++++ test/compress/harmony.js | 19 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/lib/scope.js b/lib/scope.js index caaba157..4abebddb 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -155,6 +155,14 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ // later. (node.scope = defun.parent_scope).def_function(node); } + else if (node instanceof AST_SymbolClass) { + defun.def_variable(node); + } + else if (node instanceof AST_SymbolDefClass) { + // This deals with the name of the class being available + // inside the class. + (node.scope = defun.parent_scope).def_function(node); + } else if (node instanceof AST_SymbolVar || node instanceof AST_SymbolConst) { var def = defun.def_variable(node); @@ -171,6 +179,7 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ // pass 2: find back references and eval var func = null; + var cls = null; var globals = self.globals = new Dictionary(); var tw = new TreeWalker(function(node, descend){ if (node instanceof AST_Lambda) { @@ -180,6 +189,13 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ func = prev_func; return true; } + if (node instanceof AST_Class) { + var prev_cls = cls; + cls = node; + descend(); + cls = prev_cls; + return true; + } if (node instanceof AST_SymbolRef) { var name = node.name; var sym = node.scope.find_variable(name); diff --git a/test/compress/harmony.js b/test/compress/harmony.js index e1b3078a..c959b5d1 100644 --- a/test/compress/harmony.js +++ b/test/compress/harmony.js @@ -214,6 +214,25 @@ class_statics: { expect_exact: "x=class{static staticMethod(){}static get foo(){}static set bar(){}static(){}get(){}set(){}};" } +class_name_can_be_mangled: { + mangle = { }; + input: { + function x() { + class Foo { + } + var class1 = Foo + var class2 = class Bar {} + } + } + expect: { + function x() { + class a { } + var b = a + var c = class a {} + } + } +} + number_literals: { input: { 0b1001; From e076abdbf24bc6acdfe34a11b607613016692e67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Santos?= Date: Sat, 21 Nov 2015 13:59:18 +0000 Subject: [PATCH 7/8] Mangle class names correctly --- lib/scope.js | 9 +++++++-- test/compress/harmony.js | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/lib/scope.js b/lib/scope.js index 4abebddb..3ae51dd4 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -66,7 +66,11 @@ SymbolDef.prototype = { || (!options.eval && (this.scope.uses_eval || this.scope.uses_with)) || (options.keep_fnames && (this.orig[0] instanceof AST_SymbolLambda - || this.orig[0] instanceof AST_SymbolDefun)); + || this.orig[0] instanceof AST_SymbolDefun)) + || this.orig[0] instanceof AST_SymbolMethod + || (options.keep_classnames + && (this.orig[0] instanceof AST_SymbolClass + || this.orig[0] instanceof AST_SymbolDefClass)); }, mangle: function(options) { var cache = options.cache && options.cache.props; @@ -379,7 +383,8 @@ AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options){ sort : false, toplevel : false, screw_ie8 : false, - keep_fnames : false + keep_fnames : false, + keep_classnames : false }); }); diff --git a/test/compress/harmony.js b/test/compress/harmony.js index c959b5d1..1d18301d 100644 --- a/test/compress/harmony.js +++ b/test/compress/harmony.js @@ -233,6 +233,24 @@ class_name_can_be_mangled: { } } +class_name_can_be_preserved: { + mangle = { + keep_classnames: true + } + input: { + function x() { + (class Baz { }); + class Foo {}; + } + } + expect: { + function x() { + (class Baz { }); + class Foo {}; + } + } +} + number_literals: { input: { 0b1001; From a800356ad089c81a4bc25a90d58bc0071ebe7a01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?F=C3=A1bio=20Santos?= Date: Sat, 21 Nov 2015 14:48:23 +0000 Subject: [PATCH 8/8] Implement new.target --- lib/ast.js | 4 ++++ lib/output.js | 3 +++ lib/parse.js | 8 ++++++++ test/compress/harmony.js | 8 ++++++++ 4 files changed, 23 insertions(+) diff --git a/lib/ast.js b/lib/ast.js index faae55fd..c315c4a1 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -1003,6 +1003,10 @@ var AST_Symbol = DEFNODE("Symbol", "scope name thedef", { $documentation: "Base class for all symbols", }); +var AST_NewTarget = DEFNODE("NewTarget", null, { + $documentation: "A reference to new.target" +}); + var AST_SymbolAccessor = DEFNODE("SymbolAccessor", null, { $documentation: "The name of a property accessor (setter/getter function)" }, AST_Symbol); diff --git a/lib/output.js b/lib/output.js index d1ebdeee..74d88d25 100644 --- a/lib/output.js +++ b/lib/output.js @@ -1219,6 +1219,9 @@ function OutputStream(options) { }); else output.print("{}"); }); + DEFPRINT(AST_NewTarget, function(self, output) { + output.print("new.target"); + }); DEFPRINT(AST_ObjectKeyVal, function(self, output){ var key = self.key; var quote = self.quote; diff --git a/lib/parse.js b/lib/parse.js index 9341ad39..8f03435f 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -1292,6 +1292,14 @@ function parse($TEXT, options) { var new_ = function(allow_calls) { var start = S.token; expect_token("operator", "new"); + if (is("punc", ".")) { + next(); + expect_token("name"); + return subscripts(new AST_NewTarget({ + start : start, + end : prev() + }), allow_calls); + } var newexp = expr_atom(false), args; if (is("punc", "(")) { next(); diff --git a/test/compress/harmony.js b/test/compress/harmony.js index 1d18301d..e53f9458 100644 --- a/test/compress/harmony.js +++ b/test/compress/harmony.js @@ -251,6 +251,14 @@ class_name_can_be_preserved: { } } +new_target: { + input: { + new.target; + new.target.name; + } + expect_exact: "new.target;new.target.name;" +} + number_literals: { input: { 0b1001;