diff --git a/README.md b/README.md index 628bcdec..58583432 100644 --- a/README.md +++ b/README.md @@ -553,6 +553,9 @@ can pass additional arguments that control the code output: - `3` -- always use the original quotes - `keep_quoted_props` (default `false`) -- when turned on, prevents stripping quotes from property names in object literals. +- `ecma` (default `5`) -- set output printing mode. This will only change the + output in direct control of the beautifier. Non-compatible features in the + abstract syntax tree will still be outputted as is. ### Keeping copyright notices or other comments diff --git a/lib/ast.js b/lib/ast.js index f3df78fe..64352ae0 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -146,7 +146,7 @@ var AST_SimpleStatement = DEFNODE("SimpleStatement", "body", { function walk_body(node, visitor) { var body = node.body; - if (body instanceof AST_Statement) { + if (body instanceof AST_Node) { body._walk(visitor); } else for (var i = 0, len = body.length; i < len; i++) { @@ -266,6 +266,10 @@ var AST_ForIn = DEFNODE("ForIn", "init name object", { } }, AST_IterationStatement); +var AST_ForOf = DEFNODE("ForOf", null, { + $documentation: "A `for ... of` statement", +}, AST_ForIn); + var AST_With = DEFNODE("With", "expression", { $documentation: "A `with` statement", $propdoc: { @@ -293,6 +297,13 @@ var AST_Scope = DEFNODE("Scope", "directives variables functions uses_with uses_ enclosed: "[SymbolDef*/S] a list of all symbol definitions that are accessed from this scope or any subscopes", cname: "[integer/S] current index for mangling variables (used internally by the mangler)", }, + get_defun_scope: function () { + var self = this; + while (self.is_block_scope() && self.parent_scope) { + self = self.parent_scope; + } + return self; + } }, AST_Block); var AST_Toplevel = DEFNODE("Toplevel", "globals", { @@ -362,13 +373,107 @@ var AST_Toplevel = DEFNODE("Toplevel", "globals", { } }, AST_Scope); -var AST_Lambda = DEFNODE("Lambda", "name argnames uses_arguments", { +var AST_Expansion = DEFNODE("Expansion", "expression", { + $documentation: "An expandible argument, such as ...rest, a splat, such as [1,2,...all], or an expansion in a variable declaration, such as var [first, ...rest] = list", + $propdoc: { + expression: "AST_Symbol the thing to be expanded" + }, + _walk: function(visitor) { + var self = this; + return visitor._visit(this, function(){ + self.expression.walk(visitor); + }); + } +}); + +var AST_ArrowParametersOrSeq = DEFNODE("ArrowParametersOrSeq", "expressions", { + $documentation: "A set of arrow function parameters or a sequence expression. This is used because when the parser sees a \"(\" it could be the start of a seq, or the start of a parameter list of an arrow function.", + $propdoc: { + expressions: "[AST_Expression|AST_Destructuring|AST_Expansion*] array of expressions or argument names or destructurings." + }, + as_params: function (croak) { + // We don't want anything which doesn't belong in a destructuring + var root = this; + return this.expressions.map(function to_fun_args(ex, _, __, default_seen_above) { + var insert_default = function(ex, default_value) { + if (default_value) { + return new AST_DefaultAssign({ + start: ex.start, + left: ex, + operator: "=", + right: default_value, + end: default_value.end + }); + } + return ex; + } + if (ex instanceof AST_Object) { + return insert_default(new AST_Destructuring({ + start: ex.start, + end: ex.end, + is_array: false, + names: ex.properties.map(to_fun_args) + }), default_seen_above); + } else if (ex instanceof AST_ObjectKeyVal) { + if (ex.key instanceof AST_SymbolRef) { + ex.key = to_fun_args(ex.key, 0, [ex.key]); + } + ex.value = to_fun_args(ex.value, 0, [ex.key]); + return insert_default(ex, default_seen_above); + } else if (ex instanceof AST_Hole) { + return ex; + } else if (ex instanceof AST_Destructuring) { + if (ex.names.length == 0) + croak("Invalid destructuring function parameter", ex.start.line, ex.start.col); + ex.names = ex.names.map(to_fun_args); + return insert_default(ex, default_seen_above); + } else if (ex instanceof AST_SymbolRef) { + return insert_default(new AST_SymbolFunarg({ + name: ex.name, + start: ex.start, + end: ex.end + }), default_seen_above); + } else if (ex instanceof AST_Expansion) { + ex.expression = to_fun_args(ex.expression); + return insert_default(ex, default_seen_above); + } else if (ex instanceof AST_Array) { + return insert_default(new AST_Destructuring({ + start: ex.start, + end: ex.end, + is_array: true, + names: ex.elements.map(to_fun_args) + }), default_seen_above); + } else if (ex instanceof AST_Assign) { + return insert_default(to_fun_args(ex.left, undefined, undefined, ex.right), default_seen_above); + } else { + croak("Invalid function parameter", ex.start.line, ex.start.col); + } + }); + }, + as_expr: function (croak) { + return AST_Seq.from_array(this.expressions); + } +}); + +var AST_Lambda = DEFNODE("Lambda", "name argnames uses_arguments is_generator", { $documentation: "Base class for functions", $propdoc: { + is_generator: "[boolean] is generatorFn or not", name: "[AST_SymbolDeclaration?] the name of this function", - argnames: "[AST_SymbolFunarg*] array of function arguments", + argnames: "[AST_SymbolFunarg|AST_Destructuring|AST_Expansion|AST_DefaultAssign*] array of function arguments, destructurings, or expanding arguments", uses_arguments: "[boolean/S] tells whether this function accesses the arguments array" }, + args_as_names: function () { + var out = []; + for (var i = 0; i < this.argnames.length; i++) { + if (this.argnames[i] instanceof AST_Destructuring) { + out = out.concat(this.argnames[i].all_symbols()); + } else { + out.push(this.argnames[i]); + } + } + return out; + }, _walk: function(visitor) { return visitor._visit(this, function(){ if (this.name) this.name._walk(visitor); @@ -389,10 +494,78 @@ var AST_Function = DEFNODE("Function", null, { $documentation: "A function expression" }, AST_Lambda); +var AST_Arrow = DEFNODE("Arrow", null, { + $documentation: "An ES6 Arrow function ((a) => b)" +}, AST_Lambda); + var AST_Defun = DEFNODE("Defun", null, { $documentation: "A function definition" }, AST_Lambda); +/* -----[ DESTRUCTURING ]----- */ +var AST_Destructuring = DEFNODE("Destructuring", "names is_array", { + $documentation: "A destructuring of several names. Used in destructuring assignment and with destructuring function argument names", + $propdoc: { + "names": "[AST_Node*] Array of properties or elements", + "is_array": "[Boolean] Whether the destructuring represents an object or array" + }, + _walk: function(visitor) { + return visitor._visit(this, function(){ + this.names.forEach(function(name){ + name._walk(visitor); + }); + }); + }, + all_symbols: function() { + var out = []; + this.walk(new TreeWalker(function (node) { + if (node instanceof AST_Symbol) { + out.push(node); + } + if (node instanceof AST_Expansion) { + out.push(node.expression); + } + })); + return out; + } +}); + +var AST_PrefixedTemplateString = DEFNODE("PrefixedTemplateString", "template_string prefix", { + $documentation: "A templatestring with a prefix, such as String.raw`foobarbaz`", + $propdoc: { + template_string: "[AST_TemplateString] The template string", + prefix: "[AST_SymbolRef|AST_PropAccess] The prefix, which can be a symbol such as `foo` or a dotted expression such as `String.raw`." + }, + _walk: function(visitor) { + this.prefix._walk(visitor); + this.template_string._walk(visitor); + } +}) + +var AST_TemplateString = DEFNODE("TemplateString", "segments", { + $documentation: "A template string literal", + $propdoc: { + segments: "[AST_TemplateSegment|AST_Expression]* One or more segments, starting with AST_TemplateSegment. AST_Expression may follow AST_TemplateSegment, but each AST_Expression must be followed by AST_TemplateSegment." + }, + _walk: function(visitor) { + return visitor._visit(this, function(){ + this.segments.forEach(function(seg, i){ + if (i % 2 !== 0) { + seg._walk(visitor); + } + }); + }); + } +}); + +var AST_TemplateSegment = DEFNODE("TemplateSegment", "value raw", { + $documentation: "A segment of a template string literal", + $propdoc: { + value: "Content of the segment", + raw: "Raw content of the segment" + } +}); + /* -----[ JUMPS ]----- */ var AST_Jump = DEFNODE("Jump", null, { @@ -512,7 +685,7 @@ var AST_Try = DEFNODE("Try", "bcatch bfinally", { var AST_Catch = DEFNODE("Catch", "argname", { $documentation: "A `catch` node; only makes sense as part of a `try` statement", $propdoc: { - argname: "[AST_SymbolCatch] symbol for the exception" + argname: "[AST_SymbolCatch|AST_Destructuring|AST_Expansion|AST_DefaultAssign] symbol for the exception" }, _walk: function(visitor) { return visitor._visit(this, function(){ @@ -547,16 +720,78 @@ var AST_Var = DEFNODE("Var", null, { $documentation: "A `var` statement" }, AST_Definitions); +var AST_Let = DEFNODE("Let", null, { + $documentation: "A `let` statement" +}, AST_Definitions); + var AST_Const = DEFNODE("Const", null, { $documentation: "A `const` statement" }, AST_Definitions); +var AST_NameImport = DEFNODE("NameImport", "foreign_name name", { + $documentation: "The part of the import statement that imports names from a module.", + $propdoc: { + foreign_name: "[AST_SymbolImportForeign] The name being imported (as specified in the module)", + name: "[AST_SymbolImport] The name as it becomes available to this module." + }, + _walk: function (visitor) { + return visitor._visit(this, function() { + this.foreign_name._walk(visitor); + this.name._walk(visitor); + }); + } +}) + +var AST_Import = DEFNODE("Import", "imported_name imported_names module_name", { + $documentation: "An `import` statement", + $propdoc: { + imported_name: "[AST_SymbolImport] The name of the variable holding the module's default export.", + imported_names: "[AST_NameImport*] The names of non-default imported variables", + module_name: "[AST_String] String literal describing where this module came from", + }, + _walk: function(visitor) { + return visitor._visit(this, function() { + if (this.imported_name) { + this.imported_name._walk(visitor); + } + if (this.imported_names) { + this.imported_names.forEach(function (name_import) { + name_import._walk(visitor); + }); + } + this.module_name._walk(visitor); + }); + } +}); + +var AST_Export = DEFNODE("Export", "exported_definition exported_value is_default", { + $documentation: "An `export` statement", + $propdoc: { + exported_definition: "[AST_Defun|AST_Definitions|AST_DefClass?] An exported definition", + exported_value: "[AST_Node?] An exported value", + is_default: "[Boolean] Whether this is the default exported value of this module" + }, + _walk: function (visitor) { + visitor._visit(this, function () { + if (this.exported_definition) { + this.exported_definition._walk(visitor); + } + if (this.exported_value) { + this.exported_value._walk(visitor); + } + }); + } +}, AST_Statement); + var AST_VarDef = DEFNODE("VarDef", "name value", { $documentation: "A variable declaration; only appears in a AST_Definitions node", $propdoc: { - name: "[AST_SymbolVar|AST_SymbolConst] name of the variable", + name: "[AST_SymbolVar|AST_SymbolConst|AST_Destructuring] name of the variable", value: "[AST_Node?] initializer, or null of there's no initializer" }, + is_destructuring: function() { + return this.name instanceof AST_Destructuring; + }, _walk: function(visitor) { return visitor._visit(this, function(){ this.name._walk(visitor); @@ -737,6 +972,10 @@ var AST_Assign = DEFNODE("Assign", null, { $documentation: "An assignment expression — `a = b + 5`", }, AST_Binary); +var AST_DefaultAssign = DEFNODE("DefaultAssign", null, { + $documentation: "A default assignment expression like in `(a = 3) => a`" +}, AST_Binary); + /* -----[ LITERALS ]----- */ var AST_Array = DEFNODE("Array", "elements", { @@ -772,11 +1011,13 @@ var AST_Object = DEFNODE("Object", "properties", { var AST_ObjectProperty = DEFNODE("ObjectProperty", "key value", { $documentation: "Base class for literal object properties", $propdoc: { - key: "[string] the property name converted to a string for ObjectKeyVal. For setters and getters this is an arbitrary AST_Node.", - value: "[AST_Node] property value. For setters and getters this is an AST_Function." + key: "[string|AST_Node] the property name converted to a string for ObjectKeyVal. For setters, getters and computed property this is an arbitrary AST_Node", + value: "[AST_Node] property value. For setters and getters this is an AST_Function." }, _walk: function(visitor) { return visitor._visit(this, function(){ + if (this.key instanceof AST_Node) + this.key._walk(visitor); this.value._walk(visitor); }); } @@ -789,21 +1030,72 @@ var AST_ObjectKeyVal = DEFNODE("ObjectKeyVal", "quote", { } }, AST_ObjectProperty); -var AST_ObjectSetter = DEFNODE("ObjectSetter", null, { +var AST_ObjectSetter = DEFNODE("ObjectSetter", "quote static", { + $propdoc: { + quote: "[string|undefined] the original quote character, if any", + 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", "quote static", { + $propdoc: { + quote: "[string|undefined] the original quote character, if any", + static: "[boolean] whether this is a static getter (classes only)" + }, $documentation: "An object getter property", }, AST_ObjectProperty); +var AST_ConciseMethod = DEFNODE("ConciseMethod", "quote static is_generator", { + $propdoc: { + quote: "[string|undefined] the original quote character, if any", + static: "[boolean] whether this method is static (classes only)", + is_generator: "[boolean] is generatorFn or not", + }, + $documentation: "An ES6 concise method inside an object or class" +}, AST_ObjectProperty); + +var AST_Class = DEFNODE("Class", "name extends properties", { + $propdoc: { + name: "[AST_SymbolClass|AST_SymbolDefClass?] optional class name.", + extends: "[AST_Node]? optional parent class", + properties: "[AST_ObjectProperty*] array of properties" + }, + $documentation: "An ES6 class", + _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_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", scope: "[AST_Scope/S] the current scope (not necessarily the definition scope)", thedef: "[SymbolDef/S] the definition of this symbol" }, - $documentation: "Base class for all symbols", + $documentation: "Base class for all symbols" +}); + +var AST_NewTarget = DEFNODE("NewTarget", null, { + $documentation: "A reference to new.target" }); var AST_SymbolAccessor = DEFNODE("SymbolAccessor", null, { @@ -814,16 +1106,24 @@ var AST_SymbolDeclaration = DEFNODE("SymbolDeclaration", "init", { $documentation: "A declaration symbol (symbol in var/const, function name or argument, symbol in catch)", $propdoc: { init: "[AST_Node*/S] array of initializers for this declaration." - } + }, }, AST_Symbol); var AST_SymbolVar = DEFNODE("SymbolVar", null, { $documentation: "Symbol defining a variable", }, AST_SymbolDeclaration); +var AST_SymbolBlockDeclaration = DEFNODE("SymbolBlockDeclaration", null, { + $documentation: "Base class for block-scoped declaration symbols" +}, AST_SymbolDeclaration); + var AST_SymbolConst = DEFNODE("SymbolConst", null, { $documentation: "A constant declaration" -}, AST_SymbolDeclaration); +}, AST_SymbolBlockDeclaration); + +var AST_SymbolLet = DEFNODE("SymbolLet", null, { + $documentation: "A block-scoped `let` declaration" +}, AST_SymbolBlockDeclaration); var AST_SymbolFunarg = DEFNODE("SymbolFunarg", null, { $documentation: "Symbol naming a function argument", @@ -833,13 +1133,33 @@ var AST_SymbolDefun = DEFNODE("SymbolDefun", null, { $documentation: "Symbol defining a function", }, AST_SymbolDeclaration); +var AST_SymbolMethod = DEFNODE("SymbolMethod", null, { + $documentation: "Symbol in an object defining a method", +}, AST_Symbol); + var AST_SymbolLambda = DEFNODE("SymbolLambda", null, { $documentation: "Symbol naming a function expression", }, AST_SymbolDeclaration); +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_SymbolBlockDeclaration); + +var AST_SymbolClass = DEFNODE("SymbolClass", 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); +}, AST_SymbolBlockDeclaration); + +var AST_SymbolImport = DEFNODE("SymbolImport", null, { + $documentation: "Symbol refering to an imported name", +}, AST_SymbolBlockDeclaration); + +var AST_SymbolImportForeign = DEFNODE("SymbolImportForeign", null, { + $documentation: "A symbol imported from a module, but it is defined in the other module, and its real name is irrelevant for this module's purposes", +}, AST_Symbol); var AST_Label = DEFNODE("Label", "references", { $documentation: "Symbol naming a label (declaration)", @@ -864,6 +1184,10 @@ var AST_This = DEFNODE("This", null, { $documentation: "The `this` symbol", }, AST_Symbol); +var AST_Super = DEFNODE("Super", null, { + $documentation: "The `super` symbol", +}, AST_Symbol); + var AST_Constant = DEFNODE("Constant", null, { $documentation: "Base class for all constants", getValue: function() { @@ -937,6 +1261,21 @@ var AST_True = DEFNODE("True", null, { value: true }, AST_Boolean); +/* -----[ Yield ]----- */ + +var AST_Yield = DEFNODE("Yield", "expression is_star", { + $documentation: "A `yield` statement", + $propdoc: { + expression: "[AST_Node?] the value returned or thrown by this statement; could be null (representing undefined) but only when is_star is set to false", + is_star: "[Boolean] Whether this is a yield or yield* statement" + }, + _walk: function(visitor) { + return visitor._visit(this, this.expression && function(){ + this.expression._walk(visitor); + }); + } +}); + /* -----[ TreeWalker ]----- */ function TreeWalker(callback) { @@ -964,12 +1303,15 @@ TreeWalker.prototype = { this.directives = Object.create(this.directives); } else if (node instanceof AST_Directive) { this.directives[node.value] = this.directives[node.value] ? "up" : true; + } else if (node instanceof AST_Class) { + this.directives = Object.create(this.directives); + this.directives["use strict"] = this.directives["use strict"] ? "up" : true; } this.stack.push(node); }, pop: function(node) { this.stack.pop(); - if (node instanceof AST_Lambda) { + if (node instanceof AST_Lambda || node instanceof AST_Class) { this.directives = Object.getPrototypeOf(this.directives); } }, @@ -987,7 +1329,7 @@ TreeWalker.prototype = { var dir = this.directives[type]; if (dir) return dir; var node = this.stack[this.stack.length - 1]; - if (node instanceof AST_Scope) { + if (node instanceof AST_Scope && node.body) { for (var i = 0; i < node.body.length; ++i) { var st = node.body[i]; if (!(st instanceof AST_Directive)) break; diff --git a/lib/compress.js b/lib/compress.js index ec1e7174..c921d446 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -78,6 +78,7 @@ function Compressor(options, false_by_default) { pure_funcs : null, negate_iife : !false_by_default, screw_ie8 : true, + ecma : 5, drop_console : false, angular : false, warnings : true, @@ -374,6 +375,14 @@ merge(Compressor.prototype, { return false; }; + function can_be_evicted_from_block(node) { + return !( + node instanceof AST_DefClass || + node instanceof AST_Let || + node instanceof AST_Const + ); + } + function loop_body(x) { if (x instanceof AST_Switch) return x; if (x instanceof AST_For || x instanceof AST_ForIn || x instanceof AST_DWLoop) { @@ -497,7 +506,7 @@ merge(Compressor.prototype, { if (side_effects_encountered |= lvalues_encountered) continue; // Non-constant single use vars can only be replaced in same scope. - if (ref.scope !== self) { + if (ref.scope.get_defun_scope() !== self) { side_effects_encountered |= var_decl.value.has_side_effects(compressor); continue; } @@ -588,8 +597,16 @@ merge(Compressor.prototype, { return /@ngInject/.test(comment.value); } function make_arguments_names_list(func) { + var foundDestructuring = false; return func.argnames.map(function(sym){ + if (sym instanceof AST_Destructuring) { + compressor.warn("Function with destructuring arguments marked with @ngInject [{file}:{line},{col}]", token); + foundDestructuring = true; + } + if (foundDestructuring) { return null; } return make_node(AST_String, sym, { value: sym.name }); + }).filter(function (name) { + return name !== null; }); } function make_array(orig, elements) { @@ -660,7 +677,7 @@ merge(Compressor.prototype, { function eliminate_spurious_blocks(statements) { var seen_dirs = []; return statements.reduce(function(a, stat){ - if (stat instanceof AST_BlockStatement) { + if (stat instanceof AST_BlockStatement && all(stat.body, can_be_evicted_from_block)) { CHANGED = true; a.push.apply(a, eliminate_spurious_blocks(stat.body)); } else if (stat instanceof AST_EmptyStatement) { @@ -998,7 +1015,7 @@ merge(Compressor.prototype, { compressor.warn("Dropping unreachable code [{file}:{line},{col}]", stat.start); } stat.walk(new TreeWalker(function(node){ - if (node instanceof AST_Definitions) { + if (node instanceof AST_Var) { compressor.warn("Declarations in unreachable code! [{file}:{line},{col}]", node.start); node.remove_initializers(); target.push(node); @@ -1074,8 +1091,11 @@ merge(Compressor.prototype, { // methods to determine if an expression has a string result type (function (def){ - def(AST_Node, return_false); - def(AST_String, return_true); + def(AST_Node, function(){ return false }); + def(AST_String, function(){ return true }); + def(AST_TemplateString, function(){ + return this.segments.length === 1; + }); def(AST_UnaryPrefix, function(){ return this.operator == "typeof"; }); @@ -1257,6 +1277,12 @@ merge(Compressor.prototype, { def(AST_Function, function(){ throw def; }); + def(AST_Arrow, function() { + throw def; + }); + def(AST_Class, function() { + throw def; + }); function ev(node, compressor) { if (!compressor) throw new Error("Compressor must be passed"); @@ -1268,6 +1294,10 @@ merge(Compressor.prototype, { def(AST_Constant, function(){ return this.getValue(); }); + def(AST_TemplateString, function() { + if (this.segments.length !== 1) throw def; + return this.segments[0].value; + }); def(AST_Array, function(compressor){ if (compressor.option("unsafe")) { return this.elements.map(function(element) { @@ -1303,7 +1333,8 @@ merge(Compressor.prototype, { case "typeof": // Function would be evaluated to an array and so typeof would // incorrectly return 'object'. Hence making is a special case. - if (e instanceof AST_Function) return typeof function(){}; + if (e instanceof AST_Function || + e instanceof AST_Arrow) return typeof function(){}; e = ev(e, compressor); @@ -1329,6 +1360,7 @@ merge(Compressor.prototype, { case "^" : result = ev(left, c) ^ ev(right, c); break; case "+" : result = ev(left, c) + ev(right, c); break; case "*" : result = ev(left, c) * ev(right, c); break; + case "**" : result = Math.pow(ev(left, c), ev(right, c)); break; case "/" : result = ev(left, c) / ev(right, c); break; case "%" : result = ev(left, c) % ev(right, c); break; case "-" : result = ev(left, c) - ev(right, c); break; @@ -1514,6 +1546,8 @@ merge(Compressor.prototype, { }); def(AST_Defun, return_true); def(AST_Function, return_false); + def(AST_Class, return_false); + def(AST_DefClass, return_true); def(AST_Binary, function(compressor){ return this.left.has_side_effects(compressor) || this.right.has_side_effects(compressor); @@ -1525,9 +1559,7 @@ merge(Compressor.prototype, { || this.alternative.has_side_effects(compressor); }); def(AST_Unary, function(compressor){ - return this.operator == "delete" - || this.operator == "++" - || this.operator == "--" + return member(this.operator, ["delete", "++", "--"]) || this.expression.has_side_effects(compressor); }); def(AST_SymbolRef, function(compressor){ @@ -1540,6 +1572,9 @@ merge(Compressor.prototype, { return false; }); def(AST_ObjectProperty, function(compressor){ + if (this.key instanceof AST_ObjectKeyVal && + this.key.has_side_effects(compressor)) + return true; return this.value.has_side_effects(compressor); }); def(AST_Array, function(compressor){ @@ -1579,6 +1614,7 @@ merge(Compressor.prototype, { var n = this.body.length; return n > 0 && aborts(this.body[n - 1]); }; + def(AST_Import, function(){ return null; }); def(AST_BlockStatement, block_aborts); def(AST_SwitchBranch, block_aborts); def(AST_If, function(){ @@ -1612,6 +1648,7 @@ merge(Compressor.prototype, { }); OPT(AST_Block, function(self, compressor){ + if (self.body instanceof AST_Node) { return self; } self.body = tighten_body(self.body, compressor); return self; }); @@ -1619,7 +1656,11 @@ merge(Compressor.prototype, { OPT(AST_BlockStatement, function(self, compressor){ self.body = tighten_body(self.body, compressor); switch (self.body.length) { - case 1: return self.body[0]; + case 1: + if (can_be_evicted_from_block(self.body[0])) { + return self.body[0]; + } + break; case 0: return make_node(AST_EmptyStatement, self); } return self; @@ -1650,6 +1691,8 @@ merge(Compressor.prototype, { }); } var initializations = new Dictionary(); + var destructuring_value = null; + var in_definition = false; // pass 1: find out which symbols are directly used in // this scope (not in nested scopes). var scope = this; @@ -1663,6 +1706,8 @@ merge(Compressor.prototype, { in_use.push(node_def); } } + } + if (node instanceof AST_Defun || node instanceof AST_DefClass) { initializations.add(node.name.name, node); return true; // don't go in nested scopes } @@ -1676,7 +1721,16 @@ merge(Compressor.prototype, { } } if (def.value) { - initializations.add(def.name.name, def.value); + if (def.is_destructuring()) { + var destructuring_cache = destructuring_value; + destructuring_value = def.value; + in_definition = true; + def.walk(tw); + in_definition = false; + destructuring_value = destructuring_cache; + } else { + initializations.add(def.name.name, def.value); + } if (def.value.has_side_effects(compressor)) { def.value.walk(tw); } @@ -1707,6 +1761,39 @@ merge(Compressor.prototype, { scope = save_scope; return true; } + if (node instanceof AST_Destructuring) { + if (!in_definition) { + return true; + } + for (var i = 0; i < node.names.length; i++) { + if (node.names[i] instanceof AST_Destructuring) { + node.names[i].walk(tw); + } + else if (node.names[i] instanceof AST_Expansion) { + if (node.names[i].expression instanceof AST_Symbol) { + initializations.add(node.names[i].expression.name, destructuring_value); + } else { + throw new Error(string_template("Can't handle expansion of type: {type}", { + type: Object.getPrototypeOf(node.names[i].expression).TYPE + })); + } + } + else if (node.names[i] instanceof AST_Hole) { + continue; + } + else if (node.names[i] instanceof AST_ObjectKeyVal && typeof node.names[i].key === "string") { + initializations.add(node.names[i].key, destructuring_value); + } + else if (node.names[i] instanceof AST_Symbol) { + initializations.add(node.names[i].name, destructuring_value); + } else { + throw new Error(string_template("Unknown destructuring element of type: {type}", { + type: Object.getPrototypeOf(node.names[i]).TYPE + })); + } + } + return true; + } } }); self.walk(tw); @@ -1743,22 +1830,35 @@ merge(Compressor.prototype, { if (node instanceof AST_Lambda && !(node instanceof AST_Accessor)) { if (!compressor.option("keep_fargs")) { for (var a = node.argnames, i = a.length; --i >= 0;) { - var sym = a[i]; - if (!(sym.definition().id in in_use_ids)) { - a.pop(); - compressor.warn("Dropping unused function argument {name} [{file}:{line},{col}]", { - name : sym.name, - file : sym.start.file, - line : sym.start.line, - col : sym.start.col - }); + if (a[i] instanceof AST_Destructuring) { + // Do not drop destructuring arguments. + // They constitute a type assertion, so dropping + // them would stop that TypeError which would happen + // if someone called it with an incorrectly formatted + // parameter. + break; + } else { + var sym = a[i]; + if (sym instanceof AST_Expansion) { + sym = sym.symbol; + } + if (!(sym.definition().id in in_use_ids)) { + a.pop(); + compressor.warn("Dropping unused function argument {name} [{file}:{line},{col}]", { + name : sym.name, + file : sym.start.file, + line : sym.start.line, + col : sym.start.col + }); + } + else break; } - else break; } } } - if (drop_funcs && node instanceof AST_Defun && node !== self) { - if (!(node.name.definition().id in in_use_ids)) { + if (drop_funcs && (node instanceof AST_Defun || node instanceof AST_DefClass) && node !== self) { + var keep = (node.name.definition().id in in_use_ids) || node.name.definition().global; + if (!keep) { compressor.warn("Dropping unused function {name} [{file}:{line},{col}]", { name : node.name.name, file : node.name.start.file, @@ -1771,7 +1871,10 @@ merge(Compressor.prototype, { } if (drop_vars && node instanceof AST_Definitions && !(tt.parent() instanceof AST_ForIn)) { var def = node.definitions.filter(function(def){ + if (def.is_destructuring()) return true; if (def.name.definition().id in in_use_ids) return true; + if (def.name.definition().global) return true; + var w = { name : def.name.name, file : def.name.start.file, @@ -1861,6 +1964,12 @@ merge(Compressor.prototype, { }); } } + if (node instanceof AST_BlockStatement) { + descend(node, this); + if (in_list && all(node.body, can_be_evicted_from_block)) { + return MAP.splice(node.body); + } + } if (node instanceof AST_Scope && node !== self) return node; } @@ -1872,8 +1981,12 @@ merge(Compressor.prototype, { AST_Scope.DEFMETHOD("hoist_declarations", function(compressor){ var self = this; if (compressor.has_directive("use asm")) return self; + // Hoisting makes no sense in an arrow func + if (!Array.isArray(self.body)) return self; + var hoist_funs = compressor.option("hoist_funs"); var hoist_vars = compressor.option("hoist_vars"); + if (hoist_funs || hoist_vars) { var dirs = []; var hoisted = []; @@ -1902,6 +2015,7 @@ merge(Compressor.prototype, { } if (node instanceof AST_Var && hoist_vars) { node.definitions.forEach(function(def){ + if (def.is_destructuring()) { return; } vars.set(def.name.name, def); ++vars_found; }); @@ -1934,7 +2048,7 @@ merge(Compressor.prototype, { vars.each(function(def, name){ if (self instanceof AST_Lambda && find_if(function(x){ return x.name == def.name.name }, - self.argnames)) { + self.args_as_names())) { vars.del(name); } else { def = def.clone(); @@ -2501,13 +2615,23 @@ merge(Compressor.prototype, { AST_Definitions.DEFMETHOD("to_assignments", function(){ var assignments = this.definitions.reduce(function(a, def){ - if (def.value) { + if (def.value && !def.is_destructuring()) { var name = make_node(AST_SymbolRef, def.name, def.name); a.push(make_node(AST_Assign, def, { operator : "=", left : name, right : def.value })); + } else if (def.value) { + // Because it's a destructuring, do not turn into an assignment. + var varDef = make_node(AST_VarDef, def, { + name: def.name, + value: def.value + }); + var var_ = make_node(AST_Var, def, { + definitions: [ varDef ] + }); + a.push(var_); } return a; }, []); @@ -2521,6 +2645,10 @@ merge(Compressor.prototype, { return self; }); + OPT(AST_Import, function(self, compressor) { + return self; + }); + OPT(AST_Call, function(self, compressor){ if (compressor.option("unused") && self.expression instanceof AST_Function @@ -2616,7 +2744,7 @@ merge(Compressor.prototype, { if (!fun) return self; var args = fun.argnames.map(function(arg, i){ return make_node(AST_String, self.args[i], { - value: arg.print_to_string() + value: arg.print_to_string({ecma: compressor.option("ecma")}) }); }); var code = OutputStream(); @@ -2638,6 +2766,11 @@ merge(Compressor.prototype, { } } break; + case "Symbol": + // Symbol's argument is only used for debugging. + self.args = []; + return self; + break; } } else if (exp instanceof AST_Dot && exp.property == "toString" && self.args.length == 0) { @@ -2713,6 +2846,7 @@ merge(Compressor.prototype, { } if (compressor.option("side_effects")) { if (self.expression instanceof AST_Function + && !self.expression.is_generator && self.args.length == 0 && !AST_Block.prototype.has_side_effects.call(self.expression, compressor)) { return make_node(AST_Undefined, self).transform(compressor); @@ -3380,6 +3514,22 @@ merge(Compressor.prototype, { return self; }); + OPT(AST_DefaultAssign, function(self, compressor){ + if (!compressor.option("evaluate")) { + return self; + } + var evaluateRight = self.right.evaluate(compressor); + + // `[x = undefined] = foo` ---> `[x] = foo` + if (evaluateRight.length > 1 && evaluateRight[1] === undefined) { + self = self.left; + } else { + self.right = evaluateRight[0]; + } + + return self; + }); + OPT(AST_Conditional, function(self, compressor){ if (!compressor.option("conditionals")) return self; if (self.condition instanceof AST_Seq) { @@ -3642,6 +3792,19 @@ merge(Compressor.prototype, { return self; }); + OPT(AST_Class, function(self, compressor){ + // HACK to avoid compress failure. + // AST_Class is not really an AST_Scope/AST_Block as it lacks a body. + return self; + }); + + OPT(AST_Yield, function(self, compressor){ + if (!self.is_star && self.expression instanceof AST_Undefined) { + self.expression = null; + } + return self; + }); + OPT(AST_VarDef, function(self, compressor){ var defines = compressor.option("global_defs"); if (defines && HOP(defines, self.name.name)) { @@ -3650,4 +3813,37 @@ merge(Compressor.prototype, { return self; }); + OPT(AST_TemplateString, function(self, compressor){ + if (!compressor.option("evaluate") + || compressor.parent() instanceof AST_PrefixedTemplateString) + return self; + + var segments = []; + for (var i = 0; i < self.segments.length; i++) { + if (self.segments[i] instanceof AST_Node) { + var result = self.segments[i].evaluate(compressor); + // No result[1] means nothing to stringify + if (result.length === 1) { + segments.push(result[0]); + continue; + } + // Evaluate length + if (result[0].print_to_string().length + 3 /* ${} */ < (result[1]+"").length) { + segments.push(result[0]); + continue; + } + // There should always be a previous and next segment if segment is a node + segments[segments.length - 1].value = segments[segments.length - 1].value + result[1] + self.segments[++i].value; + } else { + segments.push(self.segments[i]); + } + } + self.segments = segments; + + return self; + }); + + OPT(AST_PrefixedTemplateString, function(self, compressor){ + return self; + }); })(); diff --git a/lib/output.js b/lib/output.js index 4a0a1e0e..9f472ce6 100644 --- a/lib/output.js +++ b/lib/output.js @@ -67,6 +67,7 @@ function OutputStream(options) { quote_keys : false, space_colon : true, ascii_only : false, + ascii_identifiers: undefined, unescape_regexps : false, inline_script : false, width : 80, @@ -82,9 +83,17 @@ function OutputStream(options) { preamble : null, quote_style : 0, keep_quoted_props: false, + shorthand : undefined, + ecma : 5, wrap_iife : false, }, true); + if (typeof options.ascii_identifiers === 'undefined') + options.ascii_identifiers = options.ascii_only; + + if (options.shorthand === undefined) + options.shorthand = options.ecma > 5; + // Convert comment option to RegExp if neccessary and set up comments filter var comment_filter = options.shebang ? is_comment5 : return_false; // Default case, throw all comments away except shebangs if (options.comments) { @@ -120,9 +129,19 @@ function OutputStream(options) { var OUTPUT = ""; function to_ascii(str, identifier) { - return str.replace(/[\u0000-\u001f\u007f-\uffff]/g, function(ch) { - var code = ch.charCodeAt(0).toString(16); - if (code.length <= 2 && !identifier) { + return str.replace(/[\ud800-\udbff][\udc00-\udfff]|[\u0000-\u001f\u007f-\uffff]/g, function(ch) { + var code = get_full_char_code(ch, 0).toString(16); + + if ((identifier && code.length === 1 && options.ecma >= 6) || code.length > 4) { + if (options.ecma < 6) { + if (identifier) { + return ch; // no \u{} support + } + return "\\u" + ch.charCodeAt(0).toString(16) + "\\u" + + ch.charCodeAt(1).toString(16); + } + return "\\u{" + code + "}"; + } else if (code.length <= 2 && !identifier) { while (code.length < 2) code = "0" + code; return "\\x" + code; } else { @@ -150,7 +169,7 @@ function OutputStream(options) { case "\u2029": return "\\u2029"; case "\ufeff": return "\\ufeff"; case "\0": - return /[0-7]/.test(str.charAt(i+1)) ? "\\x00" : "\\0"; + return /[0-7]/.test(get_full_char(str, i+1)) ? "\\x00" : "\\0"; } return s; }); @@ -160,7 +179,22 @@ function OutputStream(options) { function quote_double() { return '"' + str.replace(/\x22/g, '\\"') + '"'; } + function quote_template() { + if (!options.ascii_only) { + str = str.replace(/\\(n|r|u2028|u2029)/g, function(s, c) { + switch(c) { + case "n": return "\n"; + case "r": return "\r"; + case "u2028": return "\u2028"; + case "u2029": return "\u2029"; + } + return s; + }); + } + return '`' + str.replace(/`/g, '\\`') + '`'; + } if (options.ascii_only) str = to_ascii(str); + if (quote === "`") return quote_template(); switch (options.quote_style) { case 1: return quote_single(); @@ -185,7 +219,7 @@ function OutputStream(options) { function make_name(name) { name = name.toString(); - if (options.ascii_only) + if (options.ascii_identifiers) name = to_ascii(name, true); return name; }; @@ -202,7 +236,13 @@ function OutputStream(options) { var last = null; function last_char() { - return last.charAt(last.length - 1); + var char = last.charAt(last.length - 1); + + if (is_surrogate_pair_tail(char)) { + return last.charAt(last.length - 2) + char; + } + + return char; }; var ensure_line_len = options.max_line_len ? function() { @@ -226,7 +266,7 @@ function OutputStream(options) { function print(str) { str = String(str); - var ch = str.charAt(0); + var ch = get_full_char(str, 0); if (might_need_semicolon) { might_need_semicolon = false; @@ -291,6 +331,10 @@ function OutputStream(options) { last = str; }; + var star = function(){ + print("*"); + } + var space = options.beautify ? function() { print(" "); } : function() { @@ -414,6 +458,7 @@ function OutputStream(options) { should_break : function() { return options.width && this.current_width() >= options.width }, newline : newline, print : print, + star : star, space : space, comma : comma, colon : colon, @@ -433,6 +478,10 @@ function OutputStream(options) { } print(encoded); }, + print_template_string_chars: function(str) { + var encoded = encode_string(str, '`').replace(/\${/g, "\\${"); + return print(encoded.substr(1, encoded.length - 2)); + }, encode_string : encode_string, next_indent : next_indent, with_indent : with_indent, @@ -595,7 +644,13 @@ function OutputStream(options) { PARENS([ AST_Unary, AST_Undefined ], function(output){ var p = output.parent(); return p instanceof AST_PropAccess && p.expression === this - || p instanceof AST_Call && p.expression === this; + || p instanceof AST_Call && p.expression === this + || p instanceof AST_Binary + && p.operator === "**" + && this instanceof AST_UnaryPrefix + && p.left === this + && this.operator !== "++" + && this.operator !== "--"; }); PARENS(AST_Seq, function(output){ @@ -609,6 +664,8 @@ function OutputStream(options) { || p instanceof AST_ObjectProperty // { foo: (1, 2) }.foo ==> 2 || p instanceof AST_Conditional /* (false, true) ? (a = 10, b = 20) : (c = 30) * ==> 20 (side effect, set a := 10 and b := 20) */ + || p instanceof AST_Arrow // x => (x, x) + || p instanceof AST_DefaultAssign // x => (x = (0, function(){})) ; }); @@ -635,6 +692,24 @@ function OutputStream(options) { } }); + PARENS(AST_Yield, function(output){ + var p = output.parent(); + // (yield 1) + (yield 2) + // a = yield 3 + if (p instanceof AST_Binary && p.operator !== "=") + return true; + // (yield 1) ? yield 2 : yield 3 + if (p instanceof AST_Conditional && p.condition === this) + return true; + // -(yield 4) + if (p instanceof AST_Unary) + return true; + // (yield x).foo + // (yield x)['foo'] + if (p instanceof AST_PropAccess && p.expression === this) + return true; + }); + PARENS(AST_PropAccess, function(output){ var p = output.parent(); if (p instanceof AST_New && p.expression === this) { @@ -704,6 +779,9 @@ function OutputStream(options) { // (a = foo)["prop"] —or— (a = foo).prop if (p instanceof AST_PropAccess && p.expression === this) return true; + // ({a, b} = {a: 1, b: 2}), a destructuring assignment + if (this instanceof AST_Assign && this.left instanceof AST_Destructuring && this.left.is_array === false) + return true; }); /* -----[ PRINTERS ]----- */ @@ -712,6 +790,28 @@ function OutputStream(options) { output.print_string(self.value, self.quote); output.semicolon(); }); + + DEFPRINT(AST_Expansion, function (self, output) { + output.print('...'); + self.expression.print(output); + }); + + DEFPRINT(AST_Destructuring, function (self, output) { + output.print(self.is_array ? "[" : "{"); + var first = true; + var len = self.names.length; + self.names.forEach(function (name, i) { + if (first) first = false; else { output.comma(); output.space(); } + name.print(output); + // If the final element is a hole, we need to make sure it + // doesn't look like a trailing comma, by inserting an actual + // trailing comma. + if (i === len - 1 && name instanceof AST_Hole) + output.comma(); + }) + output.print(self.is_array ? "]" : "}"); + }); + DEFPRINT(AST_Debugger, function(self, output){ output.print("debugger"); output.semicolon(); @@ -836,7 +936,11 @@ function OutputStream(options) { output.with_parens(function(){ self.init.print(output); output.space(); - output.print("in"); + if (self instanceof AST_ForOf) { + output.print("of"); + } else { + output.print("in"); + } output.space(); self.object.print(output); }); @@ -858,10 +962,19 @@ function OutputStream(options) { var self = this; if (!nokeyword) { output.print("function"); + if (this.is_generator) { + output.star(); + } + if (self.name) { + output.space(); + } } - if (self.name) { - output.space(); + if (self.name instanceof AST_Symbol) { 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(){ self.argnames.forEach(function(arg, i){ @@ -876,6 +989,56 @@ function OutputStream(options) { self._do_print(output); }); + DEFPRINT(AST_PrefixedTemplateString, function(self, output) { + self.prefix.print(output); + self.template_string.print(output); + }); + DEFPRINT(AST_TemplateString, function(self, output) { + var is_tagged = output.parent() instanceof AST_PrefixedTemplateString; + + output.print("`"); + for (var i = 0; i < self.segments.length; i++) { + if (!(self.segments[i] instanceof AST_TemplateSegment)) { + output.print("${"); + self.segments[i].print(output); + output.print("}"); + } else if (is_tagged) { + output.print(self.segments[i].raw); + } else { + output.print_template_string_chars(self.segments[i].value); + } + } + output.print("`"); + }); + + AST_Arrow.DEFMETHOD("_do_print", function(output){ + var self = this; + var parent = output.parent(); + var needs_parens = parent instanceof AST_Binary || + parent instanceof AST_Unary || + (parent instanceof AST_Call && self === parent.expression); + if (needs_parens) { output.print("(") } + if (self.argnames.length === 1 && self.argnames[0] instanceof AST_Symbol) { + 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.body instanceof AST_Node) { + this.body.print(output); + } else { + print_bracketed(this.body, output); + } + if (needs_parens) { output.print(")") } + }); + /* -----[ exits ]----- */ AST_Exit.DEFMETHOD("_do_print", function(output, kind){ output.print(kind); @@ -892,6 +1055,17 @@ function OutputStream(options) { self._do_print(output, "throw"); }); + /* -----[ yield ]----- */ + + DEFPRINT(AST_Yield, function(self, output){ + var star = self.is_star ? "*" : ""; + output.print("yield" + star); + if (self.expression) { + output.space(); + self.expression.print(output); + } + }); + /* -----[ loop control ]----- */ AST_LoopControl.DEFMETHOD("_do_print", function(output, kind){ output.print(kind); @@ -1051,12 +1225,77 @@ function OutputStream(options) { if (!avoid_semicolon) output.semicolon(); }); + DEFPRINT(AST_Let, function(self, output){ + self._do_print(output, "let"); + }); DEFPRINT(AST_Var, function(self, output){ self._do_print(output, "var"); }); DEFPRINT(AST_Const, function(self, output){ self._do_print(output, "const"); }); + DEFPRINT(AST_Import, function(self, output) { + output.print("import"); + output.space(); + if (self.imported_name) { + self.imported_name.print(output); + } + if (self.imported_name && self.imported_names) { + output.print(","); + output.space(); + } + if (self.imported_names) { + output.print("{"); + self.imported_names.forEach(function(name_import, i) { + output.space(); + name_import.print(output); + if (i < self.imported_names.length - 1) { + output.print(","); + output.space(); + } + }); + output.space(); + output.print("}"); + } + if (self.imported_name || self.imported_names) { + output.space(); + output.print("from") + output.space(); + } + self.module_name.print(output); + output.semicolon(); + }); + + DEFPRINT(AST_NameImport, function(self, output) { + var definition = self.name.definition(); + var names_are_different = + (definition && definition.mangled_name || self.name.name) !== + self.foreign_name.name; + if (names_are_different) { + output.print(self.foreign_name.name); + output.space(); + output.print("as"); + output.space(); + self.name.print(output); + } else { + self.name.print(output); + } + }); + + DEFPRINT(AST_Export, function(self, output) { + output.print("export"); + output.space(); + if (self.is_default) { + output.print("default"); + output.space(); + } + if (self.exported_value) { + self.exported_value.print(output); + } else if (self.exported_definition) { + self.exported_definition.print(output); + } + output.semicolon(); + }); function parenthesize_for_noin(node, output, noin) { if (!noin) node.print(output); @@ -1228,9 +1467,35 @@ function OutputStream(options) { }); else output.print("{}"); }); - DEFPRINT(AST_ObjectKeyVal, function(self, output){ - var key = self.key; - var quote = self.quote; + 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_NewTarget, function(self, output) { + output.print("new.target"); + }); + AST_ObjectProperty.DEFMETHOD("print_property_name", function(key, quote, output) { if (output.option("quote_keys")) { output.print_string(key + ""); } else if ((typeof key == "number" @@ -1247,24 +1512,91 @@ function OutputStream(options) { } else { output.print_string(key, quote); } - output.colon(); - self.value.print(output); + }); + DEFPRINT(AST_ObjectKeyVal, function(self, output){ + function get_name(self) { + var def = self.definition(); + return def ? def.mangled_name || def.name : self.name; + } + + var allowShortHand = output.option("shorthand"); + if (allowShortHand && + self.value instanceof AST_Symbol && + is_identifier_string(self.key) && + get_name(self.value) === self.key + ) { + self.print_property_name(self.key, self.quote, output); + + } else if (allowShortHand && + self.value instanceof AST_DefaultAssign && + self.value.left instanceof AST_Symbol && + is_identifier_string(self.key) && + get_name(self.value.left) === self.key + ) { + self.print_property_name(self.key, self.quote, output); + output.print("="); + self.value.right.print(output); + } else { + if (!(self.key instanceof AST_Node)) { + self.print_property_name(self.key, self.quote, output); + } else { + output.with_square(function() { + self.key.print(output); + }); + } + output.colon(); + self.value.print(output); + } + }); + AST_ObjectProperty.DEFMETHOD("_print_getter_setter", function(type, self, output) { + if (self.static) { + output.print("static"); + output.space(); + } + output.print(type); + output.space(); + if (self.key instanceof AST_SymbolMethod) { + self.print_property_name(self.key.name, self.quote, output); + } else { + output.with_square(function() { + self.key.print(output); + }); + } + self.value._do_print(output, true); }); DEFPRINT(AST_ObjectSetter, function(self, output){ - output.print("set"); - output.space(); - self.key.print(output); - self.value._do_print(output, true); + self._print_getter_setter("set", self, output); }); DEFPRINT(AST_ObjectGetter, function(self, output){ - output.print("get"); + self._print_getter_setter("get", self, output); + }); + DEFPRINT(AST_ConciseMethod, function(self, output){ + if (self.static) { + output.print("static"); + output.space(); + } + if (self.is_generator) { + output.print("*"); + } output.space(); - self.key.print(output); + if (self.key instanceof AST_SymbolMethod) { + self.print_property_name(self.key.name, self.quote, output); + } else { + output.with_square(function() { + self.key.print(output); + }); + } self.value._do_print(output, true); }); - DEFPRINT(AST_Symbol, function(self, output){ - var def = self.definition(); - output.print_name(def ? def.mangled_name || def.name : self.name); + AST_Symbol.DEFMETHOD("_do_print", function(output){ + var def = this.definition(); + output.print_name(def ? def.mangled_name || def.name : this.name); + }); + DEFPRINT(AST_Symbol, function (self, output) { + self._do_print(output); + }); + DEFPRINT(AST_SymbolDeclaration, function(self, output){ + self._do_print(output); }); DEFPRINT(AST_Undefined, function(self, output){ output.print("void 0"); @@ -1279,6 +1611,9 @@ function OutputStream(options) { DEFPRINT(AST_This, function(self, output){ output.print("this"); }); + DEFPRINT(AST_Super, function(self, output){ + output.print("super"); + }); DEFPRINT(AST_Constant, function(self, output){ output.print(self.getValue()); }); diff --git a/lib/parse.js b/lib/parse.js index 9b198ccd..118ccb9e 100644 --- a/lib/parse.js +++ b/lib/parse.js @@ -44,11 +44,10 @@ "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 void while with'; +var KEYWORDS = 'break case catch class const continue debugger default delete do else export extends finally for function if in instanceof new return switch throw try typeof var let void while with import'; 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 let 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'; +var RESERVED_WORDS = 'enum implements interface package private protected public static super this ' + KEYWORDS_ATOM + " " + KEYWORDS; +var KEYWORDS_BEFORE_EXPRESSION = 'return new delete throw else case yield'; KEYWORDS = makePredicate(KEYWORDS); RESERVED_WORDS = makePredicate(RESERVED_WORDS); @@ -57,8 +56,12 @@ KEYWORDS_ATOM = makePredicate(KEYWORDS_ATOM); var OPERATOR_CHARS = makePredicate(characters("+-*&%=<>!?|~^")); +var RE_NUM_LITERAL = /[0-9a-f]/i; var RE_HEX_NUMBER = /^0x[0-9a-f]+$/i; var RE_OCT_NUMBER = /^0[0-7]+$/; +var RE_ES6_OCT_NUMBER = /^0o[0-7]+$/i; +var RE_BIN_NUMBER = /^0b[01]+$/i; +var RE_DEC_NUMBER = /^\d*\.?\d*(?:e[+-]?\d*(?:\d\.?|\.?\d)\d*)?$/i; var OPERATORS = makePredicate([ "in", @@ -77,6 +80,7 @@ var OPERATORS = makePredicate([ "|", "^", "*", + "**", "/", "%", ">>", @@ -96,6 +100,7 @@ var OPERATORS = makePredicate([ "-=", "/=", "*=", + "**=", "%=", ">>=", "<<=", @@ -111,6 +116,8 @@ var WHITESPACE_CHARS = makePredicate(characters(" \u00a0\n\r\t\f\u000b\u200b\u20 var NEWLINE_CHARS = makePredicate(characters("\n\r\u2028\u2029")); +var PUNC_AFTER_EXPRESSION = makePredicate(characters(";]),:")); + var PUNC_BEFORE_EXPRESSION = makePredicate(characters("[{(,.;:")); var PUNC_CHARS = makePredicate(characters("[]{}(),;:")); @@ -119,58 +126,89 @@ var REGEXP_MODIFIERS = makePredicate(characters("gmsiy")); /* -----[ Tokenizer ]----- */ -// regexps adapted from http://xregexp.com/plugins/#unicode +// surrogate safe regexps adapted from https://github.com/mathiasbynens/unicode-8.0.0/tree/89b412d8a71ecca9ed593d9e9fa073ab64acfebe/Binary_Property var UNICODE = { - letter: new RegExp("[\\u0041-\\u005A\\u0061-\\u007A\\u00AA\\u00B5\\u00BA\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02C1\\u02C6-\\u02D1\\u02E0-\\u02E4\\u02EC\\u02EE\\u0370-\\u0374\\u0376\\u0377\\u037A-\\u037D\\u037F\\u0386\\u0388-\\u038A\\u038C\\u038E-\\u03A1\\u03A3-\\u03F5\\u03F7-\\u0481\\u048A-\\u052F\\u0531-\\u0556\\u0559\\u0561-\\u0587\\u05D0-\\u05EA\\u05F0-\\u05F2\\u0620-\\u064A\\u066E\\u066F\\u0671-\\u06D3\\u06D5\\u06E5\\u06E6\\u06EE\\u06EF\\u06FA-\\u06FC\\u06FF\\u0710\\u0712-\\u072F\\u074D-\\u07A5\\u07B1\\u07CA-\\u07EA\\u07F4\\u07F5\\u07FA\\u0800-\\u0815\\u081A\\u0824\\u0828\\u0840-\\u0858\\u08A0-\\u08B2\\u0904-\\u0939\\u093D\\u0950\\u0958-\\u0961\\u0971-\\u0980\\u0985-\\u098C\\u098F\\u0990\\u0993-\\u09A8\\u09AA-\\u09B0\\u09B2\\u09B6-\\u09B9\\u09BD\\u09CE\\u09DC\\u09DD\\u09DF-\\u09E1\\u09F0\\u09F1\\u0A05-\\u0A0A\\u0A0F\\u0A10\\u0A13-\\u0A28\\u0A2A-\\u0A30\\u0A32\\u0A33\\u0A35\\u0A36\\u0A38\\u0A39\\u0A59-\\u0A5C\\u0A5E\\u0A72-\\u0A74\\u0A85-\\u0A8D\\u0A8F-\\u0A91\\u0A93-\\u0AA8\\u0AAA-\\u0AB0\\u0AB2\\u0AB3\\u0AB5-\\u0AB9\\u0ABD\\u0AD0\\u0AE0\\u0AE1\\u0B05-\\u0B0C\\u0B0F\\u0B10\\u0B13-\\u0B28\\u0B2A-\\u0B30\\u0B32\\u0B33\\u0B35-\\u0B39\\u0B3D\\u0B5C\\u0B5D\\u0B5F-\\u0B61\\u0B71\\u0B83\\u0B85-\\u0B8A\\u0B8E-\\u0B90\\u0B92-\\u0B95\\u0B99\\u0B9A\\u0B9C\\u0B9E\\u0B9F\\u0BA3\\u0BA4\\u0BA8-\\u0BAA\\u0BAE-\\u0BB9\\u0BD0\\u0C05-\\u0C0C\\u0C0E-\\u0C10\\u0C12-\\u0C28\\u0C2A-\\u0C39\\u0C3D\\u0C58\\u0C59\\u0C60\\u0C61\\u0C85-\\u0C8C\\u0C8E-\\u0C90\\u0C92-\\u0CA8\\u0CAA-\\u0CB3\\u0CB5-\\u0CB9\\u0CBD\\u0CDE\\u0CE0\\u0CE1\\u0CF1\\u0CF2\\u0D05-\\u0D0C\\u0D0E-\\u0D10\\u0D12-\\u0D3A\\u0D3D\\u0D4E\\u0D60\\u0D61\\u0D7A-\\u0D7F\\u0D85-\\u0D96\\u0D9A-\\u0DB1\\u0DB3-\\u0DBB\\u0DBD\\u0DC0-\\u0DC6\\u0E01-\\u0E30\\u0E32\\u0E33\\u0E40-\\u0E46\\u0E81\\u0E82\\u0E84\\u0E87\\u0E88\\u0E8A\\u0E8D\\u0E94-\\u0E97\\u0E99-\\u0E9F\\u0EA1-\\u0EA3\\u0EA5\\u0EA7\\u0EAA\\u0EAB\\u0EAD-\\u0EB0\\u0EB2\\u0EB3\\u0EBD\\u0EC0-\\u0EC4\\u0EC6\\u0EDC-\\u0EDF\\u0F00\\u0F40-\\u0F47\\u0F49-\\u0F6C\\u0F88-\\u0F8C\\u1000-\\u102A\\u103F\\u1050-\\u1055\\u105A-\\u105D\\u1061\\u1065\\u1066\\u106E-\\u1070\\u1075-\\u1081\\u108E\\u10A0-\\u10C5\\u10C7\\u10CD\\u10D0-\\u10FA\\u10FC-\\u1248\\u124A-\\u124D\\u1250-\\u1256\\u1258\\u125A-\\u125D\\u1260-\\u1288\\u128A-\\u128D\\u1290-\\u12B0\\u12B2-\\u12B5\\u12B8-\\u12BE\\u12C0\\u12C2-\\u12C5\\u12C8-\\u12D6\\u12D8-\\u1310\\u1312-\\u1315\\u1318-\\u135A\\u1380-\\u138F\\u13A0-\\u13F4\\u1401-\\u166C\\u166F-\\u167F\\u1681-\\u169A\\u16A0-\\u16EA\\u16EE-\\u16F8\\u1700-\\u170C\\u170E-\\u1711\\u1720-\\u1731\\u1740-\\u1751\\u1760-\\u176C\\u176E-\\u1770\\u1780-\\u17B3\\u17D7\\u17DC\\u1820-\\u1877\\u1880-\\u18A8\\u18AA\\u18B0-\\u18F5\\u1900-\\u191E\\u1950-\\u196D\\u1970-\\u1974\\u1980-\\u19AB\\u19C1-\\u19C7\\u1A00-\\u1A16\\u1A20-\\u1A54\\u1AA7\\u1B05-\\u1B33\\u1B45-\\u1B4B\\u1B83-\\u1BA0\\u1BAE\\u1BAF\\u1BBA-\\u1BE5\\u1C00-\\u1C23\\u1C4D-\\u1C4F\\u1C5A-\\u1C7D\\u1CE9-\\u1CEC\\u1CEE-\\u1CF1\\u1CF5\\u1CF6\\u1D00-\\u1DBF\\u1E00-\\u1F15\\u1F18-\\u1F1D\\u1F20-\\u1F45\\u1F48-\\u1F4D\\u1F50-\\u1F57\\u1F59\\u1F5B\\u1F5D\\u1F5F-\\u1F7D\\u1F80-\\u1FB4\\u1FB6-\\u1FBC\\u1FBE\\u1FC2-\\u1FC4\\u1FC6-\\u1FCC\\u1FD0-\\u1FD3\\u1FD6-\\u1FDB\\u1FE0-\\u1FEC\\u1FF2-\\u1FF4\\u1FF6-\\u1FFC\\u2071\\u207F\\u2090-\\u209C\\u2102\\u2107\\u210A-\\u2113\\u2115\\u2119-\\u211D\\u2124\\u2126\\u2128\\u212A-\\u212D\\u212F-\\u2139\\u213C-\\u213F\\u2145-\\u2149\\u214E\\u2160-\\u2188\\u2C00-\\u2C2E\\u2C30-\\u2C5E\\u2C60-\\u2CE4\\u2CEB-\\u2CEE\\u2CF2\\u2CF3\\u2D00-\\u2D25\\u2D27\\u2D2D\\u2D30-\\u2D67\\u2D6F\\u2D80-\\u2D96\\u2DA0-\\u2DA6\\u2DA8-\\u2DAE\\u2DB0-\\u2DB6\\u2DB8-\\u2DBE\\u2DC0-\\u2DC6\\u2DC8-\\u2DCE\\u2DD0-\\u2DD6\\u2DD8-\\u2DDE\\u2E2F\\u3005-\\u3007\\u3021-\\u3029\\u3031-\\u3035\\u3038-\\u303C\\u3041-\\u3096\\u309D-\\u309F\\u30A1-\\u30FA\\u30FC-\\u30FF\\u3105-\\u312D\\u3131-\\u318E\\u31A0-\\u31BA\\u31F0-\\u31FF\\u3400-\\u4DB5\\u4E00-\\u9FCC\\uA000-\\uA48C\\uA4D0-\\uA4FD\\uA500-\\uA60C\\uA610-\\uA61F\\uA62A\\uA62B\\uA640-\\uA66E\\uA67F-\\uA69D\\uA6A0-\\uA6EF\\uA717-\\uA71F\\uA722-\\uA788\\uA78B-\\uA78E\\uA790-\\uA7AD\\uA7B0\\uA7B1\\uA7F7-\\uA801\\uA803-\\uA805\\uA807-\\uA80A\\uA80C-\\uA822\\uA840-\\uA873\\uA882-\\uA8B3\\uA8F2-\\uA8F7\\uA8FB\\uA90A-\\uA925\\uA930-\\uA946\\uA960-\\uA97C\\uA984-\\uA9B2\\uA9CF\\uA9E0-\\uA9E4\\uA9E6-\\uA9EF\\uA9FA-\\uA9FE\\uAA00-\\uAA28\\uAA40-\\uAA42\\uAA44-\\uAA4B\\uAA60-\\uAA76\\uAA7A\\uAA7E-\\uAAAF\\uAAB1\\uAAB5\\uAAB6\\uAAB9-\\uAABD\\uAAC0\\uAAC2\\uAADB-\\uAADD\\uAAE0-\\uAAEA\\uAAF2-\\uAAF4\\uAB01-\\uAB06\\uAB09-\\uAB0E\\uAB11-\\uAB16\\uAB20-\\uAB26\\uAB28-\\uAB2E\\uAB30-\\uAB5A\\uAB5C-\\uAB5F\\uAB64\\uAB65\\uABC0-\\uABE2\\uAC00-\\uD7A3\\uD7B0-\\uD7C6\\uD7CB-\\uD7FB\\uF900-\\uFA6D\\uFA70-\\uFAD9\\uFB00-\\uFB06\\uFB13-\\uFB17\\uFB1D\\uFB1F-\\uFB28\\uFB2A-\\uFB36\\uFB38-\\uFB3C\\uFB3E\\uFB40\\uFB41\\uFB43\\uFB44\\uFB46-\\uFBB1\\uFBD3-\\uFD3D\\uFD50-\\uFD8F\\uFD92-\\uFDC7\\uFDF0-\\uFDFB\\uFE70-\\uFE74\\uFE76-\\uFEFC\\uFF21-\\uFF3A\\uFF41-\\uFF5A\\uFF66-\\uFFBE\\uFFC2-\\uFFC7\\uFFCA-\\uFFCF\\uFFD2-\\uFFD7\\uFFDA-\\uFFDC]"), - digit: new RegExp("[\\u0030-\\u0039\\u0660-\\u0669\\u06F0-\\u06F9\\u07C0-\\u07C9\\u0966-\\u096F\\u09E6-\\u09EF\\u0A66-\\u0A6F\\u0AE6-\\u0AEF\\u0B66-\\u0B6F\\u0BE6-\\u0BEF\\u0C66-\\u0C6F\\u0CE6-\\u0CEF\\u0D66-\\u0D6F\\u0DE6-\\u0DEF\\u0E50-\\u0E59\\u0ED0-\\u0ED9\\u0F20-\\u0F29\\u1040-\\u1049\\u1090-\\u1099\\u17E0-\\u17E9\\u1810-\\u1819\\u1946-\\u194F\\u19D0-\\u19D9\\u1A80-\\u1A89\\u1A90-\\u1A99\\u1B50-\\u1B59\\u1BB0-\\u1BB9\\u1C40-\\u1C49\\u1C50-\\u1C59\\uA620-\\uA629\\uA8D0-\\uA8D9\\uA900-\\uA909\\uA9D0-\\uA9D9\\uA9F0-\\uA9F9\\uAA50-\\uAA59\\uABF0-\\uABF9\\uFF10-\\uFF19]"), - non_spacing_mark: new RegExp("[\\u0300-\\u036F\\u0483-\\u0487\\u0591-\\u05BD\\u05BF\\u05C1\\u05C2\\u05C4\\u05C5\\u05C7\\u0610-\\u061A\\u064B-\\u065E\\u0670\\u06D6-\\u06DC\\u06DF-\\u06E4\\u06E7\\u06E8\\u06EA-\\u06ED\\u0711\\u0730-\\u074A\\u07A6-\\u07B0\\u07EB-\\u07F3\\u0816-\\u0819\\u081B-\\u0823\\u0825-\\u0827\\u0829-\\u082D\\u0900-\\u0902\\u093C\\u0941-\\u0948\\u094D\\u0951-\\u0955\\u0962\\u0963\\u0981\\u09BC\\u09C1-\\u09C4\\u09CD\\u09E2\\u09E3\\u0A01\\u0A02\\u0A3C\\u0A41\\u0A42\\u0A47\\u0A48\\u0A4B-\\u0A4D\\u0A51\\u0A70\\u0A71\\u0A75\\u0A81\\u0A82\\u0ABC\\u0AC1-\\u0AC5\\u0AC7\\u0AC8\\u0ACD\\u0AE2\\u0AE3\\u0B01\\u0B3C\\u0B3F\\u0B41-\\u0B44\\u0B4D\\u0B56\\u0B62\\u0B63\\u0B82\\u0BC0\\u0BCD\\u0C3E-\\u0C40\\u0C46-\\u0C48\\u0C4A-\\u0C4D\\u0C55\\u0C56\\u0C62\\u0C63\\u0CBC\\u0CBF\\u0CC6\\u0CCC\\u0CCD\\u0CE2\\u0CE3\\u0D41-\\u0D44\\u0D4D\\u0D62\\u0D63\\u0DCA\\u0DD2-\\u0DD4\\u0DD6\\u0E31\\u0E34-\\u0E3A\\u0E47-\\u0E4E\\u0EB1\\u0EB4-\\u0EB9\\u0EBB\\u0EBC\\u0EC8-\\u0ECD\\u0F18\\u0F19\\u0F35\\u0F37\\u0F39\\u0F71-\\u0F7E\\u0F80-\\u0F84\\u0F86\\u0F87\\u0F90-\\u0F97\\u0F99-\\u0FBC\\u0FC6\\u102D-\\u1030\\u1032-\\u1037\\u1039\\u103A\\u103D\\u103E\\u1058\\u1059\\u105E-\\u1060\\u1071-\\u1074\\u1082\\u1085\\u1086\\u108D\\u109D\\u135F\\u1712-\\u1714\\u1732-\\u1734\\u1752\\u1753\\u1772\\u1773\\u17B7-\\u17BD\\u17C6\\u17C9-\\u17D3\\u17DD\\u180B-\\u180D\\u18A9\\u1920-\\u1922\\u1927\\u1928\\u1932\\u1939-\\u193B\\u1A17\\u1A18\\u1A56\\u1A58-\\u1A5E\\u1A60\\u1A62\\u1A65-\\u1A6C\\u1A73-\\u1A7C\\u1A7F\\u1B00-\\u1B03\\u1B34\\u1B36-\\u1B3A\\u1B3C\\u1B42\\u1B6B-\\u1B73\\u1B80\\u1B81\\u1BA2-\\u1BA5\\u1BA8\\u1BA9\\u1C2C-\\u1C33\\u1C36\\u1C37\\u1CD0-\\u1CD2\\u1CD4-\\u1CE0\\u1CE2-\\u1CE8\\u1CED\\u1DC0-\\u1DE6\\u1DFD-\\u1DFF\\u20D0-\\u20DC\\u20E1\\u20E5-\\u20F0\\u2CEF-\\u2CF1\\u2DE0-\\u2DFF\\u302A-\\u302F\\u3099\\u309A\\uA66F\\uA67C\\uA67D\\uA6F0\\uA6F1\\uA802\\uA806\\uA80B\\uA825\\uA826\\uA8C4\\uA8E0-\\uA8F1\\uA926-\\uA92D\\uA947-\\uA951\\uA980-\\uA982\\uA9B3\\uA9B6-\\uA9B9\\uA9BC\\uAA29-\\uAA2E\\uAA31\\uAA32\\uAA35\\uAA36\\uAA43\\uAA4C\\uAAB0\\uAAB2-\\uAAB4\\uAAB7\\uAAB8\\uAABE\\uAABF\\uAAC1\\uABE5\\uABE8\\uABED\\uFB1E\\uFE00-\\uFE0F\\uFE20-\\uFE26]"), - space_combining_mark: new RegExp("[\\u0903\\u093E-\\u0940\\u0949-\\u094C\\u094E\\u0982\\u0983\\u09BE-\\u09C0\\u09C7\\u09C8\\u09CB\\u09CC\\u09D7\\u0A03\\u0A3E-\\u0A40\\u0A83\\u0ABE-\\u0AC0\\u0AC9\\u0ACB\\u0ACC\\u0B02\\u0B03\\u0B3E\\u0B40\\u0B47\\u0B48\\u0B4B\\u0B4C\\u0B57\\u0BBE\\u0BBF\\u0BC1\\u0BC2\\u0BC6-\\u0BC8\\u0BCA-\\u0BCC\\u0BD7\\u0C01-\\u0C03\\u0C41-\\u0C44\\u0C82\\u0C83\\u0CBE\\u0CC0-\\u0CC4\\u0CC7\\u0CC8\\u0CCA\\u0CCB\\u0CD5\\u0CD6\\u0D02\\u0D03\\u0D3E-\\u0D40\\u0D46-\\u0D48\\u0D4A-\\u0D4C\\u0D57\\u0D82\\u0D83\\u0DCF-\\u0DD1\\u0DD8-\\u0DDF\\u0DF2\\u0DF3\\u0F3E\\u0F3F\\u0F7F\\u102B\\u102C\\u1031\\u1038\\u103B\\u103C\\u1056\\u1057\\u1062-\\u1064\\u1067-\\u106D\\u1083\\u1084\\u1087-\\u108C\\u108F\\u109A-\\u109C\\u17B6\\u17BE-\\u17C5\\u17C7\\u17C8\\u1923-\\u1926\\u1929-\\u192B\\u1930\\u1931\\u1933-\\u1938\\u19B0-\\u19C0\\u19C8\\u19C9\\u1A19-\\u1A1B\\u1A55\\u1A57\\u1A61\\u1A63\\u1A64\\u1A6D-\\u1A72\\u1B04\\u1B35\\u1B3B\\u1B3D-\\u1B41\\u1B43\\u1B44\\u1B82\\u1BA1\\u1BA6\\u1BA7\\u1BAA\\u1C24-\\u1C2B\\u1C34\\u1C35\\u1CE1\\u1CF2\\uA823\\uA824\\uA827\\uA880\\uA881\\uA8B4-\\uA8C3\\uA952\\uA953\\uA983\\uA9B4\\uA9B5\\uA9BA\\uA9BB\\uA9BD-\\uA9C0\\uAA2F\\uAA30\\uAA33\\uAA34\\uAA4D\\uAA7B\\uABE3\\uABE4\\uABE6\\uABE7\\uABE9\\uABEA\\uABEC]"), - connector_punctuation: new RegExp("[\\u005F\\u203F\\u2040\\u2054\\uFE33\\uFE34\\uFE4D-\\uFE4F\\uFF3F]") + ID_Start: /[A-Za-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0-\u08B4\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2118-\u211D\u2124\u2126\u2128\u212A-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309B-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FD5\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA7AD\uA7B0-\uA7B7\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB65\uAB70-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDD40-\uDD74\uDE80-\uDE9C\uDEA0-\uDED0\uDF00-\uDF1F\uDF30-\uDF4A\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF\uDFD1-\uDFD5]|\uD801[\uDC00-\uDC9D\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDCE0-\uDCF2\uDCF4\uDCF5\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE33\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE4\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2]|\uD804[\uDC03-\uDC37\uDC83-\uDCAF\uDCD0-\uDCE8\uDD03-\uDD26\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDDA\uDDDC\uDE00-\uDE11\uDE13-\uDE2B\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEDE\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF50\uDF5D-\uDF61]|\uD805[\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDD80-\uDDAE\uDDD8-\uDDDB\uDE00-\uDE2F\uDE44\uDE80-\uDEAA\uDF00-\uDF19]|\uD806[\uDCA0-\uDCDF\uDCFF\uDEC0-\uDEF8]|\uD808[\uDC00-\uDF99]|\uD809[\uDC00-\uDC6E\uDC80-\uDD43]|[\uD80C\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDED0-\uDEED\uDF00-\uDF2F\uDF40-\uDF43\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDF00-\uDF44\uDF50\uDF93-\uDF9F]|\uD82C[\uDC00\uDC01]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB]|\uD83A[\uDC00-\uDCC4]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1]|\uD87E[\uDC00-\uDE1D]/, + ID_Continue: /[0-9A-Z_a-z\xAA\xB5\xB7\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0300-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u0483-\u0487\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u05D0-\u05EA\u05F0-\u05F2\u0610-\u061A\u0620-\u0669\u066E-\u06D3\u06D5-\u06DC\u06DF-\u06E8\u06EA-\u06FC\u06FF\u0710-\u074A\u074D-\u07B1\u07C0-\u07F5\u07FA\u0800-\u082D\u0840-\u085B\u08A0-\u08B4\u08E3-\u0963\u0966-\u096F\u0971-\u0983\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BC-\u09C4\u09C7\u09C8\u09CB-\u09CE\u09D7\u09DC\u09DD\u09DF-\u09E3\u09E6-\u09F1\u0A01-\u0A03\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A59-\u0A5C\u0A5E\u0A66-\u0A75\u0A81-\u0A83\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABC-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AD0\u0AE0-\u0AE3\u0AE6-\u0AEF\u0AF9\u0B01-\u0B03\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3C-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B5C\u0B5D\u0B5F-\u0B63\u0B66-\u0B6F\u0B71\u0B82\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD0\u0BD7\u0BE6-\u0BEF\u0C00-\u0C03\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C58-\u0C5A\u0C60-\u0C63\u0C66-\u0C6F\u0C81-\u0C83\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBC-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CDE\u0CE0-\u0CE3\u0CE6-\u0CEF\u0CF1\u0CF2\u0D01-\u0D03\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D-\u0D44\u0D46-\u0D48\u0D4A-\u0D4E\u0D57\u0D5F-\u0D63\u0D66-\u0D6F\u0D7A-\u0D7F\u0D82\u0D83\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DE6-\u0DEF\u0DF2\u0DF3\u0E01-\u0E3A\u0E40-\u0E4E\u0E50-\u0E59\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB9\u0EBB-\u0EBD\u0EC0-\u0EC4\u0EC6\u0EC8-\u0ECD\u0ED0-\u0ED9\u0EDC-\u0EDF\u0F00\u0F18\u0F19\u0F20-\u0F29\u0F35\u0F37\u0F39\u0F3E-\u0F47\u0F49-\u0F6C\u0F71-\u0F84\u0F86-\u0F97\u0F99-\u0FBC\u0FC6\u1000-\u1049\u1050-\u109D\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u135D-\u135F\u1369-\u1371\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1714\u1720-\u1734\u1740-\u1753\u1760-\u176C\u176E-\u1770\u1772\u1773\u1780-\u17D3\u17D7\u17DC\u17DD\u17E0-\u17E9\u180B-\u180D\u1810-\u1819\u1820-\u1877\u1880-\u18AA\u18B0-\u18F5\u1900-\u191E\u1920-\u192B\u1930-\u193B\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19DA\u1A00-\u1A1B\u1A20-\u1A5E\u1A60-\u1A7C\u1A7F-\u1A89\u1A90-\u1A99\u1AA7\u1AB0-\u1ABD\u1B00-\u1B4B\u1B50-\u1B59\u1B6B-\u1B73\u1B80-\u1BF3\u1C00-\u1C37\u1C40-\u1C49\u1C4D-\u1C7D\u1CD0-\u1CD2\u1CD4-\u1CF6\u1CF8\u1CF9\u1D00-\u1DF5\u1DFC-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u203F\u2040\u2054\u2071\u207F\u2090-\u209C\u20D0-\u20DC\u20E1\u20E5-\u20F0\u2102\u2107\u210A-\u2113\u2115\u2118-\u211D\u2124\u2126\u2128\u212A-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D7F-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2DE0-\u2DFF\u3005-\u3007\u3021-\u302F\u3031-\u3035\u3038-\u303C\u3041-\u3096\u3099-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FD5\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA62B\uA640-\uA66F\uA674-\uA67D\uA67F-\uA6F1\uA717-\uA71F\uA722-\uA788\uA78B-\uA7AD\uA7B0-\uA7B7\uA7F7-\uA827\uA840-\uA873\uA880-\uA8C4\uA8D0-\uA8D9\uA8E0-\uA8F7\uA8FB\uA8FD\uA900-\uA92D\uA930-\uA953\uA960-\uA97C\uA980-\uA9C0\uA9CF-\uA9D9\uA9E0-\uA9FE\uAA00-\uAA36\uAA40-\uAA4D\uAA50-\uAA59\uAA60-\uAA76\uAA7A-\uAAC2\uAADB-\uAADD\uAAE0-\uAAEF\uAAF2-\uAAF6\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB65\uAB70-\uABEA\uABEC\uABED\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE00-\uFE0F\uFE20-\uFE2F\uFE33\uFE34\uFE4D-\uFE4F\uFE70-\uFE74\uFE76-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF3F\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDD40-\uDD74\uDDFD\uDE80-\uDE9C\uDEA0-\uDED0\uDEE0\uDF00-\uDF1F\uDF30-\uDF4A\uDF50-\uDF7A\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF\uDFD1-\uDFD5]|\uD801[\uDC00-\uDC9D\uDCA0-\uDCA9\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDCE0-\uDCF2\uDCF4\uDCF5\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00-\uDE03\uDE05\uDE06\uDE0C-\uDE13\uDE15-\uDE17\uDE19-\uDE33\uDE38-\uDE3A\uDE3F\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE6\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2]|\uD804[\uDC00-\uDC46\uDC66-\uDC6F\uDC7F-\uDCBA\uDCD0-\uDCE8\uDCF0-\uDCF9\uDD00-\uDD34\uDD36-\uDD3F\uDD50-\uDD73\uDD76\uDD80-\uDDC4\uDDCA-\uDDCC\uDDD0-\uDDDA\uDDDC\uDE00-\uDE11\uDE13-\uDE37\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEEA\uDEF0-\uDEF9\uDF00-\uDF03\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3C-\uDF44\uDF47\uDF48\uDF4B-\uDF4D\uDF50\uDF57\uDF5D-\uDF63\uDF66-\uDF6C\uDF70-\uDF74]|\uD805[\uDC80-\uDCC5\uDCC7\uDCD0-\uDCD9\uDD80-\uDDB5\uDDB8-\uDDC0\uDDD8-\uDDDD\uDE00-\uDE40\uDE44\uDE50-\uDE59\uDE80-\uDEB7\uDEC0-\uDEC9\uDF00-\uDF19\uDF1D-\uDF2B\uDF30-\uDF39]|\uD806[\uDCA0-\uDCE9\uDCFF\uDEC0-\uDEF8]|\uD808[\uDC00-\uDF99]|\uD809[\uDC00-\uDC6E\uDC80-\uDD43]|[\uD80C\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD811[\uDC00-\uDE46]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDE60-\uDE69\uDED0-\uDEED\uDEF0-\uDEF4\uDF00-\uDF36\uDF40-\uDF43\uDF50-\uDF59\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDF00-\uDF44\uDF50-\uDF7E\uDF8F-\uDF9F]|\uD82C[\uDC00\uDC01]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99\uDC9D\uDC9E]|\uD834[\uDD65-\uDD69\uDD6D-\uDD72\uDD7B-\uDD82\uDD85-\uDD8B\uDDAA-\uDDAD\uDE42-\uDE44]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB\uDFCE-\uDFFF]|\uD836[\uDE00-\uDE36\uDE3B-\uDE6C\uDE75\uDE84\uDE9B-\uDE9F\uDEA1-\uDEAF]|\uD83A[\uDC00-\uDCC4\uDCD0-\uDCD6]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1]|\uD87E[\uDC00-\uDE1D]|\uDB40[\uDD00-\uDDEF]/, }; -function is_letter(code) { - return (code >= 97 && code <= 122) - || (code >= 65 && code <= 90) - || (code >= 0xaa && UNICODE.letter.test(String.fromCharCode(code))); -}; +function get_full_char(str, pos) { + var char = str.charAt(pos); + if (char >= "\ud800" && char <= "\udbff") { + return char + str.charAt(pos + 1); + } + return char; +} + +function get_full_char_code(str, pos) { + // https://en.wikipedia.org/wiki/Universal_Character_Set_characters#Surrogates + if (is_surrogate_pair_head(str.charAt(pos))) { + return 0x10000 + (str.charCodeAt(pos) - 0xd800 << 10) + str.charCodeAt(pos + 1) - 0xdc00; + } + return str.charCodeAt(pos); +} + +function get_full_char_length(str) { + var surrogates = 0; + + for (var i = 0; i < str.length; i++) { + if (str.charCodeAt(i) >= 0xd800 && str.charCodeAt(i) <= 0xdbff) { + if (str.charCodeAt(i + 1) >= 0xdc00 && str.charCodeAt(i + 1) <= 0xdfff) { + surrogates++; + i++; + } + } + } + + return str.length - surrogates; +} + +function from_char_code(code) { + // Based on https://github.com/mathiasbynens/String.fromCodePoint/blob/master/fromcodepoint.js + if (code > 0xFFFF) { + code -= 0x10000; + return (String.fromCharCode((code >> 10) + 0xD800) + + String.fromCharCode((code % 0x400) + 0xDC00)); + } + return String.fromCharCode(code); +} + +function is_surrogate_pair_head(code) { + if (typeof code === "string") + code = code.charCodeAt(0); + + return code >= 0xd800 && code <= 0xdbff; +} + +function is_surrogate_pair_tail(code) { + if (typeof code === "string") + code = code.charCodeAt(0); + return code >= 0xdc00 && code <= 0xdfff; +} function is_digit(code) { return code >= 48 && code <= 57; }; -function is_alphanumeric_char(code) { - return is_digit(code) || is_letter(code); -}; - -function is_unicode_digit(code) { - return UNICODE.digit.test(String.fromCharCode(code)); -} - -function is_unicode_combining_mark(ch) { - return UNICODE.non_spacing_mark.test(ch) || UNICODE.space_combining_mark.test(ch); -}; - -function is_unicode_connector_punctuation(ch) { - return UNICODE.connector_punctuation.test(ch); -}; - function is_identifier(name) { - return !RESERVED_WORDS(name) && /^[a-z_$][a-z0-9_$]*$/i.test(name); + if (typeof name !== "string" || RESERVED_WORDS(name)) + return false; + + return true; }; -function is_identifier_start(code) { - return code == 36 || code == 95 || is_letter(code); +function is_identifier_start(ch) { + var code = ch.charCodeAt(0); + return UNICODE.ID_Start.test(ch) || code == 36 || code == 95; }; function is_identifier_char(ch) { var code = ch.charCodeAt(0); - return is_identifier_start(code) - || is_digit(code) + return UNICODE.ID_Continue.test(ch) + || code == 36 + || code == 95 || code == 8204 // \u200c: zero-width non-joiner || code == 8205 // \u200d: zero-width joiner (in my ECMA-262 PDF, this is also 200c) - || is_unicode_combining_mark(ch) - || is_unicode_connector_punctuation(ch) - || is_unicode_digit(code) ; }; @@ -183,6 +221,12 @@ function parse_js_number(num) { return parseInt(num.substr(2), 16); } else if (RE_OCT_NUMBER.test(num)) { return parseInt(num.substr(1), 8); + } else if (RE_ES6_OCT_NUMBER.test(num)) { + return parseInt(num.substr(2), 8); + } else if (RE_BIN_NUMBER.test(num)) { + return parseInt(num.substr(2), 2); + } else if (RE_DEC_NUMBER.test(num)) { + return parseFloat(num); } else { var val = parseFloat(num); if (val == num) return val; @@ -224,15 +268,17 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { tokcol : 0, newline_before : false, regex_allowed : false, + brace_counter : 0, + template_braces : [], comments_before : [], directives : {}, directive_stack : [] }; - function peek() { return S.text.charAt(S.pos); }; + function peek() { return get_full_char(S.text, S.pos); }; function next(signal_eof, in_string) { - var ch = S.text.charAt(S.pos++); + var ch = get_full_char(S.text, S.pos++); if (signal_eof && !ch) throw EX_EOF; if (NEWLINE_CHARS(ch)) { @@ -245,6 +291,10 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { ch = "\n"; } } else { + if (is_surrogate_pair_head(ch)) { + ++S.pos; + ++S.col; + } ++S.col; } return ch; @@ -284,7 +334,8 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { function token(type, value, is_comment) { S.regex_allowed = ((type == "operator" && !UNARY_POSTFIX(value)) || (type == "keyword" && KEYWORDS_BEFORE_EXPRESSION(value)) || - (type == "punc" && PUNC_BEFORE_EXPRESSION(value))); + (type == "punc" && PUNC_BEFORE_EXPRESSION(value))) || + (type == "arrow"); prev_was_dot = (type == "punc" && value == "."); var ret = { type : type, @@ -334,6 +385,9 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { var num = read_while(function(ch, i){ var code = ch.charCodeAt(0); switch (code) { + case 98: case 66: // bB + return (has_x = true); // Can occur in hex sequence, don't return false yet + case 111: case 79: // oO case 120: case 88: // xX return has_x ? false : (has_x = true); case 101: case 69: // eE @@ -345,7 +399,7 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { case (after_e = false, 46): // . return (!has_dot && !has_x && !has_e) ? (has_dot = true) : false; } - return is_alphanumeric_char(code); + return RE_NUM_LITERAL.test(ch); }); if (prefix) num = prefix + num; if (RE_OCT_NUMBER.test(num) && next_token.has_directive("use strict")) { @@ -369,7 +423,22 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { case 118 : return "\u000b"; // \v case 102 : return "\f"; case 120 : return String.fromCharCode(hex_bytes(2)); // \x - case 117 : return String.fromCharCode(hex_bytes(4)); // \u + case 117 : // \u + if (peek() == "{") { + next(true); + if (peek() === "}") + parse_error("Expecting hex-character between {}"); + while (peek() == "0") next(true); // No significance + var result, length = find("}", true) - S.pos; + // Avoid 32 bit integer overflow (1 << 32 === 1) + // We know first character isn't 0 and thus out of range anyway + if (length > 6 || (result = hex_bytes(length)) > 0x10FFFF) { + parse_error("Unicode reference out of bounce"); + } + next(true); + return from_char_code(result); + } + return String.fromCharCode(hex_bytes(4)); case 10 : return ""; // newline case 13 : // \r if (peek() == "\n") { // DOS newline @@ -423,6 +492,40 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { return tok; }); + var read_template_characters = with_eof_error("Unterminated template", function(begin){ + if (begin) { + S.template_braces.push(S.brace_counter); + } + var content = "", raw = "", ch, tok; + next(true, true); + while ((ch = next(true, true)) !== "`") { + if (ch === "$" && peek() === "{") { + next(true, true); + S.brace_counter++; + tok = token(begin ? "template_head" : "template_substitution", content); + tok.begin = begin; + tok.raw = raw; + tok.end = false; + return tok; + } + + raw += ch; + if (ch === "\\") { + var tmp = S.pos; + ch = read_escaped_char(); + raw += S.text.substr(tmp, S.pos - tmp); + } + + content += ch; + } + S.template_braces.pop(); + tok = token(begin ? "template_head" : "template_substitution", content); + tok.begin = begin; + tok.raw = raw; + tok.end = true; + return tok; + }); + function skip_line_comment(type) { var regex_allowed = S.regex_allowed; var i = find_eol(), ret; @@ -444,34 +547,56 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { var i = find("*/", true); var text = S.text.substring(S.pos, i).replace(/\r\n|\r|\u2028|\u2029/g, '\n'); // update stream position - forward(text.length /* doesn't count \r\n as 2 char while S.pos - i does */ + 2); + forward(get_full_char_length(text) /* text length doesn't count \r\n as 2 char while S.pos - i does */ + 2); S.comments_before.push(token("comment2", text, true)); + S.newline_before = S.newline_before || text.indexOf("\n") >= 0; S.regex_allowed = regex_allowed; return next_token; }); - function read_name() { - var backslash = false, name = "", ch, escaped = false, hex; - while ((ch = peek()) != null) { - if (!backslash) { - if (ch == "\\") escaped = backslash = true, next(); - else if (is_identifier_char(ch)) name += next(); - else break; - } - else { - if (ch != "u") parse_error("Expecting UnicodeEscapeSequence -- uXXXX"); - ch = read_escaped_char(); - if (!is_identifier_char(ch)) parse_error("Unicode char: " + ch.charCodeAt(0) + " is not valid in identifier"); - name += ch; - backslash = false; + var read_name = with_eof_error("Unterminated identifier name", function() { + var name = "", ch, escaped = false, hex; + var read_escaped_identifier_char = function() { + escaped = true; + next(); + if (peek() !== "u") { + parse_error("Expecting UnicodeEscapeSequence -- uXXXX or u{XXXX}"); } + return read_escaped_char(); } - if (KEYWORDS(name) && escaped) { - hex = name.charCodeAt(0).toString(16).toUpperCase(); - name = "\\u" + "0000".substr(hex.length) + hex + name.slice(1); + + // Read first character (ID_Start) + if ((name = peek()) === "\\") { + name = read_escaped_identifier_char(); + if (!is_identifier_start(name)) { + parse_error("First identifier char is an invalid identifier char"); + } + } else if (is_identifier_start(name)){ + next(); + } else { + return ""; + } + + // Read ID_Continue + while ((ch = peek()) != null) { + if ((ch = peek()) === "\\") { + ch = read_escaped_identifier_char(); + if (!is_identifier_char(ch)) { + parse_error("Invalid escaped identifier char"); + } + } else { + if (!is_identifier_char(ch)) { + break; + } + next(); + } + name += ch; + } + if (RESERVED_WORDS(name) && escaped) { + parse_error("Escaped characters are not allowed in keywords"); } return name; - }; + }); var read_regexp = with_eof_error("Unterminated regular expression", function(regexp){ var prev_backslash = false, ch, in_class = false; @@ -528,11 +653,28 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { return S.regex_allowed ? read_regexp("") : read_operator("/"); }; + function handle_eq_sign() { + next(); + if (peek() === ">") { + next(); + return token("arrow", "=>"); + } else { + return read_operator("="); + } + }; + function handle_dot() { next(); - return is_digit(peek().charCodeAt(0)) - ? read_num(".") - : token("punc", "."); + if (is_digit(peek().charCodeAt(0))) { + return read_num("."); + } + if (peek() === ".") { + next(); // Consume second dot + next(); // Consume third dot + return token("expand", "..."); + } + + return token("punc", "."); }; function read_word() { @@ -584,11 +726,22 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { if (tok === next_token) continue; return tok; } + case 61: return handle_eq_sign(); + case 96: return read_template_characters(true); + case 123: + S.brace_counter++; + break; + case 125: + S.brace_counter--; + if (S.template_braces.length > 0 + && S.template_braces[S.template_braces.length - 1] === S.brace_counter) + return read_template_characters(false); + break; } if (is_digit(code)) return read_num(); if (PUNC_CHARS(ch)) return token("punc", next()); if (OPERATOR_CHARS(ch)) return read_operator(); - if (code == 92 || is_identifier_start(code)) return read_word(); + if (code == 92 || is_identifier_start(ch)) return read_word(); if (shebang) { if (S.pos == 0 && looking_at("#!")) { forward(2); @@ -601,6 +754,9 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { parse_error("Unexpected character '" + ch + "'"); }; + next_token.next = next; + next_token.peek = peek; + next_token.context = function(nc) { if (nc) S = nc; return S; @@ -655,7 +811,7 @@ var UNARY_PREFIX = makePredicate([ var UNARY_POSTFIX = makePredicate([ "--", "++" ]); -var ASSIGNMENT = makePredicate([ "=", "+=", "-=", "/=", "*=", "%=", ">>=", "<<=", ">>>=", "|=", "^=", "&=" ]); +var ASSIGNMENT = makePredicate([ "=", "+=", "-=", "/=", "*=", "**=", "%=", ">>=", "<<=", ">>>=", "|=", "^=", "&=" ]); var PRECEDENCE = (function(a, ret){ for (var i = 0; i < a.length; ++i) { @@ -676,7 +832,8 @@ var PRECEDENCE = (function(a, ret){ ["<", ">", "<=", ">=", "in", "instanceof"], [">>", "<<", ">>>"], ["+", "-"], - ["*", "/", "%"] + ["*", "/", "%"], + ["**"] ], {} ); @@ -708,6 +865,7 @@ function parse($TEXT, options) { prev : null, peeked : null, in_function : 0, + in_generator : -1, in_directives : true, in_loop : 0, labels : [] @@ -773,6 +931,10 @@ function parse($TEXT, options) { ); }; + function is_in_generator() { + return S.in_generator === S.in_function; + } + function semicolon(optional) { if (is("punc", ";")) next(); else if (!optional && !can_insert_semicolon()) unexpected(); @@ -788,7 +950,7 @@ function parse($TEXT, options) { function embed_tokens(parser) { return function() { var start = S.token; - var expr = parser(); + var expr = parser.apply(null, arguments); var end = prev(); expr.start = start; expr.end = end; @@ -826,6 +988,7 @@ function parse($TEXT, options) { }); } return stat; + case "template_head": case "num": case "regexp": case "operator": @@ -883,6 +1046,9 @@ function parse($TEXT, options) { case "for": return for_(); + case "class": + return class_(AST_DefClass); + case "function": return function_(AST_Defun); @@ -919,6 +1085,9 @@ function parse($TEXT, options) { case "var": return tmp = var_(), semicolon(), tmp; + case "let": + return tmp = let_(), semicolon(), tmp; + case "const": return tmp = const_(), semicolon(), tmp; @@ -931,14 +1100,22 @@ function parse($TEXT, options) { body : statement() }); - default: - unexpected(); + case "import": + return tmp = import_(), semicolon(), tmp; + + case "export": + return export_(); } } + unexpected(); }); function labeled_statement() { var label = as_symbol(AST_Label); + if (label.name === "yield" && is_in_generator()) { + // Ecma-262, 12.1.1 Static Semantics: Early Errors + token_error(S.prev, "Yield cannot be used as label inside generators"); + } if (find_if(function(l){ return l.name == label.name }, S.labels)) { // ECMA-262, 12.12: An ECMAScript program is considered // syntactically incorrect if it contains a @@ -992,14 +1169,23 @@ function parse($TEXT, options) { expect("("); var init = null; if (!is("punc", ";")) { - init = is("keyword", "var") - ? (next(), var_(true)) - : expression(true, true); - if (is("operator", "in")) { - if (init instanceof AST_Var && init.definitions.length > 1) + init = + is("keyword", "var") ? (next(), var_(true)) : + is("keyword", "let") ? (next(), let_(true)) : + is("keyword", "const") ? (next(), const_(true)) : + expression(true, true); + var is_in = is("operator", "in"); + var is_of = is("name", "of"); + if (is_in || is_of) { + if ((init instanceof AST_Definitions) && + init.definitions.length > 1) croak("Only one variable declaration allowed in for..in loop"); next(); - return for_in(init); + if (is_in) { + return for_in(init); + } else { + return for_of(init); + } } } return regular_for(init); @@ -1019,8 +1205,20 @@ function parse($TEXT, options) { }); }; + function for_of(init) { + var lhs = init instanceof AST_Definitions ? init.definitions[0].name : null; + var obj = expression(true); + expect(")"); + return new AST_ForOf({ + init : init, + name : lhs, + object : obj, + body : in_loop(statement) + }); + }; + function for_in(init) { - var lhs = init instanceof AST_Var ? init.definitions[0].name : null; + var lhs = init instanceof AST_Definitions ? init.definitions[0].name : null; var obj = expression(true); expect(")"); return new AST_ForIn({ @@ -1031,38 +1229,439 @@ function parse($TEXT, options) { }); }; - var function_ = function(ctor) { + var arrow_function = function(args) { + if (S.token.nlb) { + croak("Unexpected newline before arrow (=>)"); + } + + expect_token("arrow", "=>"); + + var argnames; + if (typeof args.length === 'number') { + argnames = args; + } else { + argnames = args.as_params(croak); + } + + var body = is("punc", "{") ? + _function_body(true) : + _function_body(false); + + return new AST_Arrow({ + start : args.start, + end : body.end, + argnames : argnames, + body : body + }); + }; + + var function_ = function(ctor, is_generator_property) { + var start = S.token + var in_statement = ctor === AST_Defun; + var is_generator = is("operator", "*"); + if (is_generator) { + next(); + } + var name = is("name") ? as_symbol(in_statement ? AST_SymbolDefun : AST_SymbolLambda) : null; if (in_statement && !name) unexpected(); - expect("("); + + var args = parameters(); + var body = _function_body(true, is_generator || is_generator_property); return new ctor({ - name: name, - argnames: (function(first, a){ - while (!is("punc", ")")) { - if (first) first = false; else expect(","); - a.push(as_symbol(AST_SymbolFunarg)); - } - next(); - return a; - })(true, []), - body: (function(loop, labels){ - ++S.in_function; - S.in_directives = true; - S.input.push_directives_stack(); - S.in_loop = 0; - S.labels = []; - var a = block_(); - S.input.pop_directives_stack(); - --S.in_function; - S.in_loop = loop; - S.labels = labels; - return a; - })(S.in_loop, S.labels) + start : args.start, + end : body.end, + is_generator: is_generator, + name : name, + argnames: args, + body : body }); }; + function track_used_binding_identifiers(is_parameter, strict) { + var parameters = {}; + var duplicate = false; + var default_assignment = false; + var spread = false; + var strict_mode = !!strict; + var tracker = { + add_parameter: function(token) { + if (parameters["$" + token.value] !== undefined) { + if (duplicate === false) { + duplicate = token; + } + tracker.check_strict(); + } else { + parameters["$" + token.value] = true; + if (is_parameter) { + switch (token.value) { + case "arguments": + case "eval": + case "yield": + if (strict_mode) { + token_error(token, "Unexpected " + token.value + " identifier as parameter inside strict mode"); + } + break; + default: + if (RESERVED_WORDS(token.value)) { + unexpected(); + } + } + } + } + }, + mark_default_assignment: function(token) { + if (default_assignment === false) { + default_assignment = token; + } + }, + mark_spread: function(token) { + if (spread === false) { + spread = token; + } + }, + mark_strict_mode: function() { + strict_mode = true; + }, + is_strict: function() { + return default_assignment !== false || spread !== false || strict_mode + }, + check_strict: function() { + if (tracker.is_strict() && duplicate !== false) { + token_error(duplicate, "Parameter " + duplicate.value + " was used already"); + } + } + }; + + return tracker; + } + + function parameters() { + var start = S.token; + var first = true; + var params = []; + var used_parameters = track_used_binding_identifiers(true, S.input.has_directive("use strict")); + + expect("("); + + while (!is("punc", ")")) { + if (first) { + first = false; + } else { + expect(","); + } + + var param = parameter(used_parameters); + params.push(param); + + if (param instanceof AST_Expansion) { + break; + } + } + + next(); + return params; + } + + function parameter(used_parameters, symbol_type) { + var param; + var expand = false; + if (used_parameters === undefined) { + used_parameters = track_used_binding_identifiers(true, S.input.has_directive("use strict")); + } + if (is("expand", "...")) { + expand = S.token; + used_parameters.mark_spread(S.token); + next(); + } + param = binding_element(used_parameters, symbol_type); + + if (is("operator", "=") && expand === false) { + used_parameters.mark_default_assignment(S.token); + next(); + param = new AST_DefaultAssign({ + start: param.start, + left: param, + operator: "=", + right: expression(false), + end: S.token + }); + } + + if (expand !== false) { + if (!is("punc", ")")) { + unexpected(); + } + param = new AST_Expansion({ + start: expand, + expression: param, + end: expand + }); + } + used_parameters.check_strict(); + + return param; + } + + function binding_element(used_parameters, symbol_type) { + var elements = []; + var first = true; + var is_expand = false; + var expand_token; + var first_token = S.token; + if (used_parameters === undefined) { + used_parameters = track_used_binding_identifiers(false, S.input.has_directive("use strict")); + } + symbol_type = symbol_type === undefined ? AST_SymbolFunarg : symbol_type; + if (is("punc", "[")) { + next(); + while (!is("punc", "]")) { + if (first) { + first = false; + } else { + expect(","); + } + + if (is("expand", "...")) { + is_expand = true; + expand_token = S.token; + used_parameters.mark_spread(S.token); + next(); + } + if (is("punc")) { + switch (S.token.value) { + case ",": + elements.push(new AST_Hole({ + start: S.token, + end: S.token + })); + continue; + case "]": // Trailing comma after last element + break; + case "[": + case "{": + elements.push(binding_element(used_parameters, symbol_type)); + break; + default: + unexpected(); + } + } else if (is("name")) { + used_parameters.add_parameter(S.token); + elements.push(new symbol_type({ + start: S.token, + name: S.token.value, + end: S.token + })); + next(); + } else { + croak("Invalid function parameter"); + } + if (is("operator", "=") && is_expand === false) { + used_parameters.mark_default_assignment(S.token); + next(); + elements[elements.length - 1] = new AST_DefaultAssign({ + start: elements[elements.length - 1].start, + left: elements[elements.length - 1], + operator: "=", + right: expression(false), + end: S.token + }); + } + if (is_expand) { + if (!is("punc", "]")) { + unexpected(); // Must be last element + } + elements[elements.length - 1] = new AST_Expansion({ + start: expand_token, + expression: elements[elements.length - 1], + end: expand_token + }); + } + } + expect("]"); + used_parameters.check_strict(); + return new AST_Destructuring({ + start: first_token, + names: elements, + is_array: true, + end: prev() + }); + } else if (is("punc", "{")) { + next(); + while (!is("punc", "}")) { + if (first) { + first = false; + } else { + expect(","); + } + if (is("name") && (is_token(peek(), "punc") || is_token(peek(), "operator")) && [",", "}", "="].indexOf(peek().value) !== -1) { + used_parameters.add_parameter(S.token); + elements.push(new AST_ObjectKeyVal({ + start: prev(), + key: S.token.value, + value: new symbol_type({ + start: S.token, + name: S.token.value, + end: S.token + }), + end: prev() + })); + next(); + } else if (is("punc", "}")) { + continue; // Allow trailing hole + } else { + var property_token = S.token; + var property = as_property_name(); + if (property === null) { + unexpected(prev()); + } else if (prev().type === "name" && !is("punc", ":")) { + elements.push(new AST_ObjectKeyVal({ + start: prev(), + key: property, + value: new symbol_type({ + start: prev(), + name: property, + end: prev() + }), + end: prev() + })); + } else { + expect(":"); + elements.push(new AST_ObjectKeyVal({ + start: property_token, + quote: property_token.quote, + key: property, + value: binding_element(used_parameters, symbol_type), + end: prev() + })); + } + } + if (is("operator", "=")) { + used_parameters.mark_default_assignment(S.token); + next(); + elements[elements.length - 1].value = new AST_DefaultAssign({ + start: elements[elements.length - 1].value.start, + left: elements[elements.length - 1].value, + operator: "=", + right: expression(false), + end: S.token + }); + } + } + expect("}"); + used_parameters.check_strict(); + return new AST_Destructuring({ + start: first_token, + names: elements, + is_array: false, + end: prev() + }); + } else if (is("name")) { + used_parameters.add_parameter(S.token); + next(); + return new symbol_type({ + start: prev(), + name: prev().value, + end: prev() + }); + } else { + croak("Invalid function parameter"); + } + } + + function params_or_seq_() { + var start = S.token + expect("("); + var first = true; + var a = []; + while (!is("punc", ")")) { + if (first) first = false; else expect(","); + if (is("expand", "...")) { + var spread_token = S.token; + next(); + a.push(new AST_Expansion({ + start: prev(), + expression: expression(false), + end: S.token, + })); + if (!is("punc", ")")) { + unexpected(spread_token); + } + } else { + a.push(expression(false)); + } + } + var end = S.token + next(); + return new AST_ArrowParametersOrSeq({ + start: start, + end: end, + expressions: a + }); + } + + function _function_body(block, generator) { + var loop = S.in_loop; + var labels = S.labels; + var current_generator = S.in_generator; + ++S.in_function; + if (generator) + S.in_generator = S.in_function; + if (block) + S.in_directives = true; + S.in_loop = 0; + S.labels = []; + if (block) { + S.input.push_directives_stack(); + var a = block_(); + S.input.pop_directives_stack(); + } else { + var a = expression(false); + } + --S.in_function; + S.in_loop = loop; + S.labels = labels; + S.in_generator = current_generator; + return a; + } + + function _yield_expression() { + // Previous token must be keyword yield and not be interpret as an identifier + if (!is_in_generator()) { + croak("Unexpected yield expression outside generator function", + S.prev.line, S.prev.col, S.prev.pos); + } + var star = false; + var has_expression = true; + var tmp; + + // Attempt to get expression or star (and then the mandatory expression) + // behind yield on the same line. + // + // If nothing follows on the same line of the yieldExpression, + // it should default to the value `undefined` for yield to return. + // In that case, the `undefined` stored as `null` in ast. + // + // Note 1: It isn't allowed for yield* to close without an expression + // Note 2: If there is a nlb between yield and star, it is interpret as + // yield * + if (can_insert_semicolon() || + (is("punc") && PUNC_AFTER_EXPRESSION(S.token.value))) { + has_expression = false; + + } else if (is("operator", "*")) { + star = true; + next(); + } + + return new AST_Yield({ + is_star : star, + expression : has_expression ? expression() : null + }); + } + function if_() { var cond = parenthesised(), body = statement(), belse = null; if (is("keyword", "else")) { @@ -1128,7 +1727,7 @@ function parse($TEXT, options) { var start = S.token; next(); expect("("); - var name = as_symbol(AST_SymbolCatch); + var name = parameter(undefined, AST_SymbolCatch); expect(")"); bcatch = new AST_Catch({ start : start, @@ -1155,15 +1754,30 @@ function parse($TEXT, options) { }); }; - function vardefs(no_in, in_const) { + function vardefs(no_in, kind) { var a = []; + var def; for (;;) { - a.push(new AST_VarDef({ - start : S.token, - name : as_symbol(in_const ? AST_SymbolConst : AST_SymbolVar), - value : is("operator", "=") ? (next(), expression(false, no_in)) : null, - end : prev() - })); + var sym_type = + kind === "var" ? AST_SymbolVar : + kind === "const" ? AST_SymbolConst : + kind === "let" ? AST_SymbolLet : null; + if (is("punc", "{") || is("punc", "[")) { + def = new AST_VarDef({ + start: S.token, + name: binding_element(undefined ,sym_type), + value: is("operator", "=") ? (expect_token("operator", "="), expression(false, no_in)) : null, + end: prev() + }); + } else { + def = new AST_VarDef({ + start : S.token, + name : as_symbol(sym_type), + value : is("operator", "=") ? (next(), expression(false, no_in)) : null, + end : prev() + }) + } + a.push(def); if (!is("punc", ",")) break; next(); @@ -1174,7 +1788,15 @@ function parse($TEXT, options) { var var_ = function(no_in) { return new AST_Var({ start : prev(), - definitions : vardefs(no_in, false), + definitions : vardefs(no_in, "var"), + end : prev() + }); + }; + + var let_ = function(no_in) { + return new AST_Let({ + start : prev(), + definitions : vardefs(no_in, "let"), end : prev() }); }; @@ -1182,7 +1804,7 @@ function parse($TEXT, options) { var const_ = function() { return new AST_Const({ start : prev(), - definitions : vardefs(false, true), + definitions : vardefs(false, "const"), end : prev() }); }; @@ -1190,6 +1812,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", "target"); + return subscripts(new AST_NewTarget({ + start : start, + end : prev() + }), allow_calls); + } var newexp = expr_atom(false), args; if (is("punc", "(")) { next(); @@ -1259,16 +1889,17 @@ function parse($TEXT, options) { if (is("punc")) { switch (start.value) { case "(": - next(); - var ex = expression(true); + var ex = params_or_seq_(); ex.start = start; ex.end = S.token; - expect(")"); - return subscripts(ex, allow_calls); + if (is("arrow", "=>")) { + return arrow_function(ex); + } + return subscripts(ex.as_expr(croak), allow_calls); case "[": return subscripts(array_(), allow_calls); case "{": - return subscripts(object_(), allow_calls); + return subscripts(object_or_object_destructuring_(), allow_calls); } unexpected(); } @@ -1279,12 +1910,55 @@ function parse($TEXT, options) { func.end = prev(); return subscripts(func, allow_calls); } + if (is("keyword", "class")) { + next(); + var cls = class_(AST_ClassExpression); + cls.start = start; + cls.end = prev(); + return subscripts(cls, allow_calls); + } + if (is("template_head")) { + return subscripts(template_string(), allow_calls); + } if (ATOMIC_START_TOKEN[S.token.type]) { return subscripts(as_atom_node(), allow_calls); } unexpected(); }; + function template_string() { + var segments = [], start = S.token; + + segments.push(new AST_TemplateSegment({ + start: S.token, + raw: S.token.raw, + value: S.token.value, + end: S.token + })); + while (S.token.end === false) { + next(); + segments.push(expression()); + + if (!is_token("template_substitution")) { + unexpected(); + } + + segments.push(new AST_TemplateSegment({ + start: S.token, + raw: S.token.raw, + value: S.token.value, + end: S.token + })); + } + next(); + + return new AST_TemplateString({ + start: start, + segments: segments, + end: S.token + }); + } + function expr_list(closing, allow_trailing_comma, allow_empty) { var first = true, a = []; while (!is("punc", closing)) { @@ -1292,6 +1966,9 @@ function parse($TEXT, options) { if (allow_trailing_comma && is("punc", closing)) break; if (is("punc", ",") && allow_empty) { a.push(new AST_Hole({ start: S.token, end: S.token })); + } else if (is("expand", "...")) { + next(); + a.push(new AST_Expansion({start: prev(), expression: expression(),end: S.token})); } else { a.push(expression(false)); } @@ -1311,63 +1988,316 @@ function parse($TEXT, options) { return function_(AST_Accessor); }); - var object_ = embed_tokens(function() { + var object_or_object_destructuring_ = embed_tokens(function() { + var start = S.token, first = true, a = []; expect("{"); - var first = true, a = []; while (!is("punc", "}")) { if (first) first = false; else expect(","); if (!options.strict && is("punc", "}")) // allow trailing comma break; - var start = S.token; + start = S.token; var type = start.type; var name = as_property_name(); - if (type == "name" && !is("punc", ":")) { - if (name == "get") { - a.push(new AST_ObjectGetter({ - start : start, - key : as_atom_node(), - value : create_accessor(), - end : prev() - })); + var value; + + // Check property and fetch value + if (!is("punc", ":")) { + var concise = concise_method_or_getset(name, start); + if (concise) { + a.push(concise); continue; } - if (name == "set") { - a.push(new AST_ObjectSetter({ - start : start, - key : as_atom_node(), - value : create_accessor(), - end : prev() - })); - continue; + if (!(start.type !== name)) { + unexpected(S.token); } + + value = new AST_SymbolRef({ + start: prev(), + name: name, + end: prev() + }); + } else if (name === null) { + unexpected(prev()); + } else { + next(); // `:` - see first condition + value = expression(false); } - expect(":"); + + // Check for default value and alter value accordingly if necessary + if (is("operator", "=")) { + next(); + value = new AST_Assign({ + start: start, + left: value, + operator: "=", + right: expression(false), + end: prev() + }); + } + + // Create property a.push(new AST_ObjectKeyVal({ - start : start, - quote : start.quote, - key : name, - value : expression(false), - end : prev() + start: start, + quote: start.quote, + key: name, + value: value, + end: prev() })); } next(); - return new AST_Object({ properties: a }); + return new AST_Object({ properties: a }) }); + function class_(KindOfClass) { + var start, method, class_name, extends_, a = []; + + S.input.push_directives_stack(); // Push directive stack, but not scope stack + S.input.add_directive("use strict"); + + if (S.token.type == "name" && S.token.value != "extends") { + class_name = as_symbol(KindOfClass === AST_DefClass ? AST_SymbolDefClass : AST_SymbolClass); + } + + if (KindOfClass === AST_DefClass && !class_name) { + unexpected(); + } + + 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; + method = concise_method_or_getset(as_property_name(), start, true); + if (!method) { unexpected(); } + a.push(method); + if (is("punc", ";")) { next(); } + } + + S.input.pop_directives_stack(); + + next(); + + return new KindOfClass({ + start: start, + name: class_name, + extends: extends_, + properties: a, + end: prev(), + }); + } + + function concise_method_or_getset(name, start, is_class) { + var get_ast = function(name, token) { + if (typeof name === "string" || typeof name === "number") { + return new AST_SymbolMethod({ + start: token, + name: name, + end: prev() + }); + } else if (name === null) { + unexpected(); + } + return name; + } + var is_static = false; + var is_generator = false; + var property_token = start; + if (is_class && name === "static" && !is("punc", "(")) { + is_static = true; + property_token = S.token; + name = as_property_name(); + } + if (name === null) { + is_generator = true; + property_token = S.token; + name = as_property_name(); + if (name === null) { + unexpected(); + } + } + if (is("punc", "(")) { + name = get_ast(name, start); + var node = new AST_ConciseMethod({ + start : start, + static : is_static, + is_generator: is_generator, + key : name, + quote : name instanceof AST_SymbolMethod ? + property_token.quote : undefined, + value : function_(AST_Accessor, is_generator), + end : prev() + }); + return node; + } + property_token = S.token; + if (name == "get") { + if (!is("punc") || is("punc", "[")) { + name = get_ast(as_property_name(), start); + return new AST_ObjectGetter({ + start : start, + static: is_static, + key : name, + quote : name instanceof AST_SymbolMethod ? + property_token.quote : undefined, + value : create_accessor(), + end : prev() + }); + } + } + else if (name == "set") { + if (!is("punc") || is("punc", "[")) { + name = get_ast(as_property_name(), start); + return new AST_ObjectSetter({ + start : start, + static: is_static, + key : name, + quote : name instanceof AST_SymbolMethod ? + property_token.quote : undefined, + value : create_accessor(), + end : prev() + }); + } + } + } + + function import_() { + var start = prev(); + var imported_name; + var imported_names; + if (is("name")) { + imported_name = as_symbol(AST_SymbolImport); + } + + if (is("punc", ",")) { + next(); + } + + if (is("punc", "{")) { + next(); + imported_names = []; + while (!is("punc", "}")) { + imported_names.push(import_name()); + if (is("punc", ",")) { + next(); + } + } + next(); + } + + if (imported_names || imported_name) { + expect_token("name", "from"); + } + var mod_str = S.token; + if (mod_str.type !== 'string') { + unexpected(); + } + next(); + return new AST_Import({ + start: start, + imported_name: imported_name, + imported_names: imported_names, + module_name: new AST_String({ + start: mod_str, + value: mod_str.value, + quote: mod_str.quote, + end: mod_str, + }), + end: S.token, + }); + } + + function import_name() { + var start = S.token; + var foreign_name; + var name; + + if (peek().value === "as" && peek().type === "name") { + foreign_name = as_symbol(AST_SymbolImportForeign); + next(); // The "as" word + } + name = as_symbol(AST_SymbolImport); + + if (foreign_name === undefined) { + foreign_name = new AST_SymbolImportForeign({ + name: name.name, + start: name.start, + end: name.end, + }); + } + + return new AST_NameImport({ + start: start, + foreign_name: foreign_name, + name: name, + end: prev(), + }) + } + + function export_() { + var start = S.token; + var is_default; + var exported_value; + var exported_definition; + + if (is("keyword", "default")) { + is_default = true; + next(); + } + + var is_definition = + is("keyword", "var") || is("keyword", "let") || is("keyword", "const") || + is("keyword", "class") || is("keyword", "function"); + + if (is_definition) { + exported_definition = statement(); + } else { + exported_value = expression(); + semicolon(); + } + + return new AST_Export({ + start: start, + is_default: is_default, + exported_value: exported_value, + exported_definition: exported_definition, + end: prev(), + }); + } + function as_property_name() { var tmp = S.token; next(); switch (tmp.type) { - case "num": - case "string": - case "name": + case "punc": + if (tmp.value === "[") { + var ex = expression(false); + expect("]"); + return ex; + } else unexpected(tmp); case "operator": + if (tmp.value === "*") { + return null; + } + if (["delete", "in", "instanceof", "new", "typeof", "void"].indexOf(tmp.value) === -1) { + unexpected(tmp); + } + case "name": + if (tmp.value === "yield" && S.input.has_directive("use strict") && !is_in_generator()) { + token_error(tmp, "Unexpected yield identifier inside strict mode"); + } + case "string": + case "num": case "keyword": case "atom": return tmp.value; default: - unexpected(); + unexpected(tmp); } }; @@ -1387,7 +2317,9 @@ function parse($TEXT, options) { function _make_symbol(type) { var name = S.token.value; - return new (name == "this" ? AST_This : type)({ + return new (name == "this" ? AST_This : + name == "super" ? AST_Super : + type)({ name : String(name), start : S.token, end : S.token @@ -1399,6 +2331,9 @@ function parse($TEXT, options) { if (!noerror) croak("Name expected"); return null; } + if (is("name", "yield") && S.input.has_directive("use strict")) { + token_error(S.prev, "Unexpected yield identifier inside strict mode"); + } var sym = _make_symbol(type); next(); return sym; @@ -1431,13 +2366,39 @@ function parse($TEXT, options) { return subscripts(new AST_Call({ start : start, expression : expr, - args : expr_list(")"), + args : call_args(), end : prev() }), true); } + if (is("template_head")) { + return subscripts(new AST_PrefixedTemplateString({ + start: start, + prefix: expr, + template_string: template_string() + }), allow_calls); + } return expr; }; + var call_args = embed_tokens(function call_args() { + var first = true; + var args = []; + while (!is("punc", ")")) { + if (first) first = false; else expect(","); + if (is("expand", "...")) { + next(); + args.push(new AST_Expansion({ + start: prev(), + expression: expression(false) + })); + } else { + args.push(expression(false)); + } + } + next(); + return args; + }); + var maybe_unary = function(allow_calls) { var start = S.token; if (is("operator") && UNARY_PREFIX(start.value)) { @@ -1467,8 +2428,12 @@ function parse($TEXT, options) { var expr_op = function(left, min_prec, no_in) { var op = is("operator") ? S.token.value : null; if (op == "in" && no_in) op = null; + if (op == "**" && left instanceof AST_UnaryPrefix + && left.end === S.prev /* unary token in front not allowed, but allowed if prev is for example `)` */ + && left.operator !== "--" && left.operator !== "++") + unexpected(left.start); var prec = op != null ? PRECEDENCE[op] : null; - if (prec != null && prec > min_prec) { + if (prec != null && (prec > min_prec || (op === "**" && min_prec === prec))) { next(); var right = expr_op(maybe_unary(true), prec, no_in); return expr_op(new AST_Binary({ @@ -1507,14 +2472,93 @@ function parse($TEXT, options) { function is_assignable(expr) { if (!options.strict) return true; if (expr instanceof AST_This) return false; + if (expr instanceof AST_Super) return false; return (expr instanceof AST_PropAccess || expr instanceof AST_Symbol); }; + // In ES6, AssignmentExpression can also be an ArrowFunction var maybe_assign = function(no_in) { var start = S.token; - var left = maybe_conditional(no_in), val = S.token.value; + + if (start.type == "name" && start.value == "yield") { + if (is_in_generator()) { + next(); + return _yield_expression(); + } else if (S.input.has_directive("use strict")) { + token_error(S.token, "Unexpected yield identifier inside strict mode") + } + } + + if (start.type == "punc" && start.value == "(" && peek().value == ")") { + next(); + next(); + return arrow_function([]); + } + + if (is("name") && is_token(peek(), "arrow")) { + var param = new AST_SymbolFunarg({ + name: start.value, + start: start, + end: start, + }); + next(); + return arrow_function([param]) + } + + var left = maybe_conditional(no_in); + var val = S.token.value; + if (is("operator") && ASSIGNMENT(val)) { if (is_assignable(left)) { + + var walk = function(node) { + var newNode; + if (node instanceof AST_Object) { + newNode = new AST_Destructuring({ + start: node.start, + names: node.properties.map(walk), + is_array: false, + end: node.end + }); + node = newNode; + } else if (node instanceof AST_Array) { + var names = []; + + for (var i = 0; i < node.elements.length; i++) { + // Only allow expansion as last element + if (node.elements[i] instanceof AST_Expansion) { + if (i + 1 !== node.elements.length) { + token_error(node.elements[i].start, "Spread must the be last element in destructuring array"); + } + node.elements[i].expression = walk(node.elements[i].expression); + } + + names.push(walk(node.elements[i])); + } + + newNode = new AST_Destructuring({ + start: node.start, + names: names, + is_array: true, + end: node.end + }); + node = newNode; + } else if (node instanceof AST_ObjectProperty) { + node.value = walk(node.value); + } else if (node instanceof AST_Assign) { + node = new AST_DefaultAssign({ + start: node.start, + left: node.left, + operator: "=", + right: node.right, + end: node.end + }); + } + + return node; + } + left = walk(left); + next(); return new AST_Assign({ start : start, @@ -1532,6 +2576,14 @@ function parse($TEXT, options) { var expression = function(commas, no_in) { var start = S.token; var expr = maybe_assign(no_in); + if (expr instanceof AST_SymbolRef && is("arrow", "=>")) { + expr = new AST_ArrowParametersOrSeq({ + start: expr.start, + end: expr.end, + expressions: [expr] + }); + return arrow_function(expr); + } if (commas && is("punc", ",")) { next(); return new AST_Seq({ diff --git a/lib/propmangle.js b/lib/propmangle.js index 3c75cac9..5d26fd4c 100644 --- a/lib/propmangle.js +++ b/lib/propmangle.js @@ -44,10 +44,27 @@ "use strict"; function find_builtins() { + + // Compatibility fix for some standard defined globals not defined on every js environment + var new_globals = ["Symbol", "Map", "Promise", "Proxy", "Reflect", "Set", "WeakMap", "WeakSet"]; + var objects = {}; + + new_globals.forEach(function (new_global) { + objects[new_global] = global[new_global] || new Function(); + }); + var a = []; [ Object, Array, Function, Number, String, Boolean, Error, Math, - Date, RegExp + Date, RegExp, objects.Symbol, ArrayBuffer, + DataView, decodeURI, decodeURIComponent, + encodeURI, encodeURIComponent, eval, EvalError, + Float32Array, Float64Array, Int8Array, Int16Array, + Int32Array, isFinite, isNaN, JSON, objects.Map, parseFloat, + parseInt, objects.Promise, objects.Proxy, RangeError, ReferenceError, + objects.Reflect, objects.Set, SyntaxError, TypeError, Uint8Array, + Uint8ClampedArray, Uint16Array, Uint32Array, URIError, + objects.WeakMap, objects.WeakSet ].forEach(function(ctor){ Object.getOwnPropertyNames(ctor).map(add); if (ctor.prototype) { @@ -113,6 +130,9 @@ function mangle_properties(ast, options) { else if (node instanceof AST_Sub) { addStrings(node.property, ignore_quoted); } + else if (node instanceof AST_ConciseMethod) { + add(node.name.name); + } })); // step 2: transform the tree, renaming properties @@ -132,6 +152,11 @@ function mangle_properties(ast, options) { if (!ignore_quoted) node.property = mangleStrings(node.property); } + else if (node instanceof AST_ConciseMethod) { + if (should_mangle(node.name.name)) { + node.name.name = mangle(node.name.name); + } + } // else if (node instanceof AST_String) { // if (should_mangle(node.value)) { // AST_Node.warn( diff --git a/lib/scope.js b/lib/scope.js index ae0c5777..7db670d5 100644 --- a/lib/scope.js +++ b/lib/scope.js @@ -49,7 +49,9 @@ function SymbolDef(scope, index, orig) { this.scope = scope; this.references = []; this.global = false; + this.export = false; this.mangled_name = null; + this.object_destructuring_arg = false; this.undeclared = false; this.index = index; this.id = SymbolDef.next_id++; @@ -62,11 +64,17 @@ SymbolDef.prototype = { if (!options) options = {}; return (this.global && !options.toplevel) + || this.export + || this.object_destructuring_arg || this.undeclared || (!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; @@ -96,16 +104,29 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ var scope = self.parent_scope = null; var labels = new Dictionary(); var defun = null; + var in_destructuring = null; + var in_export; var tw = new TreeWalker(function(node, descend){ - if (options.screw_ie8 && node instanceof AST_Catch) { + if (node.is_block_scope()) { var save_scope = scope; scope = new AST_Scope(node); scope.init_scope_vars(); scope.parent_scope = save_scope; + if (!(node instanceof AST_Scope)) { + scope.uses_with = save_scope.uses_with; + scope.uses_eval = save_scope.uses_eval; + scope.directives = save_scope.directives; + } descend(); scope = save_scope; return true; } + if (node instanceof AST_Destructuring && node.is_array === false) { + in_destructuring = node; // These don't nest + descend(); + in_destructuring = null; + return true; + } if (node instanceof AST_Scope) { node.init_scope_vars(); var save_scope = node.parent_scope = scope; @@ -119,6 +140,11 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ labels = save_labels; return true; // don't descend again in TreeWalker } + if (node instanceof AST_Export) { + in_export = true; + descend(); + in_export = false; + } if (node instanceof AST_LabeledStatement) { var l = node.label; if (labels.has(l.name)) { @@ -137,12 +163,16 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ if (node instanceof AST_Symbol) { node.scope = scope; } + if (node instanceof AST_SymbolFunarg) { + node.object_destructuring_arg = !!in_destructuring; + defun.def_variable(node, in_export); + } if (node instanceof AST_Label) { node.thedef = node; node.references = []; } if (node instanceof AST_SymbolLambda) { - defun.def_function(node); + defun.def_function(node, in_export); } else if (node instanceof AST_SymbolDefun) { // Careful here, the scope where this should be defined is @@ -150,11 +180,28 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ // scope when we encounter the AST_Defun node (which is // instanceof AST_Scope) but we get to the symbol a bit // later. - (node.scope = defun.parent_scope).def_function(node); + var parent_lambda = defun.parent_scope; + while (parent_lambda.is_block_scope()) { + parent_lambda = parent_lambda.parent_scope; + } + (node.scope = parent_lambda).def_function(node, in_export); + } + else if (node instanceof AST_SymbolClass) { + defun.def_variable(node, in_export); + } + else if (node instanceof AST_SymbolImport) { + scope.def_variable(node, in_export); + } + 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, in_export); } else if (node instanceof AST_SymbolVar - || node instanceof AST_SymbolConst) { - var def = defun.def_variable(node); + || node instanceof AST_SymbolConst + || node instanceof AST_SymbolLet) { + var def = ((node instanceof AST_SymbolBlockDeclaration) ? scope : defun).def_variable(node, in_export); + def.destructuring = in_destructuring; def.init = tw.parent().value; } else if (node instanceof AST_SymbolCatch) { @@ -175,6 +222,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) { @@ -184,6 +232,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_LoopControl && node.label) { node.label.thedef.references.push(node); return true; @@ -237,6 +292,19 @@ AST_Scope.DEFMETHOD("init_scope_vars", function(){ this.cname = -1; // the current index for mangling functions/variables }); +AST_Node.DEFMETHOD("is_block_scope", function(){ + return false; // Behaviour will be overridden by AST_Block +}); + +AST_Block.DEFMETHOD("is_block_scope", function(){ + return ( + !(this instanceof AST_Lambda) && + !(this instanceof AST_Toplevel) && + !(this instanceof AST_Class) && + !(this instanceof AST_SwitchBranch) + ); +}); + AST_Lambda.DEFMETHOD("init_scope_vars", function(){ AST_Scope.prototype.init_scope_vars.apply(this, arguments); this.uses_arguments = false; @@ -268,16 +336,20 @@ AST_Scope.DEFMETHOD("find_variable", function(name){ || (this.parent_scope && this.parent_scope.find_variable(name)); }); -AST_Scope.DEFMETHOD("def_function", function(symbol){ - this.functions.set(symbol.name, this.def_variable(symbol)); +AST_Scope.DEFMETHOD("def_function", function(symbol, in_export){ + this.functions.set(symbol.name, this.def_variable(symbol, in_export)); }); -AST_Scope.DEFMETHOD("def_variable", function(symbol){ +AST_Scope.DEFMETHOD("def_variable", function(symbol, in_export){ var def; if (!this.variables.has(symbol.name)) { def = new SymbolDef(this, this.variables.size(), symbol); this.variables.set(symbol.name, def); - def.global = !this.parent_scope; + def.object_destructuring_arg = symbol.object_destructuring_arg; + if (in_export) { + def.export = true; + } + def.global = !this.parent_scope && !(symbol instanceof AST_SymbolBlockDeclaration); } else { def = this.variables.get(symbol.name); def.orig.push(symbol); @@ -325,7 +397,8 @@ AST_Function.DEFMETHOD("next_mangled", function(options, def){ }); AST_Symbol.DEFMETHOD("unmangleable", function(options){ - return this.definition().unmangleable(options); + var def = this.definition(); + return def && def.unmangleable(options); }); // property accessors are not mangleable @@ -370,7 +443,8 @@ AST_Toplevel.DEFMETHOD("_default_mangler_options", function(options){ sort : false, // Ignored. Flag retained for backwards compatibility. toplevel : false, screw_ie8 : true, - keep_fnames : false + keep_fnames : false, + keep_classnames : false }); }); @@ -419,13 +493,19 @@ AST_Toplevel.DEFMETHOD("mangle_names", function(options){ node.mangled_name = name; return true; } - if (options.screw_ie8 && node instanceof AST_SymbolCatch) { + var mangle_with_block_scope = + (options.screw_ie8 && node instanceof AST_SymbolCatch) || + node instanceof AST_SymbolBlockDeclaration; + if (mangle_with_block_scope) { to_mangle.push(node.definition()); return; } }); this.walk(tw); - to_mangle.forEach(function(def){ def.mangle(options) }); + to_mangle.forEach(function(def){ + if (def.destructuring && !def.destructuring.is_array) return; + def.mangle(options); + }); if (options.cache) { options.cache.cname = this.cname; @@ -476,21 +556,27 @@ AST_Toplevel.DEFMETHOD("compute_char_frequency", function(options){ else if (node instanceof AST_With) base54.consider("with"); else if (node instanceof AST_ObjectSetter) - base54.consider("set" + node.key); + base54.consider("set" + (typeof node.key === "string" ? node.key : "")); else if (node instanceof AST_ObjectGetter) - base54.consider("get" + node.key); - else if (node instanceof AST_ObjectKeyVal) + base54.consider("get" + (typeof node.key === "string" ? node.key : "")); + else if (node instanceof AST_ObjectKeyVal && typeof node.key === "string") + base54.consider(node.key); + else if (node instanceof AST_ConciseMethod && typeof node.key === "string") base54.consider(node.key); else if (node instanceof AST_New) base54.consider("new"); else if (node instanceof AST_This) base54.consider("this"); + else if (node instanceof AST_Super) + base54.consider("super"); else if (node instanceof AST_Try) base54.consider("try"); else if (node instanceof AST_Catch) base54.consider("catch"); else if (node instanceof AST_Finally) base54.consider("finally"); + else if (node instanceof AST_Yield) + base54.consider("yield"); else if (node instanceof AST_Symbol && node.unmangleable(options)) base54.consider(node.name); else if (node instanceof AST_Unary || node instanceof AST_Binary) diff --git a/lib/transform.js b/lib/transform.js index 3018e8ff..8237c1e8 100644 --- a/lib/transform.js +++ b/lib/transform.js @@ -163,10 +163,18 @@ TreeTransformer.prototype = new TreeWalker; if (self.value) self.value = self.value.transform(tw); }); + _(AST_Destructuring, function(self, tw) { + self.names = do_list(self.names, tw); + }); + _(AST_Lambda, function(self, tw){ if (self.name) self.name = self.name.transform(tw); self.argnames = do_list(self.argnames, tw); - self.body = do_list(self.body, tw); + if (self.body instanceof AST_Node) { + self.body = self.body.transform(tw); + } else { + self.body = do_list(self.body, tw); + } }); _(AST_Call, function(self, tw){ @@ -188,6 +196,10 @@ TreeTransformer.prototype = new TreeWalker; self.property = self.property.transform(tw); }); + _(AST_Yield, function(self, tw){ + if (self.expression) self.expression = self.expression.transform(tw); + }); + _(AST_Unary, function(self, tw){ self.expression = self.expression.transform(tw); }); @@ -212,7 +224,32 @@ TreeTransformer.prototype = new TreeWalker; }); _(AST_ObjectProperty, function(self, tw){ + if (self.key instanceof AST_Node) { + self.key = self.key.transform(tw); + } self.value = self.value.transform(tw); }); + _(AST_Class, function(self, tw){ + if (self.name) self.name = self.name.transform(tw); + if (self.extends) self.extends = self.extends.transform(tw); + self.properties = do_list(self.properties, tw); + }); + + _(AST_Expansion, function(self, tw){ + self.expression = self.expression.transform(tw); + }); + + _(AST_TemplateString, function(self, tw) { + for (var i = 0; i < self.segments.length; i++) { + if (!(self.segments[i] instanceof AST_TemplateSegment)) { + self.segments[i] = self.segments[i].transform(tw); + } + } + }); + + _(AST_PrefixedTemplateString, function(self, tw) { + self.template_string = self.template_string.transform(tw); + }); + })(); diff --git a/test/compress/arrays.js b/test/compress/arrays.js index f0ded06c..6d0fad49 100644 --- a/test/compress/arrays.js +++ b/test/compress/arrays.js @@ -1,3 +1,5 @@ +// NOTE trailing comma doesn't contribute to length of an array +// That also means the array changes length if previous element is a hole too and got cut off holes_and_undefined: { input: { w = [1,,]; @@ -91,6 +93,79 @@ constant_join_2: { } } +spread_with_variable_as_last_element: { + input: { + var values = [4, 5, 6]; + var a = [1, 2, 3, ...values]; + } + expect: { + var values = [4, 5, 6]; + var a = [1, 2, 3, ...values]; + } +} + +spread_with_variable_in_middle: { + input: { + var values = [4, 5, 6]; + var a = [1, 2, 3, ...values, 7,,,]; + } + expect: { + var values = [4, 5, 6]; + var a = [1, 2, 3, ...values, 7,,,]; + } +} + +spread_with_variable_at_front: { + input: { + var values = [1, 2, 3]; + var a = [...values, 4, 5, 6]; + } + expect: { + var values = [1, 2, 3]; + var a = [...values, 4, 5, 6]; + } +} + +spread_with_variable_at_front_after_elisions: { + input: { + var values = [1, 2, 3]; + var a = [,,,...values, 4, 5, 6]; + } + expect: { + var values = [1, 2, 3]; + var a = [,,,...values, 4, 5, 6]; + } +} + +spread_with_array_at_end: { + input: { + var a = [1, 2, ...[4, 5, 6]]; + } + expect: { + var a = [1, 2, ...[4, 5, 6]]; + } +} + +spread_with_logical_expression_at_end: { + options = { evaluate: true } + input: { + var a = [1, 2, 3, ...[2+2]] + } + expect: { + var a = [1, 2, 3, ...[4]] + } +} + +spread_with_logical_expression_at_middle: { + options = { evaluate: true } + input: { + var a = [1, 1, ...[1+1, 1+2, 2+3], 8] + } + expect: { + var a = [1, 1, ...[2, 3, 5], 8] + } +} + constant_join_3: { options = { unsafe: true, diff --git a/test/compress/arrow.js b/test/compress/arrow.js new file mode 100644 index 00000000..e5baaaf5 --- /dev/null +++ b/test/compress/arrow.js @@ -0,0 +1,137 @@ +arrow_functions_without_body: { + input: { + var a1 = () => 42; + var a2 = (p) => p; + var a3 = p => p; + var a4 = (...p) => p; + var a5 = (b, c) => b + c; + var a6 = (b, ...c) => b + c[0]; + var a7 = (...b) => b.join(); + } + expect: { + var a1 = () => 42; + var a2 = (p) => p; + var a3 = p => p; + var a4 = (...p) => p; + var a5 = (b, c) => b + c; + var a6 = (b, ...c) => b + c[0]; + var a7 = (...b) => b.join(); + } +} + +arrow_functions_with_body: { + input: { + var a1 = () => { + var a = 42 * Math.random(); + return a; + }; + var a2 = (p) => { + var a = Math.random() * p; + return a; + }; + var a3 = p => { + var a = Math.random() * p; + return a; + }; + var a4 = (...p) => { + var a = Math.random() * p; + return a; + }; + var a5 = (b, c) => { + var result = b * c + b / c; + return result + }; + var a6 = (b, ...c) => { + var result = b; + for (var i = 0; i < c.length; i++) + result += c[i]; + return result + }; + var a7 = (...b) => { + b.join(); + } + } + expect: { + var a1 = () => { + var a = 42 * Math.random(); + return a; + }; + var a2 = (p) => { + var a = Math.random() * p; + return a; + }; + var a3 = p => { + var a = Math.random() * p; + return a; + }; + var a4 = (...p) => { + var a = Math.random() * p; + return a; + }; + var a5 = (b, c) => { + var result = b * c + b / c; + return result + }; + var a6 = (b, ...c) => { + var result = b; + for (var i = 0; i < c.length; i++) + result += c[i]; + return result + }; + var a7 = (...b) => { + b.join(); + }; + } +} + +arrow_function_with_single_parameter_with_default: { + input: { + var foo = (a = 0) => doSomething(a); + } + expect_exact: "var foo=(a=0)=>doSomething(a);" +} + +arrow_binding_pattern: { + input: { + var foo = ([]) => "foo"; + var bar = ({}) => "bar"; + var with_default = (foo = "default") => foo; + var object_with_default = ({foo = "default", bar: baz = "default"}) => foo; + var array_after_spread = (...[foo]) => foo; + var array_after_spread = (...{foo}) => foo; + var computed = ({ [compute()]: x }) => {}; + var array_hole = ([, , ...x] = [1, 2]) => {}; + var object_trailing_elision = ({foo,}) => {}; + var spread_empty_array = (...[]) => "foo"; + var spread_empty_object = (...{}) => "foo"; + } + expect: { + var foo = ([]) => "foo"; + var bar = ({}) => "bar"; + var with_default = (foo = "default") => foo; + var object_with_default = ({foo = "default", bar: baz = "default"}) => foo; + var array_after_spread = (...[foo]) => foo; + var array_after_spread = (...{foo}) => foo; + var computed = ({ [compute()]: x }) => {}; + var array_hole = ([, , ...x] = [1, 2]) => {}; + var object_trailing_elision = ({foo,}) => {}; + var spread_empty_array = (...[]) => "foo"; + var spread_empty_object = (...{}) => "foo"; + } +} + +arrow_binding_pattern_strict: { + input: { + var foo = ([,]) => "foo"; + } + expect_exact: 'var foo=([,])=>"foo";' +} + +arrow_with_regexp: { + input: { + num => /\d{11,14}/.test( num ) + } + expect: { + num => /\d{11,14}/.test( num ) + } +} diff --git a/test/compress/block-scope.js b/test/compress/block-scope.js new file mode 100644 index 00000000..cc2c316f --- /dev/null +++ b/test/compress/block-scope.js @@ -0,0 +1,174 @@ + +let_statement: { + input: { + let x = 6; + } + expect_exact: "let x=6;" +} + +do_not_hoist_let: { + options = { + hoist_vars: true, + }; + input: { + function x() { + if (FOO) { + let let1; + let let2; + var var1; + var var2; + } + } + } + expect: { + function x() { + var var1, var2; + if (FOO) { + let let1; + let let2; + } + } + } +} + +do_not_remove_anon_blocks_if_they_have_decls: { + input: { + function x() { + { + let x; + } + { + var x; + } + { + const y; + class Zee {}; + } + } + { + let y; + } + { + var y; + } + } + expect: { + function x(){ + { + let x + } + var x; + { + const y; + class Zee {} + } + } + { + let y + } + var y; + } +} + +remove_unused_in_global_block: { + options = { + unused: true, + } + input: { + { + let x; + const y; + class Zee {}; + var w; + } + let ex; + const why; + class Zed {}; + var wut; + console.log(x, y, Zee); + } + expect: { + var w; + var wut; + console.log(x, y, Zee); + } +} + +regression_block_scope_resolves: { + mangle = { }; + options = { + dead_code: false + }; + input: { + (function () { + if(1) { + let x; + const y; + class Zee {}; + } + if(1) { + let ex; + const why; + class Zi {}; + } + console.log(x, y, Zee, ex, why, Zi); + }()); + } + expect: { + (function () { + if (1) { + let o; + const n; + class c {}; + } + if (1) { + let o; + const n; + class c {}; + } + console.log(x, y, Zee, ex, why, Zi); + }()); + } +} + +switch_block_scope_mangler: { + mangle = {} + input: { + var fn = function(code) { + switch (code) { + case 1: + let apple = code + 1; + let dog = code + 4; + console.log(apple, dog); + break; + case 2: + let banana = code + 2; + console.log(banana); + break; + default: + let cat = code + 3; + console.log(cat); + } + }; + } + expect: { + var fn = function(o) { + switch (o) { + case 1: + let e = o + 1 + let c = o + 4; + console.log(e, c); + break; + + case 2: + let l = o + 2; + console.log(l); + break; + + default: + let a = o + 3; + console.log(a); + } + }; + } +} diff --git a/test/compress/dead-code.js b/test/compress/dead-code.js index cd96d02d..134212f0 100644 --- a/test/compress/dead-code.js +++ b/test/compress/dead-code.js @@ -89,6 +89,28 @@ dead_code_constant_boolean_should_warn_more: { } } +dead_code_block_decls_die: { + options = { + dead_code : true, + conditionals : true, + booleans : true, + evaluate : true + }; + input: { + if (0) { + let foo = 6; + const bar = 12; + class Baz {}; + var qux; + } + console.log(foo, bar, Baz); + } + expect: { + var qux; + console.log(foo, bar, Baz); + } +} + dead_code_const_declaration: { options = { dead_code : true, diff --git a/test/compress/destructuring.js b/test/compress/destructuring.js new file mode 100644 index 00000000..81c02eeb --- /dev/null +++ b/test/compress/destructuring.js @@ -0,0 +1,253 @@ +destructuring_arrays: { + input: { + {const [aa, bb] = cc;} + {const [aa, [bb, cc]] = dd;} + {let [aa, bb] = cc;} + {let [aa, [bb, cc]] = dd;} + var [aa, bb] = cc; + var [aa, [bb, cc]] = dd; + var [,[,,,,,],,,zz,] = xx; // Trailing comma + var [,,zzz,,] = xxx; // Trailing comma after hole + } + expect: { + {const [aa, bb] = cc;} + {const [aa, [bb, cc]] = dd;} + {let [aa, bb] = cc;} + {let [aa, [bb, cc]] = dd;} + var [aa, bb] = cc; + var [aa, [bb, cc]] = dd; + var [,[,,,,,],,,zz] = xx; + var [,,zzz,,] = xxx; + } +} + +destructuring_arrays_holes: { + input: { + var [,,,,] = a; + var [,,b,] = c; + var [d,,] = e; + } + expect_exact: "var[,,,,]=a;var[,,b]=c;var[d,,]=e;" +} + +destructuring_objects: { + input: { + {const {aa, bb} = {aa:1, bb:2};} + {const {aa, bb: {cc, dd}} = {aa:1, bb: {cc:2, dd: 3}};} + {let {aa, bb} = {aa:1, bb:2};} + {let {aa, bb: {cc, dd}} = {aa:1, bb: {cc:2, dd: 3}};} + var {aa, bb} = {aa:1, bb:2}; + var {aa, bb: {cc, dd}} = {aa:1, bb: {cc:2, dd: 3}}; + } + expect: { + {const {aa, bb} = {aa:1, bb:2};} + {const {aa, bb: {cc, dd}} = {aa:1, bb: {cc:2, dd: 3}};} + {let {aa, bb} = {aa:1, bb:2};} + {let {aa, bb: {cc, dd}} = {aa:1, bb: {cc:2, dd: 3}};} + var {aa, bb} = {aa:1, bb:2}; + var {aa, bb: {cc, dd}} = {aa:1, bb: {cc:2, dd: 3}}; + } +} + +destructuring_objects_trailing_elision: { + beautify = { + ecma: 6 + } + input: { + var {cc,} = foo; + } + expect_exact: "var{cc}=foo;" +} + +nested_destructuring_objects: { + beautify = { + ecma: 6 + } + input: { + const [{a},b] = c; + let [{a},b] = c; + var [{a},b] = c; + } + expect_exact: 'const[{a},b]=c;let[{a},b]=c;var[{a},b]=c;'; +} + +destructuring_constdef_in_loops: { + beautify = { + ecma: 6 + } + input: { + for (const [x,y] in pairs); + for (const [a] = 0;;); + for (const {c} of cees); + } + expect_exact: "for(const[x,y]in pairs);for(const[a]=0;;);for(const{c}of cees);" +} + +destructuring_letdef_in_loops: { + beautify = { + ecma: 6 + } + input: { + for (let [x,y] in pairs); + for (let [a] = 0;;); + for (let {c} of cees); + } + expect_exact: "for(let[x,y]in pairs);for(let[a]=0;;);for(let{c}of cees);" +} + +destructuring_vardef_in_loops: { + beautify = { + ecma: 6 + } + input: { + for (var [x,y] in pairs); + for (var [a] = 0;;); + for (var {c} of cees); + } + expect_exact: "for(var[x,y]in pairs);for(var[a]=0;;);for(var{c}of cees);" +} + +destructuring_expressions: { + beautify = { + ecma: 6 + } + input: { + ({a, b}); + [{a}]; + f({x}); + } + expect_exact: "({a,b});[{a}];f({x});" +} + +destructuring_remove_unused_1: { + options = { + unused: true + } + input: { + function a() { + var unused = "foo"; + var a = [1]; + var [b] = a; + f(b); + } + function b() { + var unused = "foo"; + var a = {b: 1}; + var {b} = a; + f(b); + } + function c() { + var unused = "foo"; + var a = [[1]]; + var [[b]] = a; + f(b); + } + function d() { + var unused = "foo"; + var a = {b: {b:1}}; + var {b:{b}} = a; + f(b); + } + function e() { + var unused = "foo"; + var a = [1, 2, 3, 4, 5]; + var [b, ...c] = a; + f(b, c); + } + } + expect: { + function a() { + var a = [1]; + var [b] = a; + f(b); + } + function b() { + var a = {b: 1}; + var {b} = a; + f(b); + } + function c() { + var a = [[1]]; + var [[b]] = a; + f(b); + } + function d() { + var a = {b: {b:1}}; + var {b:{b}} = a; + f(b); + } + function e() { + var a = [1, 2, 3, 4, 5]; + var [b, ...c] = a; + f(b, c); + } + } +} + +destructuring_remove_unused_2: { + options = { + unused: true + } + input: { + function a() { + var unused = "foo"; + var a = [,,1]; + var [b] = a; + f(b); + } + function b() { + var unused = "foo"; + var a = [{a: [1]}]; + var [{b: a}] = a; + f(b); + } + } + expect: { + function a() { + var a = [,,1]; + var [b] = a; + f(b); + } + function b() { + var a = [{a: [1]}]; + var [{b: a}] = a; + f(b); + } + } +} + +object_destructuring_may_need_parentheses: { + beautify = { + ecma: 6 + } + input: { + ({a, b} = {a: 1, b: 2}); + } + expect_exact: "({a,b}={a:1,b:2});" +} + +destructuring_with_undefined_as_default_assignment: { + options = { + evaluate: true + } + input: { + [foo = undefined] = bar; + [foo = void 0] = bar; + } + expect: { + [foo] = bar; + [foo] = bar; + } +} + +destructuring_dont_evaluate_with_undefined_as_default_assignment: { + options = { + evaluate: false + } + input: { + [foo = undefined] = bar; + } + expect: { + [foo = void 0] = bar; + } +} diff --git a/test/compress/directives.js b/test/compress/directives.js new file mode 100644 index 00000000..51587b95 --- /dev/null +++ b/test/compress/directives.js @@ -0,0 +1,10 @@ +class_directives_compression: { + input: { + class foo { + foo() { + "use strict"; + } + } + } + expect_exact: "class foo{foo(){}}" +} diff --git a/test/compress/drop-unused.js b/test/compress/drop-unused.js index 4b613181..d8dd0ea4 100644 --- a/test/compress/drop-unused.js +++ b/test/compress/drop-unused.js @@ -164,6 +164,87 @@ used_var_in_catch: { } } +unused_block_decls_in_catch: { + options = { unused: true }; + input: { + function foo() { + try { + foo(); + } catch(ex) { + let x = 10; + const y = 10; + class Zee {}; + } + } + } + expect: { + function foo() { + try { + foo(); + } catch(ex) {} + } + } +} + +used_block_decls_in_catch: { + options = { unused: true }; + input: { + function foo() { + try { + foo(); + } catch(ex) { + let x = 10; + const y = 10; + class Zee {}; + } + console.log(x, y, Zee); + } + } + expect: { + function foo() { + try { + foo(); + } catch(ex) {} + console.log(x, y, Zee); + } + } +} + +unused_block_decls: { + options = { unused: true }; + input: { + function foo() { + { + const x; + } + { + let y; + } + console.log(x, y); + } + } + expect: { + function foo() { + console.log(x, y); + } + } +} + +unused_keep_harmony_destructuring: { + options = { unused: true }; + input: { + function foo() { + var {x, y} = foo; + var a = foo; + } + } + expect: { + function foo() { + var {x, y} = foo; + } + } +} + keep_fnames: { options = { unused: true, keep_fnames: true, unsafe: true }; input: { diff --git a/test/compress/evaluate.js b/test/compress/evaluate.js index 26b6e489..ef0c4c78 100644 --- a/test/compress/evaluate.js +++ b/test/compress/evaluate.js @@ -222,6 +222,100 @@ positive_zero: { } } +pow: { + options = { evaluate: true } + input: { + var a = 5 ** 3; + } + expect: { + var a = 125; + } +} + +pow_sequence: { + options = { + evaluate: true + } + input: { + var a = 2 ** 3 ** 2; + } + expect: { + var a = 512; + } +} + +pow_mixed: { + options = { + evaluate: true + } + input: { + var a = 5 + 2 ** 3 + 5; + var b = 5 * 3 ** 2; + var c = 5 ** 3 * 2; + var d = 5 ** +3; + } + expect: { + var a = 18; + var b = 45; + var c = 250; + var d = 125; + } +} + +pow_with_right_side_evaluating_to_unary: { + options = { + evaluate: true + } + input: { + var a = (4 - 7) ** foo; + var b = ++bar ** 3; + var c = --baz ** 2; + } + expect_exact: "var a=(-3)**foo;var b=++bar**3;var c=--baz**2;" +} + +pow_with_number_constants: { + options = { + evaluate: true + } + input: { + var a = 5 ** NaN; + /* NaN exponent results to NaN */ + var b = 42 ** +0; + /* +0 exponent results to NaN */ + var c = 42 ** -0; + /* -0 exponent results to NaN */ + var d = NaN ** 1; + /* NaN with non-zero exponent is NaN */ + var e = 2 ** Infinity; + /* abs(base) > 1 with Infinity as exponent is Infinity */ + var f = 2 ** -Infinity; + /* abs(base) > 1 with -Infinity as exponent is +0 */ + var g = (-7) ** (0.5); + var h = 2324334 ** 34343443; + var i = (-2324334) ** 34343443; + var j = 2 ** (-3); + var k = 2.0 ** -3; + var l = 2.0 ** (5 - 7); + var m = 3 ** -10; // Result will be 0.000016935087808430286, which is too long + } + expect: { + var a = NaN; + var b = 1; + var c = 1; + var d = NaN; + var e = Infinity; + var f = 0; + var g = NaN; + var h = Infinity; + var i = -Infinity; + var j = .125; + var k = .125; + var l = .25; + var m = 3 ** -10; + } +} + unsafe_constant: { options = { evaluate : true, diff --git a/test/compress/expansions.js b/test/compress/expansions.js new file mode 100644 index 00000000..8879e67f --- /dev/null +++ b/test/compress/expansions.js @@ -0,0 +1,28 @@ + +expand_arguments: { + input: { + func(a, ...rest); + func(...all); + } + expect_exact: "func(a,...rest);func(...all);" +} + +expand_expression_arguments: { + input: { + f(...a.b); + f(...a.b()); + f(...(a)); + f(...(a.b)); + f(...a[i]); + } + expect_exact: "f(...a.b);f(...a.b());f(...a);f(...a.b);f(...a[i]);" +} + +expand_parameters: { + input: { + (function (a, ...b){}); + (function (...args){}); + } + expect_exact: "(function(a,...b){});(function(...args){});" +} + diff --git a/test/compress/expression.js b/test/compress/expression.js new file mode 100644 index 00000000..a3fe1a5a --- /dev/null +++ b/test/compress/expression.js @@ -0,0 +1,52 @@ +pow: { + input: { + var a = 2 ** 7; + var b = 3; + b **= 2; + } + expect: { + var a = 2 ** 7; + var b = 3; + b **= 2; + } +} + +pow_with_number_constants: { + input: { + var a = 5 ** NaN; + var b = 42 ** +0; + var c = 42 ** -0; + var d = NaN ** 1; + var e = 2 ** Infinity; + var f = 2 ** -Infinity; + } + expect: { + var a = 5 ** NaN; + var b = 42 ** +0; + var c = 42 ** -0; + var d = NaN ** 1; + var e = 2 ** (1/0); + var f = 2 ** -(1/0); + } +} + +pow_with_parentheses: { + input: { + var g = (-7) ** (0.5); + var h = 2324334 ** 34343443; + var i = (-2324334) ** 34343443; + var j = 2 ** (-3); + var k = 2.0 ** -3; + var l = 2.0 ** (5 - 7); + } + expect_exact: "var g=(-7)**.5;var h=2324334**34343443;var i=(-2324334)**34343443;var j=2**-3;var k=2**-3;var l=2**(5-7);" +} + +pow_with_unary_between_brackets: { + input: { + var a = (-(+5)) ** 3; + } + expect: { + var a = (-+5)**3; + } +} diff --git a/test/compress/harmony.js b/test/compress/harmony.js new file mode 100644 index 00000000..dc74b36e --- /dev/null +++ b/test/compress/harmony.js @@ -0,0 +1,288 @@ +arrow_function_parens: { + input: { + something && (() => {}); + } + expect_exact: "something&&(()=>{});" +} +arrow_function_parens_2: { + input: { + (() => null)(); + } + expect_exact: "(()=>null)();" +} + +typeof_arrow_functions: { + options = { + evaluate: true + } + input: { + var foo = typeof (x) => null; + } + expect_exact: "var foo=\"function\";" +} + +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{};" +} + +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(){}};" +} + +class_name_can_be_mangled: { + mangle = { }; + input: { + function x() { + class Foo { + } + var class1 = Foo + var class2 = class Bar {} + } + } + expect: { + function x() { + class a { } + var n = a + var r = class a {} + } + } +} + +class_name_can_be_preserved: { + mangle = { + keep_classnames: true + } + input: { + function x() { + (class Baz { }); + class Foo {}; + } + } + expect: { + function x() { + (class Baz { }); + class Foo {}; + } + } +} + +classes_can_have_generators: { + input: { + class Foo { + *bar() {} + static *baz() {} + } + } + expect: { + class Foo { + *bar() {} + static *baz() {} + } + } +} + +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']() {} + } + } +} + +class_methods_and_getters_with_keep_quoted_props_enabled: { + beautify = { + quote_style: 3, + keep_quoted_props: true, + } + input: { + class clss { + a() {} + "b"() {} + get c() { return "c"} + get "d"() { return "d"} + set e(a) { doSomething(a); } + set 'f'(a) { doSomething(b); } + static g() {} + static "h"() {} + } + } + expect_exact: 'class clss{a(){}"b"(){}get c(){return"c"}get"d"(){return"d"}set e(a){doSomething(a)}set\'f\'(a){doSomething(b)}static g(){}static"h"(){}}' +} + +new_target: { + input: { + new.target; + new.target.name; + } + expect_exact: "new.target;new.target.name;" +} + +number_literals: { + input: { + 0b1001; + 0B1001; + 0o11; + 0O11; + } + + expect: { + 9; + 9; + 9; + 9; + } +} + +import_statement: { + input: { + import "mod-name"; + import Foo from "bar"; + import { Bar, Baz } from 'lel'; + import Bar, { Foo } from 'lel'; + import { Bar as kex, Baz as food } from 'lel'; + } + expect_exact: "import\"mod-name\";import Foo from\"bar\";import{Bar,Baz}from\"lel\";import Bar,{Foo}from\"lel\";import{Bar as kex,Baz as food}from\"lel\";" +} + +export_statement: { + input: { + export default 1; + export var foo = 4; + export let foo = 6; + export const foo = 6; + export function foo() {}; + export class foo { }; + } + expect_exact: "export default 1;export var foo=4;export let foo=6;export const foo=6;export function foo(){};export class foo{};" +} + +import_statement_mangling: { + mangle = { }; + input: { + import Foo from "foo"; + import Bar, {Food} from "lel"; + import {What as Whatever} from "lel"; + Foo(); + Bar(); + Food(); + Whatever(); + } + expect: { + import l from "foo"; + import e, {Food as o} from "lel"; + import {What as f} from "lel"; + l(); + e(); + o(); + f(); + } +} + +export_statement_mangling: { + mangle = { }; + input: { + export var foo = 6; + export function bar() { } + export class Baz { } + bar(foo, Baz) + } + expect: { + export var foo = 6; + export function bar() { } + export class Baz { } + bar(foo, Baz) + } +} + +// https://github.com/mishoo/UglifyJS2/issues/1021 +regression_for_of_const: { + input: { + for (const x of y) {} + for (const x in y) {} + } + expect: { + for (const x of y);for (const x in y); + } +} + +// Fabio: My patches accidentally caused a crash whenever +// there's an extraneous set of parens around an object. +regression_cannot_destructure: { + input: { + var x = ({ x : 3 }); + x(({ x: 3 })); + } + expect_exact: "var x={x:3};x({x:3});"; +} + +regression_cannot_use_of: { + input: { + function of() { + } + var of = "is a valid variable name"; + of = { of: "is ok" }; + x.of; + of: foo() + } + expect: { + function of(){} + var of="is a valid variable name"; + of={of:"is ok"}; + x.of; + foo(); /* Label statement missing? No prob. */ + } +} + +fat_arrow_as_param: { + input: { + foo(x => x); + foo(x => x, y => y); + + foo(x => (x, x)); + foo(x => (x, x), y => (y, y)); + } + expect_exact: "foo(x=>x);foo(x=>x,y=>y);foo(x=>(x,x));foo(x=>(x,x),y=>(y,y));" +} diff --git a/test/compress/hoist.js b/test/compress/hoist.js new file mode 100644 index 00000000..68f5a8c3 --- /dev/null +++ b/test/compress/hoist.js @@ -0,0 +1,85 @@ + +hoist_vars: { + options = { + hoist_vars: true + } + input: { + function a() { + bar(); + var var1; + var var2; + } + function b(anArg) { + bar(); + var var1; + var anArg; + } + } + expect: { + function a() { + var var1, var2; // Vars go up and are joined + bar(); + } + function b(anArg) { + var var1; + bar(); + // But vars named like arguments go away! + } + } +} + +hoist_funs: { + options = { + hoist_funs: true + } + input: { + function a() { + bar(); + function foo() {} + } + } + expect: { + function a() { + function foo() {} // Funs go up + bar(); + } + } +} + +hoist_no_destructurings: { + options = { + hoist_vars: true, + hoist_funs: true + } + input: { + function a([anArg]) { + bar(); + var var1; + var anArg; // Because anArg is already declared, this goes away! + } + } + expect: { + function a([anArg]) { + var var1; + bar(); + } + } +} + +dont_hoist_var_destructurings: { + options = { + hoist_vars: true, + hoist_funs: true + } + input: { + function x() { + // If foo is null or undefined, this should be an exception + var {x,y} = foo; + } + } + expect: { + function x() { + var {x,y} = foo; + } + } +} diff --git a/test/compress/html_comments.js b/test/compress/html_comments.js index 8495b433..39973c3d 100644 --- a/test/compress/html_comments.js +++ b/test/compress/html_comments.js @@ -69,3 +69,12 @@ html_comment_in_string_literal: { } expect_exact: 'function f(){return"\\x3c!--HTML--\\x3ecomment in\\x3c!--string literal--\\x3e"}'; } + +html_comment_after_multiline_comment: { + input: { + var foo; /* +*/--> var bar; + var foobar; + } + expect_exact: "var foo;var foobar;" +} diff --git a/test/compress/issue-1001.js b/test/compress/issue-1001.js new file mode 100644 index 00000000..432e953c --- /dev/null +++ b/test/compress/issue-1001.js @@ -0,0 +1,8 @@ +parenthesis_strings_in_parenthesis: { + input: { + var foo = ('('); + a(')'); + + } + expect_exact: 'var foo="(";a(")");' +} diff --git a/test/compress/issue-1043.js b/test/compress/issue-1043.js new file mode 100644 index 00000000..78043f65 --- /dev/null +++ b/test/compress/issue-1043.js @@ -0,0 +1,30 @@ +issue_1043: { + options = { + side_effects: true + }; + + input: { + function* range(start = 0, end = null, step = 1) { + if (end == null) { + end = start; + start = 0; + } + + for (let i = start; i < end; i += step) { + yield i; + } + } + } + + expect: { + function* range(start = 0, end = null, step = 1) { + if (null == end) { + end = start; + start = 0; + } + + for (let i = start; i < end; i += step) + yield i; + } + } +} diff --git a/test/compress/issue-1044.js b/test/compress/issue-1044.js new file mode 100644 index 00000000..360075a4 --- /dev/null +++ b/test/compress/issue-1044.js @@ -0,0 +1,9 @@ +issue_1044: { + options = { evaluate: true, conditionals: true }; + input: { + const mixed = Base ? class extends Base {} : class {} + } + expect: { + const mixed = Base ? class extends Base {} : class {} + } +} diff --git a/test/compress/issue-1212.js b/test/compress/issue-1212.js new file mode 100644 index 00000000..72d6ffe0 --- /dev/null +++ b/test/compress/issue-1212.js @@ -0,0 +1,76 @@ +issue_1212_debug_false: { + options = { + global_defs : { DEBUG: false }, + sequences : true, + properties : true, + dead_code : true, + conditionals : true, + comparisons : true, + evaluate : true, + booleans : true, + loops : true, + unused : true, + hoist_funs : true, + keep_fargs : true, + if_return : true, + join_vars : true, + cascade : true, + side_effects : true, + } + input: { + class foo { + bar() { + if (DEBUG) + console.log("DEV"); + else + console.log("PROD"); + } + } + new foo().bar(); + } + expect: { + class foo{ + bar() { console.log("PROD") } + } + (new foo).bar(); + } +} + +issue_1212_debug_true: { + options = { + global_defs : { DEBUG: true }, + sequences : true, + properties : true, + dead_code : true, + conditionals : true, + comparisons : true, + evaluate : true, + booleans : true, + loops : true, + unused : true, + hoist_funs : true, + keep_fargs : true, + if_return : true, + join_vars : true, + cascade : true, + side_effects : true, + } + input: { + class foo { + bar() { + if (DEBUG) + console.log("DEV"); + else + console.log("PROD"); + } + } + new foo().bar(); + } + expect: { + class foo{ + bar() { console.log("DEV") } + } + (new foo).bar(); + } +} + diff --git a/test/compress/issue-203.js b/test/compress/issue-203.js new file mode 100644 index 00000000..0fa3c2eb --- /dev/null +++ b/test/compress/issue-203.js @@ -0,0 +1,34 @@ + +compress_new_function: { + options = { + unsafe: true + } + input: { + new Function("aa, bb", 'return aa;'); + } + expect: { + Function("a", "b", "return a"); + } +} + +compress_new_function_with_destruct: { + options = { + unsafe: true, + ecma: 6 + } + beautify = { + ecma: 6 + } + input: { + new Function("aa, [bb]", 'return aa;'); + new Function("aa, {bb}", 'return aa;'); + new Function("[[aa]], [{bb}]", 'return aa;'); + } + expect: { + Function("a", "[b]", "return a"); + Function("a", "{bb}", "return a"); + Function("[[a]]", "[{bb}]", 'return a'); + } +} + + diff --git a/test/compress/issue-926.js b/test/compress/issue-926.js new file mode 100644 index 00000000..e717efb0 --- /dev/null +++ b/test/compress/issue-926.js @@ -0,0 +1,9 @@ +template_strings: { + input: { + foo( + `${contents}`, + `${text}` + ); + } + expect_exact: "foo(`${contents}`,`${text}`);" +} diff --git a/test/compress/join-vars.js b/test/compress/join-vars.js new file mode 100644 index 00000000..ea6077b6 --- /dev/null +++ b/test/compress/join-vars.js @@ -0,0 +1,40 @@ +only_vars: { + options = { join_vars: true }; + input: { + let netmaskBinary = ''; + for (let i = 0; i < netmaskBits; ++i) { + netmaskBinary += '1'; + } + } + expect: { + let netmaskBinary = ''; + for (let i = 0; i < netmaskBits; ++i) netmaskBinary += '1'; + } +} + +issue_1079_with_vars: { + options = { join_vars: true }; + input: { + var netmaskBinary = ''; + for (var i = 0; i < netmaskBits; ++i) { + netmaskBinary += '1'; + } + } + expect: { + for (var netmaskBinary = '', i = 0; i < netmaskBits; ++i) netmaskBinary += '1'; + } +} + +issue_1079_with_mixed: { + options = { join_vars: true }; + input: { + var netmaskBinary = ''; + for (let i = 0; i < netmaskBits; ++i) { + netmaskBinary += '1'; + } + } + expect: { + var netmaskBinary = '' + for (let i = 0; i < netmaskBits; ++i) netmaskBinary += '1'; + } +} \ No newline at end of file diff --git a/test/compress/new.js b/test/compress/new.js index 83da88e6..2364a16e 100644 --- a/test/compress/new.js +++ b/test/compress/new.js @@ -82,3 +82,19 @@ new_with_unary_prefix: { } expect_exact: 'var bar=(+new Date).toString(32);'; } + +new_with_assignement_expression: { + options = { + evaluate: true + } + input: { + var a; + new x(a = 5 * 2, b = [1, 2, 3], c = {a: "a", b: "b", cd: "c" + "d"}); + new y([a, b] = [3, 4]); + } + expect: { + var a; + new x(a = 10, b = [1, 2, 3], c = {a: "a", b: "b", cd: "cd"}); + new y([a, b] = [3, 4]); + } +} diff --git a/test/compress/object.js b/test/compress/object.js new file mode 100644 index 00000000..a9f6b3cb --- /dev/null +++ b/test/compress/object.js @@ -0,0 +1,510 @@ +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 = {} + beautify = { + ecma: 6 + } + input: { + function f(get,set) { + return { + get, + set, + get g(){}, + set s(n){}, + c, + a:1, + m(){} + }; + } + } + expect_exact: "function f(n,t){return{get:n,set:t,get g(){},set s(n){},c,a:1,m(){}}}" +} + +use_shorthand_opportunity: { + beautify = { + ecma: 6 + } + input: { + var foo = 123; + var obj = {foo: foo}; + } + expect_exact: "var foo=123;var obj={foo};" +} + +computed_property_names: { + input: { + obj({ ["x" + "x"]: 6 }); + } + expect_exact: 'obj({["x"+"x"]:6});' +} + +computed_property_names_evaluated_1: { + options = { + evaluate: true + } + input: { + obj({ + [1 + 1]: 2, + ["x" + "x"]: 6 + }); + } + expect_exact: 'obj({[2]:2,["xx"]:6});' +} + +computed_property_names_evaluated_2: { + options = { + evaluate: true + } + input: { + var foo = something(); + + var obj = { + [foo]() { + return "blah"; + } + } + } + expect_exact: 'var foo=something();var obj={[foo](){return"blah"}};' +} + +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: { + beautify = { + ecma: 6 + } + 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; + }, + ["1" + "4"]() { + return 14; + } + } + } + expect: { + var foo = { + [Symbol.iterator]() { + return { /* stuff */ } + }, + [3]() { + return 3; + }, + ["14"]() { + return 14; + } + } + } +} + +concise_methods_with_computed_property2: { + options = { + evaluate: true + } + input: { + var foo = { + [[1]](){ + return "success"; + } + }; + doSomething(foo[[1]]()); + } + expect_exact: { + 'var foo={[[1]](){return"success"}};doSomething(foo[[1]]());' + } +} + +concise_methods_with_various_property_names: { + input: { + var get = "bar"; + var a = { + bar() { + return this.get; + }, + 5() { + return "five"; + }, + 0xf55() { + return "f five five"; + }, + "five"() { + return 5; + }, + 0b1010(value) { + this._ten = value; + } + }; + } + expect: { + var get = "bar"; + var a = { + bar() { + return this.get; + }, + 5() { + return "five"; + }, + 0xf55() { + return "f five five"; + }, + "five"() { + return 5; + }, + 0b1010(value) { + this._ten = value; + } + }; + } +} + +concise_methods_and_mangle_props: { + mangle_props = { + regex: /_/ + }; + input: { + function x() { + obj = { + _foo() { return 1; } + } + } + } + expect: { + function x() { + obj = { + a() { return 1; } + } + } + } +} + +concise_generators: { + beautify = { + ecma: 6 + } + 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_exact: '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}}' +} + +property_with_operator_value: { + input: { + var foo = { + "*": 1, + get "*"() { + return 2; + }, + *"*"() { + return 3; + }, + "%": 1, + get "%"() { + return 2; + }, + *"%"() { + return 3; + } + } + class bar { + get "*"() { + return 1 + } + *"*"() { + return 2; + } + get "%"() { + return 1 + } + *"%"() { + return 2; + } + } + } + expect_exact: 'var foo={"*":1,get"*"(){return 2},*"*"(){return 3},"%":1,get"%"(){return 2},*"%"(){return 3}};class bar{get"*"(){return 1}*"*"(){return 2}get"%"(){return 1}*"%"(){return 2}}' +} + +property_with_unprintable: { + input: { + var foo = { + "\x00\x01": "foo", + get "\x00\x01"() { + return "bar"; + }, + set "\x00\x01"(foo) { + save(foo); + }, + *"\x00\x01"() { + return "foobar"; + } + } + class bar { + get "\x00\x01"() { + return "bar" + } + set "\x00\x01"(foo) { + save(foo); + } + *"\x00\x01"() { + return "foobar"; + } + } + } + expect_exact: 'var foo={"\\0\x01":"foo",get"\\0\x01"(){return"bar"},set"\\0\x01"(foo){save(foo)},*"\\0\x01"(){return"foobar"}};class bar{get"\\0\x01"(){return"bar"}set"\\0\x01"(foo){save(foo)}*"\\0\x01"(){return"foobar"}}' +} + +property_with_unprintable_ascii_only: { + beautify = { + ascii_only: true, + } + input: { + var foo = { + "\x00\x01": "foo", + get "\x00\x01"() { + return "bar"; + }, + set "\x00\x01"(foo) { + save(foo); + }, + *"\x00\x01"() { + return "foobar"; + } + } + class bar { + get "\x00\x01"() { + return "bar" + } + set "\x00\x01"(foo) { + save(foo); + } + *"\x00\x01"() { + return "foobar"; + } + } + } + expect_exact: 'var foo={"\\0\\x01":"foo",get"\\0\\x01"(){return"bar"},set"\\0\\x01"(foo){save(foo)},*"\\0\\x01"(){return"foobar"}};class bar{get"\\0\\x01"(){return"bar"}set"\\0\\x01"(foo){save(foo)}*"\\0\\x01"(){return"foobar"}}' +} + +property_with_unprintable_ascii_only_static: { + beautify = { + ascii_only: true + } + input: { + class foo { + static get "\x02\x03"() { + return "bar"; + } + static set "\x04\x05"(foo) { + save(foo); + } + } + } + expect_exact: 'class foo{static get"\\x02\\x03"(){return"bar"}static set"\\x04\\x05"(foo){save(foo)}}' +} + +methods_and_getters_with_keep_quoted_props_enabled: { + beautify = { + quote_style: 3, + keep_quoted_props: true, + } + input: { + var obj = { + a() {}, + "b"() {}, + get c() { return "c"}, + get "d"() { return "d"}, + set e(a) { doSomething(a); }, + set f(a) { doSomething(b); } + } + } + expect_exact: 'var obj={a(){},"b"(){},get c(){return"c"},get"d"(){return"d"},set e(a){doSomething(a)},set f(a){doSomething(b)}};' +} + +allow_assignments_to_property_values: { + input: { + var foo = {123: foo = 123} = {foo: "456"}; + } + expect: { + var foo = {123: foo = 123} = {foo: "456"}; + } +} + +variable_as_computed_property: { + input: { + function getLine(header) { + return { + [header]: {} + }; + } + } + expect_exact: "function getLine(header){return{[header]:{}}}" +} \ No newline at end of file diff --git a/test/compress/parameters.js b/test/compress/parameters.js new file mode 100644 index 00000000..b7844edf --- /dev/null +++ b/test/compress/parameters.js @@ -0,0 +1,168 @@ +arrow_functions: { + input: { + (a) => b; // 1 args + (a, b) => c; // n args + () => b; // 0 args + (a) => (b) => c; // func returns func returns func + (a) => ((b) => c); // So these parens are dropped + () => (b,c) => d; // func returns func returns func + a=>{return b;} + a => 'lel'; // Dropping the parens + } + expect_exact: "a=>b;(a,b)=>c;()=>b;a=>b=>c;a=>b=>c;()=>(b,c)=>d;a=>{return b};a=>\"lel\";" +} + +regression_arrow_functions_and_hoist: { + options = { + hoist_vars: true, + hoist_funs: true + } + input: { + (a) => b; + } + expect_exact: "a=>b;" +} + +regression_assign_arrow_functions: { + input: { + oninstall = e => false; + oninstall = () => false; + } + expect: { + oninstall=e=>false; + oninstall=()=>false; + } +} + +destructuring_arguments_1: { + input: { + (function ( a ) { }); + (function ( [ a ] ) { }); + (function ( [ a, b ] ) { }); + (function ( [ [ a ] ] ) { }); + (function ( [ [ a, b ] ] ) { }); + (function ( [ a, [ b ] ] ) { }); + (function ( [ [ b ], a ] ) { }); + + (function ( { a } ) { }); + (function ( { a, b } ) { }); + + (function ( [ { a } ] ) { }); + (function ( [ { a, b } ] ) { }); + (function ( [ a, { b } ] ) { }); + (function ( [ { b }, a ] ) { }); + + ( [ a ] ) => { }; + ( [ a, b ] ) => { }; + + ( { a } ) => { }; + ( { a, b, c, d, e } ) => { }; + + ( [ a ] ) => b; + ( [ a, b ] ) => c; + + ( { a } ) => b; + ( { a, b } ) => c; + } + expect: { + (function(a){}); + (function([a]){}); + (function([a,b]){}); + (function([[a]]){}); + (function([[a,b]]){}); + (function([a,[b]]){}); + (function([[b],a]){}); + + (function({a}){}); + (function({a,b}){}); + + (function([{a}]){}); + (function([{a,b}]){}); + (function([a,{b}]){}); + (function([{b},a]){}); + + ([a])=>{}; + ([a,b])=>{}; + + ({a})=>{}; + ({a,b,c,d,e})=>{}; + + ([a])=>b; + ([a,b])=>c; + + ({a})=>b; + ({a,b})=>c; + } +} + +destructuring_arguments_2: { + input: { + (function([]) {}); + (function({}) {}); + (function([,,,,,]) {}); + (function ([a, {b: c}]) {}); + (function ([...args]) {}); + (function ({x,}) {}); + class a { *method({ [thrower()]: x } = {}) {}}; + (function(a, b, c, d, [{e: [...f]}]){})(1, 2, 3, 4, [{e: [1, 2, 3]}]); + } + expect: { + (function([]) {}); + (function({}) {}); + (function([,,,,,]) {}); + (function ([a, {b: c}]) {}); + (function ([...args]) {}); + (function ({x,}) {}); + class a { *method({ [thrower()]: x } = {}) {}}; + (function(a, b, c, d, [{e: [...f]}]){})(1, 2, 3, 4, [{e: [1, 2, 3]}]); + } +} + +destructuring_arguments_3: { + beautify = { + ecma: 6 + } + input: { + function fn3({x: {y: {z: {} = 42}}}) {} + const { cover = (function () {}), xCover = (0, function() {}) } = {}; + let { cover = (function () {}), xCover = (0, function() {}) } = {}; + var { cover = (function () {}), xCover = (0, function() {}) } = {}; + } + expect_exact: "function fn3({x:{y:{z:{}=42}}}){}const{cover=function(){},xCover=(0,function(){})}={};let{cover=function(){},xCover=(0,function(){})}={};var{cover=function(){},xCover=(0,function(){})}={};" +} + +default_arguments: { + beautify = { + ecma: 6 + } + input: { + function x(a = 6) { } + function x(a = (6 + 5)) { } + function x({ foo } = {}, [ bar ] = [ 1 ]) { } + } + expect_exact: "function x(a=6){}function x(a=6+5){}function x({foo}={},[bar]=[1]){}" +} + +default_values_in_destructurings: { + beautify = { + ecma: 6 + } + input: { + 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;" +} + +accept_duplicated_parameters_in_non_strict_without_spread_or_default_assignment: { + input: { + function a(b, b){} + function b({c: test, c: test}){} + } + expect: { + function a(b, b){} + function b({c: test, c: test}){} + } +} diff --git a/test/compress/properties.js b/test/compress/properties.js index 29bdfe2a..6eb4c874 100644 --- a/test/compress/properties.js +++ b/test/compress/properties.js @@ -148,7 +148,8 @@ mangle_unquoted_properties: { properties: false } mangle_props = { - ignore_quoted: true + ignore_quoted: true, + reserved: [] } beautify = { beautify: false, diff --git a/test/compress/super.js b/test/compress/super.js new file mode 100644 index 00000000..d297a84e --- /dev/null +++ b/test/compress/super.js @@ -0,0 +1,9 @@ + +super_can_be_parsed: { + input: { + super(1,2); + super.meth(); + } + expect_exact: "super(1,2);super.meth();" +} + diff --git a/test/compress/template-string.js b/test/compress/template-string.js new file mode 100644 index 00000000..4b3acd49 --- /dev/null +++ b/test/compress/template-string.js @@ -0,0 +1,379 @@ +template_strings: { + beautify = { + quote_style: 3 + } + input: { + ``; + `xx\`x`; + `${ foo + 2 }`; + ` foo ${ bar + `baz ${ qux }` }`; + } + expect_exact: "``;`xx\\`x`;`${foo+2}`;` foo ${bar+`baz ${qux}`}`;"; +} + +template_string_prefixes: { + beautify = { + quote_style: 3 + } + input: { + String.raw`foo`; + foo `bar`; + } + expect_exact: "String.raw`foo`;foo`bar`;"; +} + +template_strings_ascii_only: { + beautify = { + ascii_only: true, + quote_style: 3 + } + input: { + var foo = `foo + bar + ↂωↂ`; + var bar = `\``; + } + expect_exact: "var foo=`foo\\n bar\\n \\u2182\\u03c9\\u2182`;var bar=`\\``;" +} + +template_strings_without_ascii_only: { + beautify = { + quote_style: 3 + } + input: { + var foo = `foo + bar + ↂωↂ` + } + expect_exact: "var foo=`foo\n bar\n ↂωↂ`;" +} + +template_string_with_constant_expression: { + options = { + evaluate: true + } + beautify = { + quote_style: 3 + } + input: { + var foo = `${4 + 4} equals 4 + 4`; + } + expect: { + var foo = `8 equals 4 + 4`; + } +} + +template_string_with_predefined_constants: { + options = { + evaluate: true + } + beautify = { + quote_style: 3 + } + input: { + var foo = `This is ${undefined}`; + var bar = `This is ${NaN}`; + var baz = `This is ${null}`; + var foofoo = `This is ${Infinity}`; + var foobar = "This is ${1/0}"; + var foobaz = 'This is ${1/0}'; + var barfoo = "This is ${NaN}"; + var bazfoo = "This is ${null}"; + var bazbaz = `This is ${1/0}`; + var barbar = `This is ${0/0}`; + var barbar = "This is ${0/0}"; + var barber = 'This is ${0/0}'; + + var a = `${4**11}`; // 8 in template vs 7 chars - 4194304 + var b = `${4**12}`; // 8 in template vs 8 chars - 16777216 + var c = `${4**14}`; // 8 in template vs 9 chars - 268435456 + } + expect: { + var foo = `This is undefined`; + var bar = `This is NaN`; + var baz = `This is null`; + var foofoo = `This is ${1/0}`; + var foobar = "This is ${1/0}"; + var foobaz = 'This is ${1/0}'; + var barfoo = "This is ${NaN}"; + var bazfoo = "This is ${null}"; + var bazbaz = `This is ${1/0}`; + var barbar = `This is NaN`; + var barbar = "This is ${0/0}"; + var barber = 'This is ${0/0}'; + + var a = `4194304`; + var b = `16777216`; // Potential for further concatentation + var c = `${4**14}`; // Not worth converting + } +} + +template_string_evaluate_with_many_segments: { + options = { + evaluate: true + } + beautify = { + quote_style: 3 + } + input: { + var foo = `Hello ${guest()}, welcome to ${location()}${"."}`; + var bar = `${1}${2}${3}${4}${5}${6}${7}${8}${9}${0}`; + var baz = `${foobar()}${foobar()}${foobar()}${foobar()}`; + var buzz = `${1}${foobar()}${2}${foobar()}${3}${foobar()}`; + } + expect: { + var foo = `Hello ${guest()}, welcome to ${location()}.`; + var bar = `1234567890`; + var baz = `${foobar()}${foobar()}${foobar()}${foobar()}`; + var buzz = `1${foobar()}2${foobar()}3${foobar()}`; + } +} + +template_string_with_many_segments: { + beautify = { + quote_style: 3 + } + input: { + var foo = `Hello ${guest()}, welcome to ${location()}${"."}`; + var bar = `${1}${2}${3}${4}${5}${6}${7}${8}${9}${0}`; + var baz = `${foobar()}${foobar()}${foobar()}${foobar()}`; + var buzz = `${1}${foobar()}${2}${foobar()}${3}${foobar()}`; + } + expect: { + var foo = `Hello ${guest()}, welcome to ${location()}${"."}`; + var bar = `${1}${2}${3}${4}${5}${6}${7}${8}${9}${0}`; + var baz = `${foobar()}${foobar()}${foobar()}${foobar()}`; + var buzz = `${1}${foobar()}${2}${foobar()}${3}${foobar()}`; + } +} + +template_string_to_normal_string: { + options = { + evaluate: true + } + beautify = { + quote_style: 0 + } + input: { + var foo = `This is ${undefined}`; + var bar = "Decimals " + `${1}${2}${3}${4}${5}${6}${7}${8}${9}${0}`; + } + expect: { + var foo = `This is undefined`; + var bar = "Decimals 1234567890"; + } +} + +template_concattenating_string: { + options = { + evaluate: true + } + beautify = { + quote_style: 3 // Yes, keep quotes + } + input: { + var foo = "Have a nice " + `day. ${`day. ` + `day.`}`; + var bar = "Have a nice " + `${day()}`; + } + expect: { + var foo = "Have a nice day. day. day."; + var bar = "Have a nice " + `${day()}`; + } +} + +evaluate_nested_templates: { + options = { + evaluate: true + } + beautify = { + quote_style: 0 + } + input: { + var baz = `${`${`${`foo`}`}`}`; + } + expect: { + var baz = `foo`; + } +} + +enforce_double_quotes: { + beautify = { + quote_style: 1 + } + input: { + var foo = `Hello world`; + var bar = `Hello ${'world'}`; + var baz = `Hello ${world()}`; + } + expect: { + var foo = `Hello world`; + var bar = `Hello ${"world"}`; + var baz = `Hello ${world()}`; + } +} + +enforce_single_quotes: { + beautify = { + quote_style: 2 + } + input: { + var foo = `Hello world`; + var bar = `Hello ${"world"}`; + var baz = `Hello ${world()}`; + } + expect: { + var foo = `Hello world`; + var bar = `Hello ${'world'}`; + var baz = `Hello ${world()}`; + } +} + +enforce_double_quotes_and_evaluate: { + beautify = { + quote_style: 1 + } + options = { + evaluate: true + } + input: { + var foo = `Hello world`; + var bar = `Hello ${'world'}`; + var baz = `Hello ${world()}`; + } + expect: { + var foo = `Hello world`; + var bar = `Hello world`; + var baz = `Hello ${world()}`; + } +} + +enforce_single_quotes_and_evaluate: { + beautify = { + quote_style: 2 + } + options = { + evaluate: true + } + input: { + var foo = `Hello world`; + var bar = `Hello ${"world"}`; + var baz = `Hello ${world()}`; + } + expect: { + var foo = `Hello world`; + var bar = `Hello world`; + var baz = `Hello ${world()}`; + } +} + +respect_inline_script: { + beautify = { + inline_script: true, + quote_style: 3 + } + input: { + var foo = `${content}`; + var bar = ``; + } + expect_exact: "var foo=`<\\/script>${content}`;var bar=`\\x3c!--`;var baz=`--\\x3e`;"; +} + +do_not_optimize_tagged_template_1: { + beautify = { + quote_style: 0 + } + options = { + evaluate: true + } + input: { + var foo = tag`Shall not be optimized. ${"But " + "this " + "is " + "fine."}`; + var bar = tag`Don't even mind changing my quotes!`; + } + expect_exact: + 'var foo=tag`Shall not be optimized. ${"But this is fine."}`;var bar=tag`Don\'t even mind changing my quotes!`;'; +} + +do_not_optimize_tagged_template_2: { + options = { + evaluate: true + } + input: { + var foo = tag`test` + " something out"; + } + expect_exact: 'var foo=tag`test`+" something out";'; +} + +keep_raw_content_in_tagged_template: { + options = { + evaluate: true + } + input: { + var foo = tag`\u0020\u{20}\u{00020}\x20\40\040 `; + } + expect_exact: "var foo=tag`\\u0020\\u{20}\\u{00020}\\x20\\40\\040 `;"; +} + +allow_chained_templates: { + input: { + var foo = tag`a``b``c``d`; + } + expect: { + var foo = tag`a``b``c``d`; + } +} + +check_escaped_chars: { + input: { + var foo = `\u0020\u{20}\u{00020}\x20\40\040 `; + } + expect_exact: "var foo=` `;"; +} + +escape_dollar_curly: { + options = { + evaluate: true + } + input: { + console.log(`\$\{ beep \}`) + console.log(`${1-0}\${2-0}$\{3-0}${4-0}`) + console.log(`$${""}{not an expression}`) + } + expect_exact: "console.log(`\\${ beep }`);console.log(`1\\${2-0}\\${3-0}4`);console.log(`\\${not an expression}`);" +} + +template_starting_with_newline: { + options = { + dead_code: true + } + input: { + function foo(e) { + return ` +this is a template string!`; + }; + } expect_exact: "function foo(e){return`\nthis is a template string!`}" +} + +template_with_newline: { + options = { + dead_code: true + } + input: { + function foo(e) { + return `yep, +this is a template string!`; + }; + } expect_exact: "function foo(e){return`yep,\nthis is a template string!`}" +} + +template_ending_with_newline: { + options = { + dead_code: true + } + input: { + function foo(e) { + return `this is a template string! +`; + }; + } expect_exact: "function foo(e){return`this is a template string!\n`}" +} diff --git a/test/compress/try-catch.js b/test/compress/try-catch.js new file mode 100644 index 00000000..9c21aaf1 --- /dev/null +++ b/test/compress/try-catch.js @@ -0,0 +1,12 @@ +catch_destructuring_with_sequence: { + beautify = { + ecma: 6 + } + input: { + try { + throw {}; + } catch ({xCover = (0, function() {})} ) { + } + } + expect_exact: "try{throw{}}catch({xCover=(0,function(){})}){}" +} diff --git a/test/compress/unicode.js b/test/compress/unicode.js index 9fb9ab8c..d5853cc6 100644 --- a/test/compress/unicode.js +++ b/test/compress/unicode.js @@ -15,3 +15,106 @@ unicode_parse_variables: { var l০ = 3; } } + +unicode_escaped_identifier: { + beautify = {ecma: 6} + input: { + var \u{61} = "foo"; + var \u{10000} = "bar"; + } + expect_exact: 'var a="foo";var \u{10000}="bar";'; +} + +unicode_identifier_ascii_only: { + beautify = {ascii_only: true, ecma: 6} + input: { + var \u{0061} = "hi"; + var bar = "h\u{0065}llo"; + var \u{10000} = "testing \u{101111}"; + } + expect_exact: 'var a="hi";var bar="hello";var \\u{10000}="testing \\u{101111}";' +} + +unicode_string_literals: { + beautify = {ascii_only: true, ecma: 6} + input: { + var a = "6 length unicode character: \u{101111}"; + } + expect_exact: 'var a="6 length unicode character: \\u{101111}";' +} + +// Don't escape identifiers below es6 (or in this case double escaped in expect_exact) +unicode_output_es5_surrogates: { + beautify = {ascii_only: true, ecma: 5} + input: { + var \u{10000} = "6 length unicode character: \u{10FFFF}"; + } + expect_exact: 'var \u{10000}="6 length unicode character: \\udbff\\udfff";' +} + +check_escape_style: { + beautify = {ascii_only: true, ecma: 6} + input: { + var a = "\x01"; + var \ua0081 = "\x10"; // \u0081 only in ID_Continue + var \u0100 = "\u0100"; + var \u1000 = "\u1000"; + var \u{10000} = "\u{10000}"; + var \u{2f800} = "\u{100000}"; + } + expect_exact: 'var a="\\x01";var \\ua0081="\\x10";var \\u0100="\\u0100";var \\u1000="\\u1000";var \\u{10000}="\\u{10000}";var \\u{2f800}="\\u{100000}";' +} + +// Don't escape identifiers below es6, no escaped identifiers support and no \u{} syntax +check_escape_style_es5: { + beautify = {ascii_only: true, ecma: 5} + input: { + var a = "\x01"; + var \ua0081 = "\x10"; // \u0081 only in ID_Continue + var \u0100 = "\u0100"; + var \u1000 = "\u1000"; + var \u{10000} = "\u{10000}"; // Identifier won't be escaped in es 5.1 + var \u{2f800} = "\u{100000}"; // Same + } + expect_exact: 'var a="\\x01";var \\ua0081="\\x10";var \\u0100="\\u0100";var \\u1000="\\u1000";var \ud800\udc00="\\ud800\\udc00";var \ud87e\udc00="\\udbc0\\udc00";' +} + +ID_continue_with_surrogate_pair: { + beautify = {ascii_only: true, ecma: 6} + input: { + var \u{2f800}\u{2f800}\u{2f800}\u{2f800} = "\u{100000}\u{100000}\u{100000}\u{100000}\u{100000}"; + } + expect_exact: 'var \\u{2f800}\\u{2f800}\\u{2f800}\\u{2f800}="\\u{100000}\\u{100000}\\u{100000}\\u{100000}\\u{100000}";' +} + +escape_non_escaped_identifier: { + beautify = {ascii_only: true, ecma: 6} + input: { + var µþ = "µþ"; + } + expect_exact: 'var \\u00b5\\u00fe="\\xb5\\xfe";' +} + +non_escape_2_non_escape: { + beautify = {ascii_only: false, ecma: 6} + input: { + var µþ = "µþ"; + } + expect_exact: 'var µþ="µþ";' +} + +non_escape_2_half_escape1: { + beautify = {ascii_only: false, ascii_identifiers: true, ecma: 6} + input: { + var µþ = "µþ"; + } + expect_exact: 'var \\u00b5\\u00fe="µþ";' +} + +non_escape_2_half_escape2: { + beautify = {ascii_only: true, ascii_identifiers: false, ecma: 6} + input: { + var µþ = "µþ"; + } + expect_exact: 'var µþ="\\xb5\\xfe";' +} \ No newline at end of file diff --git a/test/compress/yield.js b/test/compress/yield.js new file mode 100644 index 00000000..d1e7ece3 --- /dev/null +++ b/test/compress/yield.js @@ -0,0 +1,192 @@ +generators: { + input: { + function* fn() {}; + } + expect_exact: "function*fn(){}" +} + +generators_yield: { + input: { + function* fn() { + yield remote(); + } + } + expect_exact: "function*fn(){yield remote()}" +} + +generators_yield_assign: { + input: { + function* fn() { + var x = {}; + x.prop = yield 5; + } + } + expect_exact: "function*fn(){var x={};x.prop=yield 5}" +} + +generator_yield_undefined: { + input: { + function* fn() { + yield; + } + } + expect_exact: "function*fn(){yield}" +} + +yield_optimize_expression: { + options = { + } + input: { + function* f1() { yield; } + function* f2() { yield undefined; } + function* f3() { yield null; } + function* f4() { yield* undefined; } + } + expect: { + function* f1() { yield } + function* f2() { yield; } + function* f3() { yield null; } + function* f4() { yield* void 0; } + } +} + +yield_statements: { + input: { + function* fn() { + var a = (yield 1) + (yield 2); + var b = (yield 3) === (yield 4); + var c = (yield 5) << (yield 6); + var d = yield 7; + var e = (yield 8) ? yield 9 : yield 10; + var f = -(yield 11); + } + } + expect_exact: "function*fn(){var a=(yield 1)+(yield 2);var b=(yield 3)===(yield 4);var c=(yield 5)<<(yield 6);var d=yield 7;var e=(yield 8)?yield 9:yield 10;var f=-(yield 11)}" +} + +yield_as_identifier_in_function_in_generator: { + input: { + var g = function*() { + function h() { + yield = 1; + } + }; + } + expect: { + var g = function*() { + function h() { + yield = 1; + } + }; + } +} + +yield_before_punctuators: { + input: { + iter = (function*() { + assignmentResult = [ x = yield ] = value; + })(); + function* g1() { (yield) } + function* g2() { [yield] } + function* g3() { return {yield} } // Added return to avoid {} drop + function* g4() { yield, yield; } + function* g5() { (yield) ? yield : yield; } + } + expect: { + iter = (function*() { + assignmentResult = [ x = yield ] = value; + })(); + function* g1() { (yield) } + function* g2() { [yield] } + function* g3() { return {yield} } + function* g4() { yield, yield; } + function* g5() { (yield) ? yield : yield; } + } +} + +yield_as_identifier_outside_strict_mode: { + input: { + import yield from "bar"; + yield = 123; + while (true) { + yield: + for(;;) break yield; + + foo(); + } + while (true) + yield: for(;;) continue yield; + function yield(){} + function foo(...yield){} + try { new Error("") } catch (yield) {} + var yield = "foo"; + } + expect: { + import yield from "bar"; + yield = 123; + while (true) { + yield: + for(;;) break yield; + + foo(); + } + while (true) + yield: for(;;) continue yield; + function yield(){} + function foo(...yield){} + try { new Error("") } catch (yield) {} + var yield = "foo"; + } +} + +empty_generator_as_parameter_with_side_effects: { + options = { + side_effects: true + } + input: { + var GeneratorPrototype = Object.getPrototypeOf( + Object.getPrototypeOf(function*() {}()) + ); + evaluate(GeneratorPrototype); + } + expect_exact: "var GeneratorPrototype=Object.getPrototypeOf(Object.getPrototypeOf(function*(){}()));evaluate(GeneratorPrototype);" +} + +empty_generator_as_parameter_without_side_effects: { + options = { + side_effects: false + } + input: { + var GeneratorPrototype = Object.getPrototypeOf( + Object.getPrototypeOf(function*() {}()) + ); + evaluate(GeneratorPrototype); + } + expect_exact: "var GeneratorPrototype=Object.getPrototypeOf(Object.getPrototypeOf(function*(){}()));evaluate(GeneratorPrototype);" +} + +yield_dot: { + options = { + } + input: { + function* foo(){ + yield x.foo; + (yield x).foo; + yield (yield obj.foo()).bar(); + } + } + expect_exact: "function*foo(){yield x.foo;(yield x).foo;yield(yield obj.foo()).bar()}" +} + +yield_sub: { + options = { + } + input: { + function* foo(){ + yield x['foo']; + (yield x)['foo']; + yield (yield obj.foo())['bar'](); + } + } + expect_exact: 'function*foo(){yield x["foo"];(yield x)["foo"];yield(yield obj.foo())["bar"]()}' +} diff --git a/test/mocha/arguments.js b/test/mocha/arguments.js index 73993a73..e43be0e4 100644 --- a/test/mocha/arguments.js +++ b/test/mocha/arguments.js @@ -27,4 +27,253 @@ describe("arguments", function() { assert.strictEqual(ast.body[0].body[0].uses_arguments, true); assert.strictEqual(ast.body[0].body[0].body[0].uses_arguments, false); }); -}); \ No newline at end of file + + it("Should parse a function containing default assignment correctly", function() { + var ast = UglifyJS.parse("function foo(a = 123) {}"); + assert(ast.body[0] instanceof UglifyJS.AST_Defun); + assert.strictEqual(ast.body[0].argnames.length, 1); + + // First argument + assert(ast.body[0].argnames[0] instanceof UglifyJS.AST_DefaultAssign); + assert(ast.body[0].argnames[0].left instanceof UglifyJS.AST_SymbolFunarg); + assert.strictEqual(ast.body[0].argnames[0].operator, "="); + assert(ast.body[0].argnames[0].right instanceof UglifyJS.AST_Number); + + ast = UglifyJS.parse("function foo(a = a) {}"); + assert(ast.body[0] instanceof UglifyJS.AST_Defun); + assert.strictEqual(ast.body[0].argnames.length, 1); + + // First argument + assert(ast.body[0].argnames[0] instanceof UglifyJS.AST_DefaultAssign); + assert(ast.body[0].argnames[0].left instanceof UglifyJS.AST_SymbolFunarg); + assert.strictEqual(ast.body[0].argnames[0].operator, "="); + assert(ast.body[0].argnames[0].right instanceof UglifyJS.AST_SymbolRef); + }); + + it("Should parse a function containing default assignments in destructuring correctly", function() { + var ast = UglifyJS.parse("function foo([a = 123]) {}"); + assert(ast.body[0] instanceof UglifyJS.AST_Defun); + assert.strictEqual(ast.body[0].argnames.length, 1); + + // First argument + assert(ast.body[0].argnames[0] instanceof UglifyJS.AST_Destructuring); + assert.strictEqual(ast.body[0].argnames[0].is_array, true); + assert.strictEqual(ast.body[0].argnames[0].names.length, 1); + assert(ast.body[0].argnames[0].names[0] instanceof UglifyJS.AST_DefaultAssign); + assert(ast.body[0].argnames[0].names[0].left instanceof UglifyJS.AST_SymbolFunarg); + assert.strictEqual(ast.body[0].argnames[0].names[0].operator, "="); + assert(ast.body[0].argnames[0].names[0].right instanceof UglifyJS.AST_Number); + + + ast = UglifyJS.parse("function foo({a = 123}) {}"); + assert(ast.body[0] instanceof UglifyJS.AST_Defun); + assert.strictEqual(ast.body[0].argnames.length, 1); + + // First argument + assert(ast.body[0].argnames[0] instanceof UglifyJS.AST_Destructuring); + assert.strictEqual(ast.body[0].argnames[0].is_array, false); + assert.strictEqual(ast.body[0].argnames[0].names.length, 1); + assert(ast.body[0].argnames[0].names[0] instanceof UglifyJS.AST_ObjectKeyVal); + assert.strictEqual(ast.body[0].argnames[0].names[0].key, "a"); + + // Property a of first argument + assert(ast.body[0].argnames[0].names[0].value instanceof UglifyJS.AST_DefaultAssign); + assert(ast.body[0].argnames[0].names[0].value.left instanceof UglifyJS.AST_SymbolFunarg); + assert.strictEqual(ast.body[0].argnames[0].names[0].value.operator, "="); + assert(ast.body[0].argnames[0].names[0].value.right instanceof UglifyJS.AST_Number); + + + ast = UglifyJS.parse("function foo({a: a = 123}) {}"); + assert(ast.body[0] instanceof UglifyJS.AST_Defun); + assert.strictEqual(ast.body[0].argnames.length, 1); + + // First argument + assert(ast.body[0].argnames[0] instanceof UglifyJS.AST_Destructuring); + assert.strictEqual(ast.body[0].argnames[0].is_array, false); + assert.strictEqual(ast.body[0].argnames[0].names.length, 1); + + // Content destructuring of first argument + assert(ast.body[0].argnames[0].names[0] instanceof UglifyJS.AST_ObjectKeyVal); + assert.strictEqual(ast.body[0].argnames[0].names[0].key, "a"); + assert(ast.body[0].argnames[0].names[0].value instanceof UglifyJS.AST_DefaultAssign); + + // Property a of first argument + assert(ast.body[0].argnames[0].names[0].value instanceof UglifyJS.AST_DefaultAssign); + assert(ast.body[0].argnames[0].names[0].value.left instanceof UglifyJS.AST_SymbolFunarg); + assert.strictEqual(ast.body[0].argnames[0].names[0].value.operator, "="); + assert(ast.body[0].argnames[0].names[0].value.right instanceof UglifyJS.AST_Number); + }); + + it("Should parse a function containing default assignments in complex destructuring correctly", function() { + var ast = UglifyJS.parse("function foo([a, [b = 123]]){}"); + assert(ast.body[0] instanceof UglifyJS.AST_Defun); + assert.strictEqual(ast.body[0].argnames.length, 1); + + // Check first argument + assert(ast.body[0].argnames[0] instanceof UglifyJS.AST_Destructuring); + assert.strictEqual(ast.body[0].argnames[0].is_array, true); + assert.strictEqual(ast.body[0].argnames[0].names.length, 2); + + // Check whole destructuring structure of first argument + assert(ast.body[0].argnames[0].names[0] instanceof UglifyJS.AST_SymbolFunarg); + assert(ast.body[0].argnames[0].names[1] instanceof UglifyJS.AST_Destructuring); + assert.strictEqual(ast.body[0].argnames[0].names[1].is_array, true); + + // Check content of second destructuring element (which is the nested destructuring pattern) + assert(ast.body[0].argnames[0].names[1].names[0] instanceof UglifyJS.AST_DefaultAssign); + assert(ast.body[0].argnames[0].names[1].names[0].left instanceof UglifyJS.AST_SymbolFunarg); + assert.strictEqual(ast.body[0].argnames[0].names[1].names[0].operator, "="); + assert(ast.body[0].argnames[0].names[1].names[0].right instanceof UglifyJS.AST_Number); + + + ast = UglifyJS.parse("function foo([a, {b: c = 123}]){}"); + assert(ast.body[0] instanceof UglifyJS.AST_Defun); + assert.strictEqual(ast.body[0].argnames.length, 1); + + // Check first argument + assert(ast.body[0].argnames[0] instanceof UglifyJS.AST_Destructuring); + assert.strictEqual(ast.body[0].argnames[0].is_array, true); + assert.strictEqual(ast.body[0].argnames[0].names.length, 2); + + // Check whole destructuring structure of first argument + assert(ast.body[0].argnames[0].names[0] instanceof UglifyJS.AST_SymbolFunarg); + assert(ast.body[0].argnames[0].names[1] instanceof UglifyJS.AST_Destructuring); + assert.strictEqual(ast.body[0].argnames[0].names[1].is_array, false); + + // Check content of second destructuring element (which is the nested destructuring pattern) + assert(ast.body[0].argnames[0].names[1].names[0] instanceof UglifyJS.AST_ObjectKeyVal); + assert.strictEqual(ast.body[0].argnames[0].names[1].names[0].key, "b"); + assert(ast.body[0].argnames[0].names[1].names[0].value instanceof UglifyJS.AST_DefaultAssign); + + // Property b of second argument + assert(ast.body[0].argnames[0].names[1].names[0].value instanceof UglifyJS.AST_DefaultAssign); + assert(ast.body[0].argnames[0].names[1].names[0].value.left instanceof UglifyJS.AST_SymbolFunarg); + assert.strictEqual(ast.body[0].argnames[0].names[1].names[0].value.operator, "="); + assert(ast.body[0].argnames[0].names[1].names[0].value.right instanceof UglifyJS.AST_Number); + + + ast = UglifyJS.parse("function foo({a, b: {b = 123}}){}"); + assert(ast.body[0] instanceof UglifyJS.AST_Defun); + assert.strictEqual(ast.body[0].argnames.length, 1); + + // Check first argument + assert(ast.body[0].argnames[0] instanceof UglifyJS.AST_Destructuring); + assert.strictEqual(ast.body[0].argnames[0].is_array, false); + assert.strictEqual(ast.body[0].argnames[0].names.length, 2); + + // Check whole destructuring structure of first argument + assert(ast.body[0].argnames[0].names[0] instanceof UglifyJS.AST_ObjectKeyVal); + assert.strictEqual(ast.body[0].argnames[0].names[0].key, "a"); + assert(ast.body[0].argnames[0].names[0].value instanceof UglifyJS.AST_SymbolFunarg); + assert(ast.body[0].argnames[0].names[1] instanceof UglifyJS.AST_ObjectKeyVal); + assert.strictEqual(ast.body[0].argnames[0].names[1].key, "b"); + assert(ast.body[0].argnames[0].names[1].value instanceof UglifyJS.AST_Destructuring); + + // Check content of nested destructuring in first parameter + var content = ast.body[0].argnames[0].names[1].value + assert.strictEqual(content.is_array, false); + assert.strictEqual(content.names.length, 1); + assert(content.names[0] instanceof UglifyJS.AST_ObjectKeyVal); + assert.strictEqual(content.names[0].key, "b"); + assert(content.names[0].value instanceof UglifyJS.AST_DefaultAssign); + assert(content.names[0].value.left instanceof UglifyJS.AST_SymbolFunarg); + assert.strictEqual(content.names[0].value.operator, "="); + assert(content.names[0].value.right instanceof UglifyJS.AST_Number); + + + ast = UglifyJS.parse("function foo({a: {b = 123}}){}"); + assert(ast.body[0] instanceof UglifyJS.AST_Defun); + assert.strictEqual(ast.body[0].argnames.length, 1); + + // Check first argument + assert(ast.body[0].argnames[0] instanceof UglifyJS.AST_Destructuring); + assert.strictEqual(ast.body[0].argnames[0].is_array, false); + assert.strictEqual(ast.body[0].argnames[0].names.length, 1); + + // Check whole destructuring structure of first argument + assert(ast.body[0].argnames[0].names[0] instanceof UglifyJS.AST_ObjectKeyVal); + assert.strictEqual(ast.body[0].argnames[0].names[0].key, "a"); + assert(ast.body[0].argnames[0].names[0].value instanceof UglifyJS.AST_Destructuring); + + // Check content of nested destructuring + content = ast.body[0].argnames[0].names[0].value + assert.strictEqual(content.is_array, false); + assert.strictEqual(content.names.length, 1); + assert(content.names[0] instanceof UglifyJS.AST_ObjectKeyVal); + assert.strictEqual(content.names[0].key, "b"); + assert(content.names[0].value instanceof UglifyJS.AST_DefaultAssign); + assert(content.names[0].value.left instanceof UglifyJS.AST_SymbolFunarg); + assert.strictEqual(content.names[0].value.operator, "="); + assert(content.names[0].value.right instanceof UglifyJS.AST_Number); + }); + + it("Should parse spread correctly", function() { + var ast = UglifyJS.parse("function foo(a, b, ...c){}"); + assert(ast.body[0] instanceof UglifyJS.AST_Defun); + assert.strictEqual(ast.body[0].argnames.length, 3); + + // Check parameters + assert(ast.body[0].argnames[0] instanceof UglifyJS.AST_SymbolFunarg); + assert(ast.body[0].argnames[1] instanceof UglifyJS.AST_SymbolFunarg); + assert(ast.body[0].argnames[2] instanceof UglifyJS.AST_Expansion); + assert(ast.body[0].argnames[2].expression instanceof UglifyJS.AST_SymbolFunarg); + + + ast = UglifyJS.parse("function foo([a, b, ...c]){}"); + assert(ast.body[0] instanceof UglifyJS.AST_Defun); + assert.strictEqual(ast.body[0].argnames.length, 1); + + // Check first parameter + assert(ast.body[0].argnames[0] instanceof UglifyJS.AST_Destructuring); + assert.strictEqual(ast.body[0].argnames[0].is_array, true); + + // Check content first parameter + assert(ast.body[0].argnames[0].names[0] instanceof UglifyJS.AST_SymbolFunarg); + assert(ast.body[0].argnames[0].names[1] instanceof UglifyJS.AST_SymbolFunarg); + assert(ast.body[0].argnames[0].names[2] instanceof UglifyJS.AST_Expansion); + assert(ast.body[0].argnames[0].names[2].expression instanceof UglifyJS.AST_SymbolFunarg); + + + ast = UglifyJS.parse("function foo([a, b, [c, ...d]]){}"); + assert(ast.body[0] instanceof UglifyJS.AST_Defun); + assert.strictEqual(ast.body[0].argnames.length, 1); + + // Check first parameter + assert(ast.body[0].argnames[0] instanceof UglifyJS.AST_Destructuring); + assert.strictEqual(ast.body[0].argnames[0].is_array, true); + + // Check content outer destructuring array + assert(ast.body[0].argnames[0].names[0] instanceof UglifyJS.AST_SymbolFunarg); + assert(ast.body[0].argnames[0].names[1] instanceof UglifyJS.AST_SymbolFunarg); + assert(ast.body[0].argnames[0].names[2] instanceof UglifyJS.AST_Destructuring); + assert.strictEqual(ast.body[0].argnames[0].names[2].is_array, true); + + // Check content nested destructuring array + assert.strictEqual(ast.body[0].argnames[0].names[2].names.length, 2); + assert(ast.body[0].argnames[0].names[2].names[0] instanceof UglifyJS.AST_SymbolFunarg); + assert(ast.body[0].argnames[0].names[2].names[1] instanceof UglifyJS.AST_Expansion); + assert(ast.body[0].argnames[0].names[2].names[1].expression instanceof UglifyJS.AST_SymbolFunarg); + + + ast = UglifyJS.parse("function foo({a: [b, ...c]}){}"); + assert(ast.body[0] instanceof UglifyJS.AST_Defun); + assert.strictEqual(ast.body[0].argnames.length, 1); + + // Check first parameter + assert(ast.body[0].argnames[0] instanceof UglifyJS.AST_Destructuring); + assert.strictEqual(ast.body[0].argnames[0].is_array, false); + + // Check outer destructuring object + assert.strictEqual(ast.body[0].argnames[0].names.length, 1); + assert(ast.body[0].argnames[0].names[0] instanceof UglifyJS.AST_ObjectKeyVal); + assert.strictEqual(ast.body[0].argnames[0].names[0].key, "a"); + assert(ast.body[0].argnames[0].names[0].value instanceof UglifyJS.AST_Destructuring); + assert.strictEqual(ast.body[0].argnames[0].names[0].value.is_array, true); + + // Check content nested destructuring array + assert.strictEqual(ast.body[0].argnames[0].names[0].value.names.length, 2); + assert(ast.body[0].argnames[0].names[0].value.names[0] instanceof UglifyJS.AST_SymbolFunarg); + assert(ast.body[0].argnames[0].names[0].value.names[1] instanceof UglifyJS.AST_Expansion); + assert(ast.body[0].argnames[0].names[0].value.names[1].expression instanceof UglifyJS.AST_SymbolFunarg); + }); +}); diff --git a/test/mocha/arrow.js b/test/mocha/arrow.js new file mode 100644 index 00000000..1ee6d403 --- /dev/null +++ b/test/mocha/arrow.js @@ -0,0 +1,385 @@ +var assert = require("assert"); +var uglify = require("../../"); + +describe("Arrow functions", function() { + it("Should not accept spread tokens on non-last parameters or without arguments parentheses", function() { + var tests = [ + "var a = ...a => {return a.join()}", + "var b = (a, ...b, c) => { return a + b.join() + c}", + "var c = (...a, b) => a.join()" + ]; + var test = function(code) { + return function() { + uglify.parse(code, {fromString: true}); + } + } + var error = function(e) { + return e instanceof uglify.JS_Parse_Error && + e.message === "SyntaxError: Unexpected token: expand (...)"; + } + + for (var i = 0; i < tests.length; i++) { + assert.throws(test(tests[i]), error); + } + }); + it("Should not accept holes in object binding patterns, while still allowing a trailing elision", function() { + var tests = [ + "f = ({, , ...x} = [1, 2]) => {};" + ]; + var test = function(code) { + return function() { + uglify.parse(code, {fromString: true}); + } + } + var error = function(e) { + return e instanceof uglify.JS_Parse_Error && + e.message === "SyntaxError: Unexpected token: punc (,)"; + } + + for (var i = 0; i < tests.length; i++) { + assert.throws(test(tests[i]), error); + } + }); + it("Should not accept newlines before arrow token", function() { + var tests = [ + "f = foo\n=> 'foo';", + "f = (foo, bar)\n=> 'foo';", + "f = ()\n=> 'foo';", + "foo((bar)\n=>'baz';);" + ]; + var test = function(code) { + return function() { + uglify.parse(code, {fromString: true}); + } + } + var error = function(e) { + return e instanceof uglify.JS_Parse_Error && + e.message === "SyntaxError: Unexpected newline before arrow (=>)"; + } + + for (var i = 0; i < tests.length; i++) { + assert.throws(test(tests[i]), error); + } + }); + it("Should not accept arrow functions in the middle or end of an expression", function() { + var tests = [ + "typeof x => 0", + "0 + x => 0" + ]; + var test = function(code) { + return function() { + uglify.parse(code, {fromString: true}); + } + } + var error = function(e) { + return e instanceof uglify.JS_Parse_Error && + e.message === "SyntaxError: Unexpected token: arrow (=>)"; + } + + for (var i = 0; i < tests.length; i++) { + assert.throws(test(tests[i]), error); + } + }); + + it("Should parse a function containing default assignment correctly", function() { + var ast = uglify.parse("var a = (a = 123) => {}"); + assert(ast.body[0] instanceof uglify.AST_Var); + assert.strictEqual(ast.body[0].definitions.length, 1); + assert(ast.body[0].definitions[0] instanceof uglify.AST_VarDef); + assert(ast.body[0].definitions[0].name instanceof uglify.AST_SymbolVar); + assert(ast.body[0].definitions[0].value instanceof uglify.AST_Arrow); + assert.strictEqual(ast.body[0].definitions[0].value.argnames.length, 1); + + // First argument + assert(ast.body[0].definitions[0].value.argnames[0] instanceof uglify.AST_DefaultAssign); + assert(ast.body[0].definitions[0].value.argnames[0].left instanceof uglify.AST_SymbolFunarg); + assert.strictEqual(ast.body[0].definitions[0].value.argnames[0].operator, "="); + assert(ast.body[0].definitions[0].value.argnames[0].right instanceof uglify.AST_Number); + + ast = uglify.parse("var a = (a = a) => {}"); + assert(ast.body[0] instanceof uglify.AST_Var); + assert.strictEqual(ast.body[0].definitions.length, 1); + assert(ast.body[0].definitions[0] instanceof uglify.AST_VarDef); + assert(ast.body[0].definitions[0].name instanceof uglify.AST_SymbolVar); + assert(ast.body[0].definitions[0].value instanceof uglify.AST_Arrow); + assert.strictEqual(ast.body[0].definitions[0].value.argnames.length, 1); + + // First argument + assert(ast.body[0].definitions[0].value.argnames[0] instanceof uglify.AST_DefaultAssign); + assert(ast.body[0].definitions[0].value.argnames[0].left instanceof uglify.AST_SymbolFunarg); + assert.strictEqual(ast.body[0].definitions[0].value.argnames[0].operator, "="); + assert(ast.body[0].definitions[0].value.argnames[0].right instanceof uglify.AST_SymbolRef); + }); + + it("Should parse a function containing default assignments in destructuring correctly", function() { + var ast = uglify.parse("var a = ([a = 123]) => {}"); + assert(ast.body[0] instanceof uglify.AST_Var); + assert.strictEqual(ast.body[0].definitions.length, 1); + assert(ast.body[0].definitions[0] instanceof uglify.AST_VarDef); + assert(ast.body[0].definitions[0].name instanceof uglify.AST_SymbolVar); + assert(ast.body[0].definitions[0].value instanceof uglify.AST_Arrow); + assert.strictEqual(ast.body[0].definitions[0].value.argnames.length, 1); + + // First argument + assert(ast.body[0].definitions[0].value.argnames[0] instanceof uglify.AST_Destructuring); + assert.strictEqual(ast.body[0].definitions[0].value.argnames[0].is_array, true); + assert.strictEqual(ast.body[0].definitions[0].value.argnames[0].names.length, 1); + assert(ast.body[0].definitions[0].value.argnames[0].names[0] instanceof uglify.AST_DefaultAssign); + assert(ast.body[0].definitions[0].value.argnames[0].names[0].left instanceof uglify.AST_SymbolFunarg); + assert.strictEqual(ast.body[0].definitions[0].value.argnames[0].names[0].operator, "="); + assert(ast.body[0].definitions[0].value.argnames[0].names[0].right instanceof uglify.AST_Number); + + + ast = uglify.parse("var a = ({a = 123}) => {}"); + assert(ast.body[0] instanceof uglify.AST_Var); + assert.strictEqual(ast.body[0].definitions.length, 1); + assert(ast.body[0].definitions[0] instanceof uglify.AST_VarDef); + assert(ast.body[0].definitions[0].name instanceof uglify.AST_SymbolVar); + assert(ast.body[0].definitions[0].value instanceof uglify.AST_Arrow); + assert.strictEqual(ast.body[0].definitions[0].value.argnames.length, 1); + + // First argument + assert(ast.body[0].definitions[0].value.argnames[0] instanceof uglify.AST_Destructuring); + assert.strictEqual(ast.body[0].definitions[0].value.argnames[0].is_array, false); + assert.strictEqual(ast.body[0].definitions[0].value.argnames[0].names.length, 1); + assert(ast.body[0].definitions[0].value.argnames[0].names[0] instanceof uglify.AST_ObjectKeyVal); + + // First object element in first argument + assert.strictEqual(ast.body[0].definitions[0].value.argnames[0].names[0].key, "a"); + assert(ast.body[0].definitions[0].value.argnames[0].names[0].value instanceof uglify.AST_DefaultAssign); + assert(ast.body[0].definitions[0].value.argnames[0].names[0].value.left instanceof uglify.AST_SymbolFunarg); + assert.strictEqual(ast.body[0].definitions[0].value.argnames[0].names[0].value.operator, "="); + assert(ast.body[0].definitions[0].value.argnames[0].names[0].value.right instanceof uglify.AST_Number); + + + ast = uglify.parse("var a = ({a: a = 123}) => {}"); + assert(ast.body[0] instanceof uglify.AST_Var); + assert.strictEqual(ast.body[0].definitions.length, 1); + assert(ast.body[0].definitions[0] instanceof uglify.AST_VarDef); + assert(ast.body[0].definitions[0].name instanceof uglify.AST_SymbolVar); + assert(ast.body[0].definitions[0].value instanceof uglify.AST_Arrow); + assert.strictEqual(ast.body[0].definitions[0].value.argnames.length, 1); + + // First argument + assert(ast.body[0].definitions[0].value.argnames[0] instanceof uglify.AST_Destructuring); + assert.strictEqual(ast.body[0].definitions[0].value.argnames[0].is_array, false); + assert.strictEqual(ast.body[0].definitions[0].value.argnames[0].names.length, 1); + + // Content destructuring of first argument + assert(ast.body[0].definitions[0].value.argnames[0].names[0] instanceof uglify.AST_ObjectProperty); + assert.strictEqual(ast.body[0].definitions[0].value.argnames[0].names[0].key, "a"); + assert(ast.body[0].definitions[0].value.argnames[0].names[0].value instanceof uglify.AST_DefaultAssign); + assert(ast.body[0].definitions[0].value.argnames[0].names[0].value.left instanceof uglify.AST_SymbolFunarg); + assert.strictEqual(ast.body[0].definitions[0].value.argnames[0].names[0].value.operator, "="); + assert(ast.body[0].definitions[0].value.argnames[0].names[0].value.right instanceof uglify.AST_Number); + }); + + it("Should parse a function containing default assignments in complex destructuring correctly", function() { + var ast = uglify.parse("var a = ([a, [b = 123]]) => {}"); + assert(ast.body[0] instanceof uglify.AST_Var); + assert.strictEqual(ast.body[0].definitions.length, 1); + assert(ast.body[0].definitions[0] instanceof uglify.AST_VarDef); + assert(ast.body[0].definitions[0].name instanceof uglify.AST_SymbolVar); + assert(ast.body[0].definitions[0].value instanceof uglify.AST_Arrow); + assert.strictEqual(ast.body[0].definitions[0].value.argnames.length, 1); + + // Check first argument + assert(ast.body[0].definitions[0].value.argnames[0] instanceof uglify.AST_Destructuring); + assert.strictEqual(ast.body[0].definitions[0].value.argnames[0].is_array, true); + assert.strictEqual(ast.body[0].definitions[0].value.argnames[0].names.length, 2); + + // Check whole destructuring structure of first argument + assert(ast.body[0].definitions[0].value.argnames[0].names[0] instanceof uglify.AST_SymbolFunarg); + assert(ast.body[0].definitions[0].value.argnames[0].names[1] instanceof uglify.AST_Destructuring); + assert.strictEqual(ast.body[0].definitions[0].value.argnames[0].names[1].is_array, true); + + // Check content of second destructuring element (which is the nested destructuring pattern) + assert(ast.body[0].definitions[0].value.argnames[0].names[1].names[0] instanceof uglify.AST_DefaultAssign); + assert(ast.body[0].definitions[0].value.argnames[0].names[1].names[0].left instanceof uglify.AST_SymbolFunarg); + assert.strictEqual(ast.body[0].definitions[0].value.argnames[0].names[1].names[0].operator, "="); + assert(ast.body[0].definitions[0].value.argnames[0].names[1].names[0].right instanceof uglify.AST_Number); + + + ast = uglify.parse("var a = ([a, {b: c = 123}]) => {}"); + assert(ast.body[0] instanceof uglify.AST_Var); + assert.strictEqual(ast.body[0].definitions.length, 1); + assert(ast.body[0].definitions[0] instanceof uglify.AST_VarDef); + assert(ast.body[0].definitions[0].name instanceof uglify.AST_SymbolVar); + assert(ast.body[0].definitions[0].value instanceof uglify.AST_Arrow); + assert.strictEqual(ast.body[0].definitions[0].value.argnames.length, 1); + + // Check first argument + assert(ast.body[0].definitions[0].value.argnames[0] instanceof uglify.AST_Destructuring); + assert.strictEqual(ast.body[0].definitions[0].value.argnames[0].is_array, true); + assert.strictEqual(ast.body[0].definitions[0].value.argnames[0].names.length, 2); + + // Check whole destructuring structure of first argument + assert(ast.body[0].definitions[0].value.argnames[0].names[0] instanceof uglify.AST_SymbolFunarg); + assert(ast.body[0].definitions[0].value.argnames[0].names[1] instanceof uglify.AST_Destructuring); + assert.strictEqual(ast.body[0].definitions[0].value.argnames[0].names[1].is_array, false); + + // Check content of second destructuring element (which is the nested destructuring pattern) + assert(ast.body[0].definitions[0].value.argnames[0].names[1].names[0] instanceof uglify.AST_ObjectKeyVal); + assert.strictEqual(ast.body[0].definitions[0].value.argnames[0].names[1].names[0].key, "b"); + assert(ast.body[0].definitions[0].value.argnames[0].names[1].names[0].value instanceof uglify.AST_DefaultAssign); + assert(ast.body[0].definitions[0].value.argnames[0].names[1].names[0].value.left instanceof uglify.AST_SymbolFunarg); + assert.strictEqual(ast.body[0].definitions[0].value.argnames[0].names[1].names[0].value.operator, "="); + assert(ast.body[0].definitions[0].value.argnames[0].names[1].names[0].value.right instanceof uglify.AST_Number); + + + ast = uglify.parse("var a = ({a, b: {b = 123}}) => {}"); + assert(ast.body[0] instanceof uglify.AST_Var); + assert.strictEqual(ast.body[0].definitions.length, 1); + assert(ast.body[0].definitions[0] instanceof uglify.AST_VarDef); + assert(ast.body[0].definitions[0].name instanceof uglify.AST_SymbolVar); + assert(ast.body[0].definitions[0].value instanceof uglify.AST_Arrow); + assert.strictEqual(ast.body[0].definitions[0].value.argnames.length, 1); + + // Check first argument + assert(ast.body[0].definitions[0].value.argnames[0] instanceof uglify.AST_Destructuring); + assert.strictEqual(ast.body[0].definitions[0].value.argnames[0].is_array, false); + assert.strictEqual(ast.body[0].definitions[0].value.argnames[0].names.length, 2); + + // First argument, property 1 + assert(ast.body[0].definitions[0].value.argnames[0].names[0] instanceof uglify.AST_ObjectKeyVal); + assert.strictEqual(ast.body[0].definitions[0].value.argnames[0].names[0].key, "a"); + assert(ast.body[0].definitions[0].value.argnames[0].names[0].value instanceof uglify.AST_SymbolFunarg); + + // First argument, property 2 + assert(ast.body[0].definitions[0].value.argnames[0].names[1] instanceof uglify.AST_ObjectKeyVal); + assert.strictEqual(ast.body[0].definitions[0].value.argnames[0].names[1].key, "b"); + assert(ast.body[0].definitions[0].value.argnames[0].names[1].value instanceof uglify.AST_Destructuring); + + // Check content of nested destructuring + var content = ast.body[0].definitions[0].value.argnames[0].names[1].value + assert.strictEqual(content.is_array, false); + assert.strictEqual(content.names.length, 1); + assert(content.names[0] instanceof uglify.AST_ObjectKeyVal); + + // Content of first property in nested destructuring + assert.strictEqual(content.names[0].key, "b"); + assert(content.names[0].value instanceof uglify.AST_DefaultAssign); + assert(content.names[0].value.left instanceof uglify.AST_SymbolFunarg); + assert.strictEqual(content.names[0].value.operator, "="); + assert(content.names[0].value.right instanceof uglify.AST_Number); + + + ast = uglify.parse("var a = ({a: {b = 123}}) => {}"); + assert(ast.body[0] instanceof uglify.AST_Var); + assert.strictEqual(ast.body[0].definitions.length, 1); + assert(ast.body[0].definitions[0] instanceof uglify.AST_VarDef); + assert(ast.body[0].definitions[0].name instanceof uglify.AST_SymbolVar); + assert(ast.body[0].definitions[0].value instanceof uglify.AST_Arrow); + assert.strictEqual(ast.body[0].definitions[0].value.argnames.length, 1); + + // Check first argument + assert(ast.body[0].definitions[0].value.argnames[0] instanceof uglify.AST_Destructuring); + assert.strictEqual(ast.body[0].definitions[0].value.argnames[0].is_array, false); + assert.strictEqual(ast.body[0].definitions[0].value.argnames[0].names.length, 1); + + // Check whole destructuring structure of first argument + assert(ast.body[0].definitions[0].value.argnames[0].names[0] instanceof uglify.AST_ObjectKeyVal); + assert.strictEqual(ast.body[0].definitions[0].value.argnames[0].names[0].key, "a"); + assert(ast.body[0].definitions[0].value.argnames[0].names[0].value instanceof uglify.AST_Destructuring); + + // Check content of nested destructuring + content = ast.body[0].definitions[0].value.argnames[0].names[0].value + assert.strictEqual(content.is_array, false); + assert.strictEqual(content.names.length, 1); + assert(content.names[0] instanceof uglify.AST_ObjectKeyVal); + + // Check first property of nested destructuring + assert.strictEqual(content.names[0].key, "b"); + assert(content.names[0].value instanceof uglify.AST_DefaultAssign); + assert(content.names[0].value.left instanceof uglify.AST_SymbolFunarg); + assert.strictEqual(content.names[0].value.operator, "="); + assert(content.names[0].value.right instanceof uglify.AST_Number); + }); + + it("Should parse spread correctly", function() { + var ast = uglify.parse("var a = (a, b, ...c) => {}"); + assert(ast.body[0] instanceof uglify.AST_Var); + assert.strictEqual(ast.body[0].definitions.length, 1); + assert(ast.body[0].definitions[0] instanceof uglify.AST_VarDef); + assert(ast.body[0].definitions[0].name instanceof uglify.AST_SymbolVar); + assert(ast.body[0].definitions[0].value instanceof uglify.AST_Arrow); + assert.strictEqual(ast.body[0].definitions[0].value.argnames.length, 3); + + // Check parameters + assert(ast.body[0].definitions[0].value.argnames[0] instanceof uglify.AST_SymbolFunarg); + assert(ast.body[0].definitions[0].value.argnames[1] instanceof uglify.AST_SymbolFunarg); + assert(ast.body[0].definitions[0].value.argnames[2] instanceof uglify.AST_Expansion); + assert(ast.body[0].definitions[0].value.argnames[2].expression instanceof uglify.AST_SymbolFunarg); + + + ast = uglify.parse("var a = ([a, b, ...c]) => {}"); + assert(ast.body[0] instanceof uglify.AST_Var); + assert.strictEqual(ast.body[0].definitions.length, 1); + assert(ast.body[0].definitions[0] instanceof uglify.AST_VarDef); + assert(ast.body[0].definitions[0].name instanceof uglify.AST_SymbolVar); + assert(ast.body[0].definitions[0].value instanceof uglify.AST_Arrow); + assert.strictEqual(ast.body[0].definitions[0].value.argnames.length, 1); + + // Check first parameter + assert(ast.body[0].definitions[0].value.argnames[0] instanceof uglify.AST_Destructuring); + assert.strictEqual(ast.body[0].definitions[0].value.argnames[0].is_array, true); + + // Check content first parameter + assert(ast.body[0].definitions[0].value.argnames[0].names[0] instanceof uglify.AST_SymbolFunarg); + assert(ast.body[0].definitions[0].value.argnames[0].names[1] instanceof uglify.AST_SymbolFunarg); + assert(ast.body[0].definitions[0].value.argnames[0].names[2] instanceof uglify.AST_Expansion); + assert(ast.body[0].definitions[0].value.argnames[0].names[2].expression instanceof uglify.AST_SymbolFunarg); + + + ast = uglify.parse("var a = ([a, b, [c, ...d]]) => {}"); + assert(ast.body[0] instanceof uglify.AST_Var); + assert.strictEqual(ast.body[0].definitions.length, 1); + assert(ast.body[0].definitions[0] instanceof uglify.AST_VarDef); + assert(ast.body[0].definitions[0].name instanceof uglify.AST_SymbolVar); + assert(ast.body[0].definitions[0].value instanceof uglify.AST_Arrow); + assert.strictEqual(ast.body[0].definitions[0].value.argnames.length, 1); + + // Check first parameter + assert(ast.body[0].definitions[0].value.argnames[0] instanceof uglify.AST_Destructuring); + assert.strictEqual(ast.body[0].definitions[0].value.argnames[0].is_array, true); + + // Check content outer destructuring array + assert(ast.body[0].definitions[0].value.argnames[0].names[0] instanceof uglify.AST_SymbolFunarg); + assert(ast.body[0].definitions[0].value.argnames[0].names[1] instanceof uglify.AST_SymbolFunarg); + assert(ast.body[0].definitions[0].value.argnames[0].names[2] instanceof uglify.AST_Destructuring); + assert.strictEqual(ast.body[0].definitions[0].value.argnames[0].names[2].is_array, true); + + // Check content nested destructuring array + assert.strictEqual(ast.body[0].definitions[0].value.argnames[0].names[2].names.length, 2); + assert(ast.body[0].definitions[0].value.argnames[0].names[2].names[0] instanceof uglify.AST_SymbolFunarg); + assert(ast.body[0].definitions[0].value.argnames[0].names[2].names[1] instanceof uglify.AST_Expansion); + assert(ast.body[0].definitions[0].value.argnames[0].names[2].names[1].expression instanceof uglify.AST_SymbolFunarg); + + + ast = uglify.parse("var a = ({a: [b, ...c]}) => {}"); + assert(ast.body[0] instanceof uglify.AST_Var); + assert.strictEqual(ast.body[0].definitions.length, 1); + assert(ast.body[0].definitions[0] instanceof uglify.AST_VarDef); + assert(ast.body[0].definitions[0].name instanceof uglify.AST_SymbolVar); + assert(ast.body[0].definitions[0].value instanceof uglify.AST_Arrow); + assert.strictEqual(ast.body[0].definitions[0].value.argnames.length, 1); + + // Check first parameter + assert(ast.body[0].definitions[0].value.argnames[0] instanceof uglify.AST_Destructuring); + assert.strictEqual(ast.body[0].definitions[0].value.argnames[0].is_array, false); + + // Check outer destructuring object + assert.strictEqual(ast.body[0].definitions[0].value.argnames[0].names.length, 1); + assert(ast.body[0].definitions[0].value.argnames[0].names[0] instanceof uglify.AST_ObjectKeyVal); + assert.strictEqual(ast.body[0].definitions[0].value.argnames[0].names[0].key, "a"); + assert(ast.body[0].definitions[0].value.argnames[0].names[0].value instanceof uglify.AST_Destructuring); + assert.strictEqual(ast.body[0].definitions[0].value.argnames[0].names[0].value.is_array, true); + + // Check content nested destructuring array + assert.strictEqual(ast.body[0].definitions[0].value.argnames[0].names[0].value.names.length, 2); + assert(ast.body[0].definitions[0].value.argnames[0].names[0].value.names[0] instanceof uglify.AST_SymbolFunarg); + assert(ast.body[0].definitions[0].value.argnames[0].names[0].value.names[1] instanceof uglify.AST_Expansion); + assert(ast.body[0].definitions[0].value.argnames[0].names[0].value.names[1].expression instanceof uglify.AST_SymbolFunarg); + }); + +}); diff --git a/test/mocha/builtins.js b/test/mocha/builtins.js new file mode 100644 index 00000000..427a32ee --- /dev/null +++ b/test/mocha/builtins.js @@ -0,0 +1,34 @@ +var UglifyJS = require("../../"); +var assert = require("assert"); + +describe("builtins", function() { + it ("Should not mangle builtins", function() { + var test = "function foo(something){\n" + + " return [Object,Array,Function,Number,String,Boolean,Error,Math,Date,RegExp,Symbol,Map,Promise,Proxy,Reflect,Set,WeakMap,WeakSet,Float32Array,something];\n" + + "};"; + + var result = UglifyJS.minify(test, {fromString: true, parse: {bare_returns: true}}).code; + + assert.strictEqual(result.indexOf("something"), -1); + + assert.notEqual(result.indexOf("Object"), -1); + assert.notEqual(result.indexOf("Array"), -1); + assert.notEqual(result.indexOf("Function"), -1); + assert.notEqual(result.indexOf("Number"), -1); + assert.notEqual(result.indexOf("String"), -1); + assert.notEqual(result.indexOf("Boolean"), -1); + assert.notEqual(result.indexOf("Error"), -1); + assert.notEqual(result.indexOf("Math"), -1); + assert.notEqual(result.indexOf("Date"), -1); + assert.notEqual(result.indexOf("RegExp"), -1); + assert.notEqual(result.indexOf("Symbol"), -1); + assert.notEqual(result.indexOf("Promise"), -1); + assert.notEqual(result.indexOf("Proxy"), -1); + assert.notEqual(result.indexOf("Reflect"), -1); + assert.notEqual(result.indexOf("Set"), -1); + assert.notEqual(result.indexOf("WeakMap"), -1); + assert.notEqual(result.indexOf("WeakSet"), -1); + assert.notEqual(result.indexOf("Map"), -1); + assert.notEqual(result.indexOf("Float32Array"), -1); + }); +}); diff --git a/test/mocha/class.js b/test/mocha/class.js new file mode 100644 index 00000000..a81d631a --- /dev/null +++ b/test/mocha/class.js @@ -0,0 +1,57 @@ +var assert = require("assert"); +var uglify = require("../../"); + +describe("Class", function() { + it("Should not accept spread on non-last parameters in methods", function() { + var tests = [ + "class foo { bar(...a, b) { return a.join(b) } }", + "class foo { bar(a, b, ...c, d) { return c.join(a + b) + d } }", + "class foo { *bar(...a, b) { return a.join(b) } }", + "class foo { *bar(a, b, ...c, d) { return c.join(a + b) + d } }" + ]; + var test = function(code) { + return function() { + uglify.parse(code, {fromString: true}); + } + } + var error = function(e) { + return e instanceof uglify.JS_Parse_Error && + e.message.substr(0, 31) === "SyntaxError: Unexpected token: "; + } + + for (var i = 0; i < tests.length; i++) { + assert.throws(test(tests[i]), error); + } + }); + + it("Should return the correct token for class methods", function() { + var tests = [ + { + code: "class foo{static test(){}}", + token_value_start: "static", + token_value_end: "}" + }, + { + code: "class bar{*procedural(){}}", + token_value_start: "*", + token_value_end: "}" + }, + { + code: "class foobar{aMethod(){}}", + token_value_start: "aMethod", + token_value_end: "}" + }, + { + code: "class foobaz{get something(){}}", + token_value_start: "get", + token_value_end: "}" + } + ]; + + for (var i = 0; i < tests.length; i++) { + var ast = uglify.parse(tests[i].code); + assert.strictEqual(ast.body[0].properties[0].start.value, tests[i].token_value_start); + assert.strictEqual(ast.body[0].properties[0].end.value, tests[i].token_value_end); + } + }); +}); diff --git a/test/mocha/cli.js b/test/mocha/cli.js index c07eeee7..08171e1c 100644 --- a/test/mocha/cli.js +++ b/test/mocha/cli.js @@ -57,7 +57,7 @@ describe("bin/uglifyjs", function () { if (err) throw err; assert.strictEqual(stdout, "var bar=function(){function foo(bar){return bar}return foo}();\n" + - "//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInRlc3QvaW5wdXQvaXNzdWUtMTMyMy9zYW1wbGUuanMiXSwibmFtZXMiOlsiYmFyIiwiZm9vIl0sIm1hcHBpbmdzIjoiQUFBQSxHQUFJQSxLQUFNLFdBQ04sUUFBU0MsS0FBS0QsS0FDVixNQUFPQSxLQUdYLE1BQU9DIn0=\n"); + "//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInRlc3QvaW5wdXQvaXNzdWUtMTMyMy9zYW1wbGUuanMiXSwibmFtZXMiOlsiYmFyIiwiZm9vIl0sIm1hcHBpbmdzIjoiQUFBQSxHQUFJQSxLQUFPLFdBQ1AsUUFBU0MsS0FBS0QsS0FDVixNQUFPQSxLQUdYLE1BQU9DIn0=\n"); done(); }); }); diff --git a/test/mocha/comment.js b/test/mocha/comment.js index 56470e0f..011676bc 100644 --- a/test/mocha/comment.js +++ b/test/mocha/comment.js @@ -31,7 +31,8 @@ describe("Comment", function() { "/*Some comment 2\r\n\r\n\r\n*/\r\n>\n\n\n\n\n\n", "/*Some comment 3\r\r\r*/\r>\n\n\n\n\n\n", "/*Some comment 4\u2028\u2028\u2028*/\u2028>\n\n\n\n\n\n", - "/*Some comment 5\u2029\u2029\u2029*/\u2029>\n\n\n\n\n\n" + "/*Some comment 5\u2029\u2029\u2029*/\u2029>\n\n\n\n\n\n", + "/*Some comment 6\udbff\udfff\udbff\udfff\n\n\n*/\n>\n\n\n\n\n" ]; var fail = function(e) { @@ -43,7 +44,7 @@ describe("Comment", function() { for (var i = 0; i < tests.length; i++) { assert.throws(function() { - uglify.parse(tests[i], {fromString: true}) + uglify.parse(tests[i], {fromString: true}); }, fail, tests[i]); } }); diff --git a/test/mocha/destructuring.js b/test/mocha/destructuring.js new file mode 100644 index 00000000..73548694 --- /dev/null +++ b/test/mocha/destructuring.js @@ -0,0 +1,138 @@ +var assert = require("assert"); +var uglify = require("../../"); + +describe("Destructuring", function() { + it("Should generate similar trees for destructuring in left hand side expressions, definitions, functions and arrow functions", function() { + var patterns = [ + "[]", + "{}", + + "[a, b, c]", + "{a: b, c: d}", + "{a}", + "{a, b}", + + "{a: {}}", + "{a: []}", + "[{}]", + "[[]]", + "{a: {b}}", + + // Can't do `a = 123` with lhs expression, so only test in destructuring + "[foo = bar]", + "{a = 123}", + "[{foo: abc = 123}]", + "{foo: [abc = 123]}", + + "[...foo]", + "[...{}]", + "[...[]]" + + // Can't do `...` because that is an invalid lhs expression, spread in array destructuring should be fine though + ]; + + var types = [ + { + name: "lhs", + symbolType: uglify.AST_SymbolRef, + tree: function (ast) { + return ast.body[0].body.left; + }, + generate: function (code) { + return "(" + code + " = a)"; + } + }, + { + name: "var", + symbolType: uglify.AST_SymbolVar, + tree: function (ast) { + return ast.body[0].definitions[0].name; + }, + generate: function (code) { + return "var " + code + " = a"; + } + }, + { + name: "function", + symbolType: uglify.AST_SymbolFunarg, + tree: function (ast) { + return ast.body[0].argnames[0]; + }, + generate: function (code) { + return "function a(" + code + ") {}"; + } + }, + { + name: "arrow", + symbolType: uglify.AST_SymbolFunarg, + tree: function (ast) { + return ast.body[0].definitions[0].value.argnames[0]; + }, + generate: function (code) { + return "var a = (" + code + ") => {}"; + } + } + ]; + + var walker = function(type, ref, code, result) { + var w = new uglify.TreeWalker(function(node) { + if (w.parent() instanceof uglify.AST_DefaultAssign && + w.parent().right === node + ) { + return true; // Don't check the content of the default assignment + + } else if (node instanceof uglify.AST_Symbol) { + assert(node instanceof type.symbolType, + node.TYPE + " while " + type.symbolType.TYPE + " expected at pos " + + node.start.pos + " in `" + code + "` (" + ref + ")" + ); + + result.push([ + new uglify.AST_Symbol({ + start: node.start, + name: node.name, + end: node.end + }), + w.parent() + ]); + + return; + } + + result.push([node, w.parent()]); + }); + + return w; + }; + + var getNodeType = function(node) { + return node[0].TYPE + (node[1] ? " " + node[1].TYPE : ""); + } + + for (var i = 0; i < patterns.length; i++) { + var results = []; + + for (var j = 0; j < types.length; j++) { + var code = types[j].generate(patterns[i]) + var ast = types[j].tree( + uglify.parse(code) + ); + results.push([]); + ast.walk(walker( + types[j], + "`" + patterns[i] + "` on " + types[j].name, + code, + results[j] + )); + + if (j > 0) { + assert.deepEqual( + results[0].map(getNodeType), + results[j].map(getNodeType), + "AST disagree on " + patterns[i] + " with " + types[j].name + ); + } + } + } + }); +}); diff --git a/test/mocha/directives.js b/test/mocha/directives.js index bc763ae0..c9970d63 100644 --- a/test/mocha/directives.js +++ b/test/mocha/directives.js @@ -62,10 +62,10 @@ describe("Directives", function() { it("Should know which strings are directive and which ones are not", function() { var test_directive = function(tokenizer, test) { test.directives.map(function(directive) { - assert.strictEqual(tokenizer.has_directive(directive), true, directive + " in " + test.input); + assert.strictEqual(tokenizer.has_directive(directive), true, "Didn't found directive `" + directive + "` at the end of `" + test.input + '`'); }); test.non_directives.map(function(fake_directive) { - assert.strictEqual(tokenizer.has_directive(fake_directive), false, fake_directive + " in " + test.input); + assert.strictEqual(tokenizer.has_directive(fake_directive), false, "Unexpectedly found directive `" + fake_directive + "` at the end of `" + test.input + '`'); }); } @@ -156,6 +156,16 @@ describe("Directives", function() { input: '"use strict";try{"use asm";', directives: ["use strict"], non_directives: ["use\nstrict", "use \nstrict", "use asm"] + }, + { + input: 'class foo {', + directives: ["use strict"], + non_directives: ["use\nstrict", "use asm"] + }, + { + input: 'class foo {}', + directives: [], + non_directives: ["use strict", "use asm", "use\nstrict"] } ]; @@ -367,4 +377,44 @@ describe("Directives", function() { ); } }); + it("Should be detect implicit usages of strict mode from tree walker", function() { + var tests = [ + { + input: 'class foo {bar(){_check_}}', + directives: ["use strict"], + non_directives: ["use bar"] + }, + { + input: 'class foo {bar(){}}_check_', + directives: [], + non_directives: ["use strict", "use bar"] + } + ]; + + var i = 0; + var checked; + var checkWalker = new uglify.TreeWalker(function(node, descend) { + if (node instanceof uglify.AST_Symbol && node.name === "_check_") { + checked = true; + for (var j = 0; j < tests[i].directives.length; j++) { + assert.equal(checkWalker.has_directive(tests[i].directives[j]), true, + "Did not found directive '" + tests[i].directives[j] + "' in test " + tests[i].input) + } + for (var k = 0; k < tests[i].non_directives.length; k++) { + assert.equal(checkWalker.has_directive(tests[i].non_directives[k]), undefined, + "Found directive '" + tests[i].non_directives[k] + "' in test " + tests[i].input) + } + } + }); + + for (; i < tests.length; i++) { + // Do tests - iterate the ast in each test - check only when _check_ occurs - fail when no _check_ has been found + checked = false; + var ast = uglify.parse(tests[i].input); + ast.walk(checkWalker); + if (!checked) { + throw "No _check_ symbol found in " + tests[i].input; + } + } + }); }); diff --git a/test/mocha/eof.js b/test/mocha/eof.js new file mode 100644 index 00000000..d5a245eb --- /dev/null +++ b/test/mocha/eof.js @@ -0,0 +1,36 @@ +var assert = require("assert"); +var uglify = require("../../"); + +describe("EOF", function() { + it("Should test code for at least throwing syntax error when incomplete", function() { + var error = function(e) { + return e instanceof uglify.JS_Parse_Error && + /^SyntaxError: /.test(e.message); + } + var parse = function(test) { + return function() { + uglify.parse(test); + } + } + // Chops off 1 char at a time until limit or start of string is reached + // The passed code must still be valid when unchopped + var test_eol = function(input, chopLimit) { + if (chopLimit === undefined) { + chopLimit = input.length - 1; + } + + assert.doesNotThrow(parse(input), "Expected valid code for \n" + input); + + for (var i = input.length - 1; chopLimit > 0; chopLimit--, i--) { + var code = input.substr(0, i); + assert.throws(parse(code), error, code); + } + } + + test_eol("var \\u1234", 7); // Incomplete identifier + test_eol("'Incomplete string'"); + test_eol("/Unterminated regex/"); + test_eol("` Unterminated template string`"); + test_eol("/* Unfinishing multiline comment */"); + }); +}); diff --git a/test/mocha/expression.js b/test/mocha/expression.js new file mode 100644 index 00000000..178cff22 --- /dev/null +++ b/test/mocha/expression.js @@ -0,0 +1,32 @@ +var assert = require("assert"); +var uglify = require("../../"); + +describe("Expression", function() { + it("Should not allow the first exponentiation operator to be prefixed with an unary operator", function() { + var tests = [ + "+5 ** 3", + "-5 ** 3", + "~5 ** 3", + "!5 ** 3", + "void 5 ** 3", + "typeof 5 ** 3", + "delete 5 ** 3", + "var a = -(5) ** 3;" + ]; + + var fail = function(e) { + return e instanceof uglify.JS_Parse_Error && + /^SyntaxError: Unexpected token: operator \((?:[!+~-]|void|typeof|delete)\)/.test(e.message); + } + + var exec = function(test) { + return function() { + uglify.parse(test); + } + } + + for (var i = 0; i < tests.length; i++) { + assert.throws(exec(tests[i]), fail, tests[i]); + } + }); +}); diff --git a/test/mocha/function.js b/test/mocha/function.js new file mode 100644 index 00000000..01206f49 --- /dev/null +++ b/test/mocha/function.js @@ -0,0 +1,252 @@ +var assert = require("assert"); +var uglify = require("../../"); + +describe("Function", function() { + it ("Should parse binding patterns correctly", function() { + // Function argument nodes are correct + function get_args(args) { + return args.map(function (arg) { + return [arg.TYPE, arg.name]; + }); + } + + // Destructurings as arguments + var destr_fun1 = uglify.parse('(function ({a, b}) {})').body[0].body; + var destr_fun2 = uglify.parse('(function ([a, [b]]) {})').body[0].body; + var destr_fun3 = uglify.parse('({a, b}) => null').body[0].body; + var destr_fun4 = uglify.parse('([a, [b]]) => null').body[0].body; + + assert.equal(destr_fun1.argnames.length, 1); + assert.equal(destr_fun2.argnames.length, 1); + assert.equal(destr_fun3.argnames.length, 1); + assert.equal(destr_fun4.argnames.length, 1); + + var destruct1 = destr_fun1.argnames[0]; + var destruct2 = destr_fun2.argnames[0]; + var destruct3 = destr_fun3.argnames[0]; + var destruct4 = destr_fun4.argnames[0]; + + assert(destruct1 instanceof uglify.AST_Destructuring); + assert(destruct2 instanceof uglify.AST_Destructuring); + assert(destruct3 instanceof uglify.AST_Destructuring); + assert(destruct4 instanceof uglify.AST_Destructuring); + assert(destruct2.names[1] instanceof uglify.AST_Destructuring); + assert(destruct4.names[1] instanceof uglify.AST_Destructuring); + + assert.equal(destruct1.start.value, '{'); + assert.equal(destruct1.end.value, '}'); + assert.equal(destruct2.start.value, '['); + assert.equal(destruct2.end.value, ']'); + assert.equal(destruct3.start.value, '{'); + assert.equal(destruct3.end.value, '}'); + assert.equal(destruct4.start.value, '['); + assert.equal(destruct4.end.value, ']'); + + assert.equal(destruct1.is_array, false); + assert.equal(destruct2.is_array, true); + assert.equal(destruct3.is_array, false); + assert.equal(destruct4.is_array, true); + + // destruct 1 + assert.deepEqual( + [ + destruct1.names[0].TYPE, + destruct1.names[0].key, + destruct1.names[0].value.name + ], + ['ObjectKeyVal', 'a', 'a'] + ); + assert.deepEqual( + [ + destruct1.names[1].TYPE, + destruct1.names[1].key, + destruct1.names[1].value.name + ], + ['ObjectKeyVal', 'b', 'b'] + ); + + // destruct 2 + assert.deepEqual( + [ + destruct2.names[0].TYPE, + destruct2.names[0].name + ], + ['SymbolFunarg', 'a'] + ); + assert.deepEqual( + [ + destruct2.names[1].names[0].TYPE, + destruct2.names[1].names[0].name + ], + ['SymbolFunarg', 'b'] + ); + + // destruct 3 + assert.strictEqual(typeof destruct3.names[0].key, "string"); + assert.strictEqual(destruct3.names[0].key, "a"); + assert.strictEqual(destruct3.names[0].value.TYPE, "SymbolFunarg"); + assert.strictEqual(destruct3.names[0].value.name, "a"); + assert.strictEqual(typeof destruct3.names[1].key, "string"); + assert.strictEqual(destruct3.names[1].key, "b"); + assert.strictEqual(destruct3.names[1].value.TYPE, "SymbolFunarg"); + assert.strictEqual(destruct3.names[1].value.name, "b"); + + // destruct 4 + assert.deepEqual( + [ + destruct4.names[0].TYPE, + destruct4.names[0].name + ], + ['SymbolFunarg', 'a'] + ); + assert.strictEqual(destruct4.names[1].TYPE, "Destructuring"); + assert.deepEqual( + [ + destruct4.names[1].names[0].TYPE, + destruct4.names[1].names[0].name + ], + ['SymbolFunarg', 'b'] + ); + + assert.deepEqual( + get_args(destr_fun1.args_as_names()), + [['SymbolFunarg', 'a'], ['SymbolFunarg', 'b']] + ); + assert.deepEqual( + get_args(destr_fun2.args_as_names()), + [['SymbolFunarg', 'a'], ['SymbolFunarg', 'b']] + ); + assert.deepEqual( + get_args(destr_fun3.args_as_names()), + [['SymbolFunarg', 'a'], ['SymbolFunarg', 'b']] + ); + assert.deepEqual( + get_args(destr_fun4.args_as_names()), + [['SymbolFunarg', 'a'], ['SymbolFunarg', 'b']] + ); + + // Making sure we don't accidentally accept things which + // Aren't argument destructurings + + assert.throws(function () { + uglify.parse('(function ( { a, [ b ] } ) { })') + }); + + assert.throws(function () { + uglify.parse('(function (1) { })'); + }, /Invalid function parameter/); + + assert.throws(function () { + uglify.parse('(function (this) { })'); + }); + + assert.throws(function () { + uglify.parse('(function ([1]) { })'); + }, /Invalid function parameter/); + + assert.throws(function () { + uglify.parse('(function [a] { })'); + }); + + // generators + var generators_def = uglify.parse('function* fn() {}').body[0]; + assert.equal(generators_def.is_generator, true); + + assert.throws(function () { + uglify.parse('function* (){ }'); + }); + + var generators_yield_def = uglify.parse('function* fn() {\nyield remote();\}').body[0].body[0]; + assert.strictEqual(generators_yield_def.body.is_star, false); + }); + it("Should not accept spread on non-last parameters", function() { + var tests = [ + "var a = function(...a, b) { return a.join(b) }", + "var b = function(a, b, ...c, d) { return c.join(a + b) + d }", + "function foo(...a, b) { return a.join(b) }", + "function bar(a, b, ...c, d) { return c.join(a + b) + d }", + "var a = function*(...a, b) { return a.join(b) }", + "var b = function*(a, b, ...c, d) { return c.join(a + b) + d }", + "function* foo(...a, b) { return a.join(b) }", + "function* bar(a, b, ...c, d) { return c.join(a + b) + d }" + ]; + var test = function(code) { + return function() { + uglify.parse(code, {fromString: true}); + } + } + var error = function(e) { + return e instanceof uglify.JS_Parse_Error && + e.message.substr(0, 31) === "SyntaxError: Unexpected token: "; + } + + for (var i = 0; i < tests.length; i++) { + assert.throws(test(tests[i]), error); + } + }); + it("Should not accept empty parameters after elision", function() { + var tests = [ + "(function(,){})()", + "(function(a,){})()", + ]; + var test = function(code) { + return function() { + uglify.parse(code, {fromString: true}); + } + } + var error = function(e) { + return e instanceof uglify.JS_Parse_Error && + e.message === "SyntaxError: Invalid function parameter"; + } + for (var i = 0; i < tests.length; i++) { + assert.throws(test(tests[i]), error); + } + }); + it("Should not accept an initializer when parameter is a rest parameter", function() { + var tests = [ + "(function(...a = b){})()", + "(function(a, ...b = [c, d]))" + ]; + var test = function(code) { + return function () { + uglify.parse(code, {fromString: true}); + } + } + var error = function (e) { + return e instanceof uglify.JS_Parse_Error; + } + for (var i = 0; i < tests.length; i++) { + assert.throws(test(tests[i]), error, tests[i]); + } + }); + it("Shoult not accept duplicated identifiers inside parameters in strict mode or when using default assigment or spread", function() { + // From: ES2016 9.2.12 FunctionDeclarationInstantiation (func, argumentsList) + // NOTE Early errors ensure that duplicate parameter names can only occur + // in non-strict functions that do not have parameter default values or + // rest parameters. + var tests = [ + "(function(a = 1, a){})()", + "(function(a, [a = 3]){})()", + "(function(a, b, c, d, [{e: [...a]}]){})()", + "'use strict'; (function(a, a){})", + "(function({a, a = b}))", + "(function(a, [...a]){})", + "(function(a, ...a){})", + "(function(a, [a, ...b]){})", + "(function(a, {b: a, c: [...d]}){})", + "(function(a, a, {b: [...c]}){})" + ]; + var test = function(code) { + return function () { + uglify.parse(code, {fromString: true}); + } + } + var error = function (e) { + return e instanceof uglify.JS_Parse_Error && + /^SyntaxError: Parameter [a-zA-Z]+ was used already$/.test(e.message); + } + for (var i = 0; i < tests.length; i++) { + assert.throws(test(tests[i]), error, tests[i]); + } + }); +}); diff --git a/test/mocha/lhs-expressions.js b/test/mocha/lhs-expressions.js new file mode 100644 index 00000000..32b88883 --- /dev/null +++ b/test/mocha/lhs-expressions.js @@ -0,0 +1,265 @@ +var assert = require("assert"); +var uglify = require("../../"); + +describe("Left-hand side expressions", function () { + it("Should parse destructuring with const/let/var correctly", function () { + var decls = uglify.parse('var {a,b} = foo, { c, d } = bar'); + + assert.equal(decls.body[0].TYPE, 'Var'); + assert.equal(decls.body[0].definitions.length, 2); + + // Item 1 + assert.equal(decls.body[0].definitions[0].name.TYPE, 'Destructuring'); + assert.equal(decls.body[0].definitions[0].value.TYPE, 'SymbolRef'); + + // Item 2 + assert.equal(decls.body[0].definitions[1].name.TYPE, 'Destructuring'); + assert.equal(decls.body[0].definitions[1].value.TYPE, 'SymbolRef'); + + var nested_def = uglify.parse('var [{x}] = foo').body[0].definitions[0]; + + assert.equal(nested_def.name.names[0].names[0].TYPE, 'ObjectKeyVal'); + assert.equal(nested_def.name.names[0].names[0].value.TYPE, 'SymbolVar'); + assert.equal(nested_def.name.names[0].names[0].key, 'x'); + assert.equal(nested_def.name.names[0].names[0].value.name, 'x'); + + var holey_def = uglify.parse('const [,,third] = [1,2,3]').body[0].definitions[0]; + + assert.equal(holey_def.name.names[0].TYPE, 'Hole'); + assert.equal(holey_def.name.names[1].TYPE, 'Hole'); + assert.equal(holey_def.name.names[2].TYPE, 'SymbolConst'); + + var expanding_def = uglify.parse('var [first, ...rest] = [1,2,3]').body[0].definitions[0]; + + assert.equal(expanding_def.name.names[0].TYPE, 'SymbolVar'); + assert.equal(expanding_def.name.names[1].TYPE, 'Expansion'); + assert.equal(expanding_def.name.names[1].expression.TYPE, 'SymbolVar'); + }); + + it("Parser should use AST_Array for array literals", function() { + var ast = uglify.parse('["foo", "bar"]'); + assert(ast.body[0] instanceof uglify.AST_SimpleStatement); + assert(ast.body[0].body instanceof uglify.AST_Array); + + ast = uglify.parse('a = ["foo"]'); + assert(ast.body[0] instanceof uglify.AST_SimpleStatement); + + assert(ast.body[0].body instanceof uglify.AST_Assign); + assert(ast.body[0].body.left instanceof uglify.AST_SymbolRef); + assert.equal(ast.body[0].body.operator, "="); + assert(ast.body[0].body.right instanceof uglify.AST_Array); + }); + + it("Parser should use AST_Object for object literals", function() { + var ast = uglify.parse('({foo: "bar"})'); + assert(ast.body[0] instanceof uglify.AST_SimpleStatement); + assert(ast.body[0].body instanceof uglify.AST_Object); + + // This example should be fine though + ast = uglify.parse('a = {foo: "bar"}'); + assert(ast.body[0] instanceof uglify.AST_SimpleStatement); + + assert(ast.body[0].body instanceof uglify.AST_Assign); + assert(ast.body[0].body.left instanceof uglify.AST_SymbolRef); + assert.equal(ast.body[0].body.operator, "="); + assert(ast.body[0].body.right instanceof uglify.AST_Object); + }); + + it("Parser should use AST_Destructuring for array assignment patterns", function() { + var ast = uglify.parse('[foo, bar] = [1, 2]'); + assert(ast.body[0] instanceof uglify.AST_SimpleStatement); + + assert(ast.body[0].body instanceof uglify.AST_Assign); + assert(ast.body[0].body.left instanceof uglify.AST_Destructuring); + assert.strictEqual(ast.body[0].body.left.is_array, true); + assert.equal(ast.body[0].body.operator, "="); + assert(ast.body[0].body.right instanceof uglify.AST_Array); + }); + + it("Parser should use AST_Destructuring for object assignment patterns", function() { + var ast = uglify.parse('({a: b, b: c} = {b: "c", c: "d"})'); + assert(ast.body[0] instanceof uglify.AST_SimpleStatement); + + assert(ast.body[0].body instanceof uglify.AST_Assign); + assert(ast.body[0].body.left instanceof uglify.AST_Destructuring); + assert.strictEqual(ast.body[0].body.left.is_array, false); + assert.equal(ast.body[0].body.operator, "="); + assert(ast.body[0].body.right instanceof uglify.AST_Object); + }); + + it("Parser should be able to handle nested destructuring", function() { + var ast = uglify.parse('[{a,b},[d, e, f, {g, h}]] = [{a: 1, b: 2}, [3, 4, 5, {g: 6, h: 7}]]'); + assert(ast.body[0] instanceof uglify.AST_SimpleStatement); + + assert(ast.body[0].body instanceof uglify.AST_Assign); + assert(ast.body[0].body.left instanceof uglify.AST_Destructuring); + assert.strictEqual(ast.body[0].body.left.is_array, true); + assert.equal(ast.body[0].body.operator, "="); + assert(ast.body[0].body.right instanceof uglify.AST_Array); + + assert(ast.body[0].body.left.names[0] instanceof uglify.AST_Destructuring); + assert.strictEqual(ast.body[0].body.left.names[0].is_array, false); + + assert(ast.body[0].body.left.names[1] instanceof uglify.AST_Destructuring); + assert.strictEqual(ast.body[0].body.left.names[1].is_array, true); + + assert(ast.body[0].body.left.names[1].names[3] instanceof uglify.AST_Destructuring); + assert.strictEqual(ast.body[0].body.left.names[1].names[3].is_array, false); + }); + + it("Should handle spread operator in destructuring", function() { + var ast = uglify.parse("[a, b, ...c] = [1, 2, 3, 4, 5]"); + assert(ast.body[0] instanceof uglify.AST_SimpleStatement); + + assert(ast.body[0].body instanceof uglify.AST_Assign); + assert(ast.body[0].body.left instanceof uglify.AST_Destructuring); + assert.strictEqual(ast.body[0].body.left.is_array, true); + assert.equal(ast.body[0].body.operator, "="); + assert(ast.body[0].body.right instanceof uglify.AST_Array); + + assert(ast.body[0].body.left.names[0] instanceof uglify.AST_SymbolRef); + assert(ast.body[0].body.left.names[1] instanceof uglify.AST_SymbolRef); + + assert(ast.body[0].body.left.names[2] instanceof uglify.AST_Expansion); + }); + + it("Should handle default assignments in destructuring", function() { + var ast = uglify.parse("({x: v, z = z + 5} = obj);"); + assert(ast.body[0] instanceof uglify.AST_SimpleStatement); + + assert(ast.body[0].body instanceof uglify.AST_Assign); + assert(ast.body[0].body.left instanceof uglify.AST_Destructuring); + assert.strictEqual(ast.body[0].body.left.is_array, false); + assert.equal(ast.body[0].body.operator, "="); + assert(ast.body[0].body.right instanceof uglify.AST_SymbolRef); + + assert(ast.body[0].body.left.names[0].value instanceof uglify.AST_SymbolRef); + assert.strictEqual(ast.body[0].body.left.names[0].start.value, "x"); + + assert(ast.body[0].body.left.names[1].value instanceof uglify.AST_DefaultAssign); + assert.strictEqual(ast.body[0].body.left.names[1].start.value, "z"); + assert(ast.body[0].body.left.names[1].value.left instanceof uglify.AST_SymbolRef); + assert.strictEqual(ast.body[0].body.left.names[1].value.operator, "="); + assert(ast.body[0].body.left.names[1].value.right instanceof uglify.AST_Binary); + + + ast = uglify.parse("({x = 123} = obj);"); + assert(ast.body[0] instanceof uglify.AST_SimpleStatement); + assert(ast.body[0].body instanceof uglify.AST_Assign); + assert(ast.body[0].body.left instanceof uglify.AST_Destructuring); + assert.strictEqual(ast.body[0].body.left.is_array, false); + assert.equal(ast.body[0].body.operator, "="); + assert(ast.body[0].body.right instanceof uglify.AST_SymbolRef); + + assert(ast.body[0].body.left.names[0].value instanceof uglify.AST_DefaultAssign); + assert.strictEqual(ast.body[0].body.left.names[0].value.operator, "="); + assert(ast.body[0].body.left.names[0].value.left instanceof uglify.AST_SymbolRef); + + + ast = uglify.parse("[x, y = 5] = foo"); + assert(ast.body[0] instanceof uglify.AST_SimpleStatement); + + assert(ast.body[0].body instanceof uglify.AST_Assign); + assert(ast.body[0].body.left instanceof uglify.AST_Destructuring); + assert.strictEqual(ast.body[0].body.left.is_array, true); + assert.equal(ast.body[0].body.operator, "="); + assert(ast.body[0].body.right instanceof uglify.AST_SymbolRef); + + assert(ast.body[0].body.left.names[0] instanceof uglify.AST_SymbolRef); + assert.strictEqual(ast.body[0].body.left.names[0].start.value, "x"); + + assert(ast.body[0].body.left.names[1] instanceof uglify.AST_DefaultAssign); + assert(ast.body[0].body.left.names[1].left instanceof uglify.AST_SymbolRef); + assert.strictEqual(ast.body[0].body.left.names[1].start.value, "y"); + }); + + it("Should handle default assignments containing assignments in a destructuring", function() { + var ast = uglify.parse("[x, y = z = 2] = a;"); + assert(ast.body[0] instanceof uglify.AST_SimpleStatement); + + assert(ast.body[0].body instanceof uglify.AST_Assign); + assert(ast.body[0].body.left instanceof uglify.AST_Destructuring); + assert.strictEqual(ast.body[0].body.left.is_array, true); + assert.equal(ast.body[0].body.operator, "="); + assert(ast.body[0].body.right instanceof uglify.AST_SymbolRef); + + assert(ast.body[0].body.left.names[0] instanceof uglify.AST_SymbolRef); + + assert(ast.body[0].body.left.names[1] instanceof uglify.AST_DefaultAssign); + assert(ast.body[0].body.left.names[1].left instanceof uglify.AST_SymbolRef); + assert.equal(ast.body[0].body.left.names[1].operator, "="); + assert(ast.body[0].body.left.names[1].right instanceof uglify.AST_Assign); + + assert(ast.body[0].body.left.names[1].right.left instanceof uglify.AST_SymbolRef); + assert.equal(ast.body[0].body.left.names[1].right.operator, "="); + assert(ast.body[0].body.left.names[1].right.right instanceof uglify.AST_Number); + + ast = uglify.parse("({a: a = 123} = obj)"); + assert(ast.body[0] instanceof uglify.AST_SimpleStatement); + + assert(ast.body[0].body instanceof uglify.AST_Assign); + assert(ast.body[0].body.left instanceof uglify.AST_Destructuring); + assert.strictEqual(ast.body[0].body.left.is_array, false); + assert.equal(ast.body[0].body.operator, "="); + assert(ast.body[0].body.right instanceof uglify.AST_SymbolRef); + + assert(ast.body[0].body.left.names[0] instanceof uglify.AST_ObjectProperty); + assert.strictEqual(ast.body[0].body.left.names[0].key, "a"); + assert(ast.body[0].body.left.names[0].value instanceof uglify.AST_DefaultAssign); + assert(ast.body[0].body.left.names[0].value.left instanceof uglify.AST_SymbolRef); + assert.strictEqual(ast.body[0].body.left.names[0].value.operator, "="); + assert(ast.body[0].body.left.names[0].value.right instanceof uglify.AST_Number); + }); + + it("Should allow multiple spread in array literals", function() { + var ast = uglify.parse("var a = [1, 2, 3], b = [4, 5, 6], joined; joined = [...a, ...b]"); + assert(ast.body[0] instanceof uglify.AST_Var); + assert(ast.body[1] instanceof uglify.AST_SimpleStatement); + + // Check statement containing array with spreads + assert(ast.body[1].body instanceof uglify.AST_Assign); + assert(ast.body[1].body.left instanceof uglify.AST_SymbolRef); + assert.equal(ast.body[1].body.operator, "="); + assert(ast.body[1].body.right instanceof uglify.AST_Array); + + // Check array content + assert.strictEqual(ast.body[1].body.right.elements.length, 2); + assert(ast.body[1].body.right.elements[0] instanceof uglify.AST_Expansion); + assert(ast.body[1].body.right.elements[0].expression instanceof uglify.AST_SymbolRef); + assert(ast.body[1].body.right.elements[0].expression.name, "a"); + assert(ast.body[1].body.right.elements[1] instanceof uglify.AST_Expansion); + assert(ast.body[1].body.right.elements[1].expression instanceof uglify.AST_SymbolRef); + assert(ast.body[1].body.right.elements[1].expression.name, "b"); + }); + + it("Should not allow spread on invalid locations", function() { + var expect = function(input, expected) { + var execute = function(input) { + return function() { + uglify.parse(input); + } + } + var check = function(e) { + return e instanceof uglify.JS_Parse_Error && + e.message === expected; + } + + assert.throws(execute(input), check); + } + + // Spreads are not allowed in destructuring array if it's not the last element + expect("[...a, ...b] = [1, 2, 3, 4]", "SyntaxError: Spread must the be last element in destructuring array"); + + // Multiple spreads are not allowed in destructuring array + expect("[...a, ...b] = [1, 2, 3, 4]", "SyntaxError: Spread must the be last element in destructuring array"); + + // Spread in obvious object pattern + expect("({...a} = foo)", "SyntaxError: Unexpected token: expand (...)"); + + // Spread in block should not be allowed + expect("{...a} = foo", "SyntaxError: Unexpected token: expand (...)"); + + // Not in standard yet + expect("let foo = {bar: 42}, bar; bar = {...foo}", "SyntaxError: Unexpected token: expand (...)"); + }); +}); diff --git a/test/mocha/new.js b/test/mocha/new.js index 083b9964..cc193d3d 100644 --- a/test/mocha/new.js +++ b/test/mocha/new.js @@ -85,4 +85,11 @@ describe("New", function() { ); } }); + + it("Should check target in new.target", function() { + assert.throws(function() {uglify.parse("new.blah")}, function(e) { + return e instanceof uglify.JS_Parse_Error + && e.message === "SyntaxError: Unexpected token name «blah», expected name «target»"; + }); + }); }); \ No newline at end of file diff --git a/test/mocha/object.js b/test/mocha/object.js new file mode 100644 index 00000000..cabe8a9a --- /dev/null +++ b/test/mocha/object.js @@ -0,0 +1,131 @@ +var Uglify = require("../../"); +var assert = require("assert"); + +describe("Object", function() { + it("Should allow objects to have a methodDefinition as property", function() { + var code = "var a = {test() {return true;}}"; + 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() { + var code = "{static test() {}}"; + var parse = function() { + Uglify.parse(code); + } + var expect = function(e) { + return e instanceof Uglify.JS_Parse_Error; + } + 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)); + } + }); + it("Should be able to use shorthand properties", function() { + var ast = Uglify.parse("var foo = 123\nvar obj = {foo: foo}"); + assert.strictEqual(ast.print_to_string({ecma: 6}), "var foo=123;var obj={foo};"); + }) +}); diff --git a/test/mocha/template-string.js b/test/mocha/template-string.js new file mode 100644 index 00000000..cc6ba7ab --- /dev/null +++ b/test/mocha/template-string.js @@ -0,0 +1,33 @@ +var assert = require("assert"); +var uglify = require("../../"); + +describe("Template string", function() { + it("Should not accept invalid sequences", function() { + var tests = [ + // Stress invalid expression + "var foo = `Hello ${]}`", + "var foo = `Test 123 ${>}`", + "var foo = `Blah ${;}`", + + // Stress invalid template_substitution after expression + "var foo = `Blablabla ${123 456}`", + "var foo = `Blub ${123;}`", + "var foo = `Bleh ${a b}`" + ]; + + var exec = function(test) { + return function() { + uglify.parse(test); + } + }; + + var fail = function(e) { + return e instanceof uglify.JS_Parse_Error + && /^SyntaxError: Unexpected token: /.test(e.message); + }; + + for (var i = 0; i < tests.length; i++) { + assert.throws(exec(tests[i]), fail, tests[i]); + } + }); +}); diff --git a/test/mocha/try.js b/test/mocha/try.js new file mode 100644 index 00000000..a447907d --- /dev/null +++ b/test/mocha/try.js @@ -0,0 +1,22 @@ +var assert = require("assert"); +var uglify = require("../../"); + +describe("Try", function() { + it("Should not allow catch with an empty parameter", function() { + var tests = [ + "try {} catch() {}" + ]; + + var test = function(code) { + return function () { + uglify.parse(code, {fromString: true}); + } + } + var error = function (e) { + return e instanceof uglify.JS_Parse_Error; + } + for (var i = 0; i < tests.length; i++) { + assert.throws(test(tests[i]), error, tests[i]); + } + }); +}); diff --git a/test/mocha/unicode.js b/test/mocha/unicode.js new file mode 100644 index 00000000..eb29e4a5 --- /dev/null +++ b/test/mocha/unicode.js @@ -0,0 +1,138 @@ +var assert = require("assert"); +var uglify = require("../../"); + +describe("Unicode", function() { + it("Should not accept invalid code ranges in unicode escape", function() { + var tests = [ + "\\u{110000}", // A bit over the unicode range + "\\u{100000061} = 'foo'", // 32-bit overflow resulting in "a" + "\\u{fffffffffff}", // A bit too much over the unicode range + ]; + + var exec = function(test) { + return function() { + uglify.parse(test); + } + } + + var fail = function(e) { + return e instanceof uglify.JS_Parse_Error + && e.message === "SyntaxError: Unicode reference out of bounce"; + } + + for (var i = 0; i < tests.length; i++) { + assert.throws(exec(tests[i]), fail); + } + }); + + it("Should not accept invalid unicode sequences", function() { + var tests = [ + "var foo = '\\u-111'", + "var bar = '\\u{-1}'", + "var baz = '\\ugggg'" + ]; + + var exec = function(test) { + return function() { + uglify.parse(test); + } + } + + var fail = function(e) { + return e instanceof uglify.JS_Parse_Error + && e.message === "SyntaxError: Invalid hex-character pattern in string"; + } + + for (var i = 0; i < tests.length; i++) { + assert.throws(exec(tests[i]), fail); + } + }); + + it("Should throw error if escaped first identifier char is not part of ID_start", function() { + var tests = [ + 'var \\u{0} = "foo";', + 'var \\u{10ffff} = "bar";', + 'var \\u000a = "what\'s up";', + // Valid ID_Start, but using up 2 escaped characters and not fitting in IdentifierStart + 'var \\ud800\\udc00 = "Hello";', + 'var \\udbff\\udfff = "Unicode";', // Same as previous test + 'var \\ud800\udc01 = "Weird unicode";', // Same as above, but mixed escaped with unicode chars + ]; + + var exec = function(test) { + return function() { + uglify.parse(test); + } + } + + var fail = function(e) { + return e instanceof uglify.JS_Parse_Error + && e.message === "SyntaxError: First identifier char is an invalid identifier char"; + } + + for (var i = 0; i < tests.length; i++) { + assert.throws(exec(tests[i]), fail); + } + }); + + it("Should throw error if escaped non-first identifier char is not part of ID_start", function() { + var tests = [ + 'var a\\u{0} = "foo";', + 'var a\\u{10ffff} = "bar";', + 'var z\\u000a = "what\'s up";' + ]; + + var exec = function(test) { + return function() { + uglify.parse(test); + } + } + + var fail = function(e) { + return e instanceof uglify.JS_Parse_Error + && e.message === "SyntaxError: Invalid escaped identifier char"; + } + + for (var i = 0; i < tests.length; i++) { + assert.throws(exec(tests[i]), fail); + } + }); + + it("Should throw error if identifier is a keyword with a escape sequences", function() { + var tests = [ + 'var \\u0069\\u006e = "foo"', // in + 'var \\u0076\\u0061\\u0072 = "bar"', // var + 'var \\u{66}\\u{6f}\\u{72} = "baz"', // for + 'var \\u0069\\u{66} = "foobar"', // if + 'var \\u{73}uper' // super + ]; + + var exec = function(test) { + return function() { + uglify.parse(test); + } + } + + var fail = function(e) { + return e instanceof uglify.JS_Parse_Error + && e.message === "SyntaxError: Escaped characters are not allowed in keywords"; + } + + for (var i = 0; i < tests.length; i++) { + assert.throws(exec(tests[i]), fail); + } + }); + + it("Should read strings containing surigates correctly", function() { + var tests = [ + ['var a = "\ud800\udc00";', 'var a="\\u{10000}";'], + ['var b = "\udbff\udfff";', 'var b="\\u{10ffff}";'] + ]; + + for (var i = 0; i < tests.length; i++) { + assert.strictEqual(uglify.minify(tests[i][0], { + fromString: true, output: { ascii_only: true, ecma: 6} + }).code, tests[i][1]); + } + }); +}); diff --git a/test/mocha/yield.js b/test/mocha/yield.js new file mode 100644 index 00000000..676ed832 --- /dev/null +++ b/test/mocha/yield.js @@ -0,0 +1,106 @@ +var UglifyJS = require("../../"); +var assert = require("assert"); + +describe("Yield", function() { + it("Should not delete statements after yield", function() { + var js = 'function *foo(bar) { yield 1; yield 2; return 3; }'; + var result = UglifyJS.minify(js, {fromString: true}); + assert.strictEqual(result.code, 'function*foo(e){return yield 1,yield 2,3}'); + }); + + it("Should not allow yield as labelIdentifier within generators", function() { + var js = "function* g() {yield: 1}" + var test = function() { + UglifyJS.parse(js); + } + var expect = function(e) { + return e instanceof UglifyJS.JS_Parse_Error && + e.message === "SyntaxError: Yield cannot be used as label inside generators"; + } + assert.throws(test, expect); + }); + + it("Should not allow yield* followed by a semicolon in generators", function() { + var js = "function* test() {yield*\n;}"; + var test = function() { + UglifyJS.parse(js); + } + var expect = function(e) { + return e instanceof UglifyJS.JS_Parse_Error && + e.message === "SyntaxError: Unexpected token: punc (;)"; + } + assert.throws(test, expect); + }); + + it("Should not allow yield with next token star on next line", function() { + var js = "function* test() {yield\n*123;}"; + var test = function() { + UglifyJS.parse(js); + } + var expect = function(e) { + return e instanceof UglifyJS.JS_Parse_Error && + e.message === "SyntaxError: Unexpected token: operator (*)"; + } + assert.throws(test, expect); + }); + + it("Should be able to compress its expression", function() { + assert.strictEqual( + UglifyJS.minify("function *f() { yield 3-4; }", {fromString: true, compress: true}).code, + "function*f(){yield-1}" + ); + }); + + it("Should keep undefined after yield without compression if found in ast", function() { + assert.strictEqual( + UglifyJS.minify("function *f() { yield undefined; yield; yield* undefined; yield void 0}", {fromString: true, compress: false}).code, + "function*f(){yield undefined;yield;yield*undefined;yield void 0}" + ); + }); + + it("Should be able to drop undefined after yield if necessary with compression", function() { + assert.strictEqual( + UglifyJS.minify("function *f() { yield undefined; yield; yield* undefined; yield void 0}", {fromString: true, compress: true}).code, + "function*f(){yield,yield,yield*void 0,yield}" + ); + }); + + it("Should not allow yield to be used as symbol, identifier or property outside generators in strict mode", function() { + var tests = [ + // Fail as as_symbol + '"use strict"; import yield from "bar";', + '"use strict"; yield = 123;', + '"use strict"; yield: "123";', + '"use strict"; for(;;){break yield;}', + '"use strict"; for(;;){continue yield;}', + '"use strict"; function yield(){}', + '"use strict"; function foo(...yield){}', + '"use strict"; try { new Error("")} catch (yield) {}', + '"use strict"; var yield = "foo";', + '"use strict"; class yield {}', + + // Fail as maybe_assign + '"use strict"; var foo = yield;', + '"use strict"; var foo = bar = yield', + + // Fail as as_property_name + '"use strict"; var foo = {yield};', + '"use strict"; var bar = {yield: "foo"};' + ]; + + var fail = function(e) { + return e instanceof UglifyJS.JS_Parse_Error && + /SyntaxError: Unexpected yield identifier (?:as parameter )?inside strict mode/.test(e.message); + } + + var test = function(input) { + return function() { + UglifyJS.parse(input); + } + } + + for (var i = 0; i < tests.length; i++) { + assert.throws(test(tests[i]), fail, tests[i]); + } + }); +}); \ No newline at end of file