diff --git a/lib/ast.js b/lib/ast.js index 42506cb2..54111e58 100644 --- a/lib/ast.js +++ b/lib/ast.js @@ -143,7 +143,7 @@ var AST_SimpleStatement = DEFNODE("SimpleStatement", "body", { }, AST_Statement); function walk_body(node, visitor) { - if (node.body instanceof AST_Statement) { + if (node.body instanceof AST_Node) { node.body._walk(visitor); } else node.body.forEach(function(stat){ @@ -263,6 +263,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: { @@ -278,9 +282,10 @@ var AST_With = DEFNODE("With", "expression", { /* -----[ scope and functions ]----- */ -var AST_Scope = DEFNODE("Scope", "directives variables functions uses_with uses_eval parent_scope enclosed cname", { +var AST_Scope = DEFNODE("Scope", "is_block_scope directives variables functions uses_with uses_eval parent_scope enclosed cname", { $documentation: "Base class for all statements introducing a lexical scope", $propdoc: { + is_block_scope: "[boolean] identifies a block scope", directives: "[string*/S] an array of directives declared in this scope", variables: "[Object/S] a map of name -> SymbolDef for all variables/functions defined in this scope", functions: "[Object/S] like `variables`, but only lists function declarations", @@ -290,6 +295,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", { @@ -359,13 +371,99 @@ 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) { + if (ex instanceof AST_Object) { + if (ex.properties.length == 0) + croak("Invalid destructuring function parameter", ex.start.line, ex.start.col); + return new AST_Destructuring({ + start: ex.start, + end: ex.end, + is_array: false, + default: default_seen_above, + names: ex.properties.map(to_fun_args) + }); + } else if (ex instanceof AST_ObjectKeyVal && ex.shorthand) { + return new AST_SymbolFunarg({ + name: ex.key, + start: ex.start, + end: ex.end + }); + } 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 ex; + } else if (ex instanceof AST_SymbolRef) { + return new AST_SymbolFunarg({ + name: ex.name, + default: default_seen_above, + start: ex.start, + end: ex.end + }); + } else if (ex instanceof AST_Expansion) { + return ex; + } else if (ex instanceof AST_Array) { + if (ex.elements.length === 0) + croak("Invalid destructuring function parameter", ex.start.line, ex.start.col); + return new AST_Destructuring({ + start: ex.start, + end: ex.end, + is_array: true, + default: default_seen_above, + names: ex.elements.map(to_fun_args) + }); + } else if (ex instanceof AST_Assign) { + return to_fun_args(ex.left, undefined, undefined, ex.right); + } 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: "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*] 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); @@ -385,10 +483,74 @@ 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_ConciseMethod = DEFNODE("ConciseMethod", "is_generator static", { + $propdoc: { + is_generator: "is generatorFn or not", + static: "[boolean] whether this method is static (classes only)", + }, + $documentation: "An ES6 concise method inside an object or class" +}, AST_Lambda); + var AST_Defun = DEFNODE("Defun", null, { $documentation: "A function definition" }, AST_Lambda); +/* -----[ DESTRUCTURING ]----- */ +var AST_Destructuring = DEFNODE("Destructuring", "names is_array default", { + $documentation: "A destructuring of several names. Used in destructuring assignment and with destructuring function argument names", + _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: "[string|AST_Expression]* One or more segments. They can be the parts that are evaluated, or the raw string parts." + }, + _walk: function(visitor) { + return visitor._visit(this, function(){ + this.segments.forEach(function(seg, i){ + if (i % 2 !== 0) { + seg._walk(visitor); + } + }); + }); + } +}); + /* -----[ JUMPS ]----- */ var AST_Jump = DEFNODE("Jump", null, { @@ -542,16 +704,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); @@ -774,21 +998,68 @@ var AST_ObjectProperty = DEFNODE("ObjectProperty", "key value", { } }); -var AST_ObjectKeyVal = DEFNODE("ObjectKeyVal", "quote", { +var AST_ObjectKeyVal = DEFNODE("ObjectKeyVal", "quote shorthand", { $documentation: "A key: value object property", $propdoc: { - quote: "[string] the original quote character" + quote: "[string] the original quote character", + shorthand: "[boolean] whether this is a shorthand key:value pair, expressed as just the key." } }, AST_ObjectProperty); -var AST_ObjectSetter = DEFNODE("ObjectSetter", null, { +var AST_ObjectComputedKeyVal = DEFNODE("ObjectComputedKeyVal", null, { + $documentation: "An object property whose key is computed. Like `[Symbol.iterator]: function...` or `[routes.homepage]: renderHomepage`", + _walk: function(visitor) { + return visitor._visit(this, function(){ + this.key._walk(visitor); + this.value._walk(visitor); + }); + } +}, AST_ObjectProperty); + +var AST_ObjectSetter = DEFNODE("ObjectSetter", "static", { + $propdoc: { + static: "[boolean] whether this is a static setter (classes only)" + }, $documentation: "An object setter property", }, AST_ObjectProperty); -var AST_ObjectGetter = DEFNODE("ObjectGetter", null, { +var AST_ObjectGetter = DEFNODE("ObjectGetter", "static", { + $propdoc: { + static: "[boolean] whether this is a static getter (classes only)" + }, $documentation: "An object getter property", }, AST_ObjectProperty); +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", @@ -798,14 +1069,24 @@ var AST_Symbol = DEFNODE("Symbol", "scope name thedef", { $documentation: "Base class for all symbols", }); +var AST_NewTarget = DEFNODE("NewTarget", null, { + $documentation: "A reference to new.target" +}); + var AST_SymbolAccessor = DEFNODE("SymbolAccessor", null, { $documentation: "The name of a property accessor (setter/getter function)" }, AST_Symbol); -var AST_SymbolDeclaration = DEFNODE("SymbolDeclaration", "init", { +var AST_SymbolDeclaration = DEFNODE("SymbolDeclaration", "init default", { $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." + init: "[AST_Node*/S] array of initializers for this declaration.", + default: "[AST_Expression] The default for this parameter. For example, `= 6`" + }, + _walk: function (visitor) { + return visitor._visit(this, function() { + if (this.default) this.default._walk(visitor); + }); } }, AST_Symbol); @@ -813,9 +1094,17 @@ 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", @@ -825,13 +1114,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)", @@ -856,6 +1165,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() { @@ -929,6 +1242,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) { @@ -979,7 +1307,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 fd839fa1..2a22e7f1 100644 --- a/lib/compress.js +++ b/lib/compress.js @@ -246,6 +246,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) { @@ -353,7 +361,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; } @@ -444,8 +452,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) { @@ -516,7 +532,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) { @@ -886,7 +902,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); @@ -1023,6 +1039,12 @@ merge(Compressor.prototype, { // places too. :-( Wish JS had multiple inheritance. 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"); @@ -1041,7 +1063,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); @@ -1067,6 +1090,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; @@ -1215,6 +1239,8 @@ merge(Compressor.prototype, { }); def(AST_Defun, function(compressor){ return true }); def(AST_Function, function(compressor){ return false }); + def(AST_Class, function(compressor){ return false }); + def(AST_DefClass, function(compressor){ return true }); def(AST_Binary, function(compressor){ return this.left.has_side_effects(compressor) || this.right.has_side_effects(compressor); @@ -1226,9 +1252,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){ @@ -1241,6 +1265,9 @@ merge(Compressor.prototype, { return false; }); def(AST_ObjectProperty, function(compressor){ + if (this instanceof AST_ObjectComputedKeyVal && + this.key.has_side_effects(compressor)) + return true; return this.value.has_side_effects(compressor); }); def(AST_Array, function(compressor){ @@ -1280,6 +1307,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(){ @@ -1313,6 +1341,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; }); @@ -1320,7 +1349,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; @@ -1330,7 +1363,6 @@ merge(Compressor.prototype, { var self = this; if (compressor.has_directive("use asm")) return self; if (compressor.option("unused") - && !(self instanceof AST_Toplevel) && !self.uses_eval && !self.uses_with ) { @@ -1342,12 +1374,13 @@ merge(Compressor.prototype, { var scope = this; var tw = new TreeWalker(function(node, descend){ if (node !== self) { - if (node instanceof AST_Defun) { + if (node instanceof AST_Defun || node instanceof AST_DefClass) { initializations.add(node.name.name, node); return true; // don't go in nested scopes } if (node instanceof AST_Definitions && scope === self) { node.definitions.forEach(function(def){ + if (def.is_destructuring()) return; /* Destructurings are type assertions! */ if (def.value) { initializations.add(def.name.name, def.value); if (def.value.has_side_effects(compressor)) { @@ -1402,22 +1435,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.unreferenced()) { - 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.unreferenced()) { + 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 (node instanceof AST_Defun && node !== self) { - if (!(node.name.definition().id in in_use_ids)) { + if ((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, @@ -1430,7 +1476,10 @@ merge(Compressor.prototype, { } if (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, @@ -1509,6 +1558,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; } @@ -1520,8 +1575,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 = []; @@ -1550,6 +1609,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; }); @@ -1582,7 +1642,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(); @@ -2004,13 +2064,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; }, []); @@ -2024,6 +2094,10 @@ merge(Compressor.prototype, { return self; }); + OPT(AST_Import, function(self, compressor) { + return self; + }); + OPT(AST_Function, function(self, compressor){ self = AST_Lambda.prototype.optimize.call(self, compressor); if (compressor.option("unused") && !compressor.option("keep_fnames")) { @@ -2137,6 +2211,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) { @@ -2902,4 +2981,11 @@ merge(Compressor.prototype, { return self; }); + OPT(AST_Yield, function(self, compressor){ + if (!self.is_star && self.expression instanceof AST_Undefined) { + self.expression = null; + } + return self; + }); + })(); diff --git a/lib/output.js b/lib/output.js index 324f96ed..715f6550 100644 --- a/lib/output.js +++ b/lib/output.js @@ -67,7 +67,8 @@ function OutputStream(options) { screw_ie8 : true, preamble : null, quote_style : 0, - keep_quoted_props: false + keep_quoted_props: false, + ecma : 5, }, true); var indentation = 0; @@ -77,9 +78,16 @@ 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.es5) || code.length > 4) { + if (options.ecma < 6) { + 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 { @@ -107,7 +115,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; }); @@ -158,7 +166,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; }; function maybe_newline() { @@ -170,7 +184,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; @@ -232,6 +246,10 @@ function OutputStream(options) { OUTPUT += str; }; + var star = function(){ + print("*"); + } + var space = options.beautify ? function() { print(" "); } : function() { @@ -349,6 +367,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, @@ -533,7 +552,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_New; + || p instanceof AST_New + || p instanceof AST_Binary + && p.operator === "**" + && this instanceof AST_UnaryPrefix + && p.left === this + && this.operator !== "++" + && this.operator !== "--"; }); PARENS(AST_Seq, function(output){ @@ -573,6 +598,20 @@ 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; + }); + PARENS(AST_PropAccess, function(output){ var p = output.parent(); if (p instanceof AST_New && p.expression === this) { @@ -650,6 +689,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; + self.names.forEach(function (name) { + if (first) first = false; else { output.comma(); output.space(); } + name.print(output); + }) + output.print(self.is_array ? "]" : "}"); + if (self.default) { + output.space(); + output.print('='); + output.space(); + self.default.print(output) + } + }) + DEFPRINT(AST_Debugger, function(self, output){ output.print("debugger"); output.semicolon(); @@ -774,7 +835,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); }); @@ -796,10 +861,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); + }); } output.with_parens(function(){ self.argnames.forEach(function(arg, i){ @@ -814,6 +888,62 @@ 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) { + output.print("`"); + for (var i = 0; i < self.segments.length; i++) { + if (typeof self.segments[i] !== "string") { + output.print("${"); + self.segments[i].print(output); + output.print("}"); + } else { + output.print(self.segments[i]); + } + } + 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; + if (needs_parens) { output.print("(") } + if (self.argnames.length === 1 && self.argnames[0] instanceof AST_Symbol && !self.argnames[0].default) { + 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(")") } + }); + DEFPRINT(AST_ConciseMethod, function(self, output){ + if (self.static) { + output.print("static"); + output.space(); + } + if (self.is_generator) { + output.print("*"); + } + self._do_print(output, true /* do not print "function" */); + }); + /* -----[ exits ]----- */ AST_Exit.DEFMETHOD("_do_print", function(output, kind){ output.print(kind); @@ -830,6 +960,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); @@ -986,12 +1127,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); @@ -1163,9 +1369,45 @@ function OutputStream(options) { }); else output.print("{}"); }); + DEFPRINT(AST_Class, function(self, output){ + output.print("class"); + output.space(); + if (self.name) { + self.name.print(output); + output.space(); + } + if (self.extends) { + output.print("extends"); + output.space(); + self.extends.print(output); + output.space(); + } + if (self.properties.length > 0) output.with_block(function(){ + self.properties.forEach(function(prop, i){ + if (i) { + output.newline(); + } + output.indent(); + prop.print(output); + }); + output.newline(); + }); + else output.print("{}"); + }); + DEFPRINT(AST_NewTarget, function(self, output) { + output.print("new.target"); + }); DEFPRINT(AST_ObjectKeyVal, function(self, output){ var key = self.key; var quote = self.quote; + var print_as_shorthand = self.shorthand && + self.value instanceof AST_Symbol && + self.key == self.value.print_to_string(); + + if (print_as_shorthand) { + output.print_name(key); + return; + } if (output.option("quote_keys")) { output.print_string(key + ""); } else if ((typeof key == "number" @@ -1186,20 +1428,56 @@ function OutputStream(options) { self.value.print(output); }); DEFPRINT(AST_ObjectSetter, function(self, output){ + if (self.static) { + output.print("static"); + output.space(); + } output.print("set"); output.space(); self.key.print(output); self.value._do_print(output, true); }); DEFPRINT(AST_ObjectGetter, function(self, output){ + if (self.static) { + output.print("static"); + output.space(); + } output.print("get"); output.space(); self.key.print(output); 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); + DEFPRINT(AST_ObjectComputedKeyVal, function(self, output) { + output.print("["); + self.key.print(output); + output.print("]:"); + output.space(); + self.value.print(output); + }); + 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); + if (self.default) { + output.space(); + output.print('='); + output.space(); + self.default.print(output) + } + }); + DEFPRINT(AST_SymbolMethod, function(self, output) { + if (self.name instanceof AST_Node) { + output.with_square(function() { + self.name.print(output); + }); + } else { + self._do_print(output); + } }); DEFPRINT(AST_Undefined, function(self, output){ output.print("void 0"); @@ -1214,6 +1492,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 bd6f95f6..28a4d990 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,66 +116,84 @@ 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("[]{}(),;:")); +var PUNC_CHARS = makePredicate(characters("[]{}(),;:`")); 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 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 +206,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; @@ -230,10 +259,10 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { 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)) { @@ -246,6 +275,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; @@ -335,6 +368,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 @@ -346,7 +382,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; var valid = parse_js_number(num); @@ -367,7 +403,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("SyntaxError: 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("SyntaxError: 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 @@ -449,24 +500,45 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { }); 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("SyntaxError: Expecting UnicodeEscapeSequence -- uXXXX"); - ch = read_escaped_char(); - if (!is_identifier_char(ch)) parse_error("SyntaxError: Unicode char: " + ch.charCodeAt(0) + " is not valid in identifier"); - name += ch; - backslash = false; + var name = "", ch, escaped = false, hex; + var read_escaped_identifier_char = function() { + escaped = true; + next(); + if (peek() !== "u") { + parse_error("SyntaxError: 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("SyntaxError: 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("SyntaxError: Invalid escaped identifier char"); + } + } else { + if (!is_identifier_char(ch)) { + break; + } + next(); + } + name += ch; + } + if (RESERVED_WORDS(name) && escaped) { + parse_error("SyntaxError: Escaped characters are not allowed in keywords"); } return name; }; @@ -526,11 +598,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() { @@ -582,11 +671,12 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { if (tok === next_token) continue; return tok; } + case 61: return handle_eq_sign(); } 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); @@ -599,6 +689,9 @@ function tokenizer($TEXT, filename, html5_comments, shebang) { parse_error("SyntaxError: Unexpected character '" + ch + "'"); }; + next_token.next = next; + next_token.peek = peek; + next_token.context = function(nc) { if (nc) S = nc; return S; @@ -653,7 +746,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) { @@ -674,7 +767,8 @@ var PRECEDENCE = (function(a, ret){ ["<", ">", "<=", ">=", "in", "instanceof"], [">>", "<<", ">>>"], ["+", "-"], - ["*", "/", "%"] + ["*", "/", "%"], + ["**"] ], {} ); @@ -706,6 +800,7 @@ function parse($TEXT, options) { prev : null, peeked : null, in_function : 0, + in_generator : -1, in_directives : true, in_loop : 0, labels : [] @@ -771,6 +866,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(); @@ -786,7 +885,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; @@ -845,6 +944,7 @@ function parse($TEXT, options) { }); case "[": case "(": + case "`": return simple_statement(); case ";": S.in_directives = false; @@ -881,6 +981,9 @@ function parse($TEXT, options) { case "for": return for_(); + case "class": + return class_(AST_DefClass); + case "function": return function_(AST_Defun); @@ -917,6 +1020,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; @@ -929,6 +1035,12 @@ function parse($TEXT, options) { body : statement() }); + case "import": + return tmp = import_(), semicolon(), tmp; + + case "export": + return tmp = export_(), semicolon(), tmp; + default: unexpected(); } @@ -937,6 +1049,10 @@ function parse($TEXT, options) { 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, "SyntaxError: 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 @@ -990,14 +1106,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("SyntaxError: 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); @@ -1017,8 +1142,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({ @@ -1029,38 +1166,144 @@ function parse($TEXT, options) { }); }; + var arrow_function = function(args) { + 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) { + 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 = params_or_seq_().as_params(croak); + var body = _function_body(true, is_generator); 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 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: as_symbol(AST_SymbolFunarg), + 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("SyntaxError: 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")) { @@ -1153,15 +1396,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: destructuring_(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(); @@ -1169,10 +1427,60 @@ function parse($TEXT, options) { return a; }; + var destructuring_ = embed_tokens(function (sym_type) { + var is_array = is("punc", "["); + var closing = is_array ? ']' : '}'; + var sym_type = sym_type || AST_SymbolRef; + + next(); + + var first = true, children = []; + while (!is("punc", closing)) { + if (first) first = false; else expect(","); + if (is("punc", closing)) break; + if (is("punc", ",")) { + children.push(new AST_Hole({ start: S.token, end: S.token })); + } else if (is("punc", "[") || is("punc", "{")) { + children.push(destructuring_(sym_type)); + } else if (is("expand", "...")) { + next(); + var symbol = _make_symbol(sym_type); + children.push(new AST_Expansion({ + start: prev(), + expression: symbol, + end: S.token + })); + next(); + } else if (is("name")) { + children.push(new (sym_type)({ + name : String(S.token.value), + start : S.token, + default: (next(), is("operator", "=")) ? (next(), expression(false)) : undefined, + end : S.token + })); + } else { + children.push(expression()); + } + } + next() + return new AST_Destructuring({ + names: children, + is_array: is_array + }) + }); + 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() }); }; @@ -1180,7 +1488,7 @@ function parse($TEXT, options) { var const_ = function() { return new AST_Const({ start : prev(), - definitions : vardefs(false, true), + definitions : vardefs(false, "const"), end : prev() }); }; @@ -1188,6 +1496,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(); @@ -1244,6 +1560,15 @@ function parse($TEXT, options) { } ret = _make_symbol(AST_SymbolRef); break; + case "punc": + if (S.token.value === "[") { + next(); + ret = expression(false); + if (S.token.type !== "punc" && S.token.value !== "]") { + unexpected(); + } + break; + } } next(); return ret; @@ -1257,16 +1582,19 @@ 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); + case "`": + return subscripts(template_string(), allow_calls); } unexpected(); } @@ -1277,12 +1605,51 @@ 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 (ATOMIC_START_TOKEN[S.token.type]) { return subscripts(as_atom_node(), allow_calls); } unexpected(); }; + function template_string() { + var tokenizer_S = S.input, start = S.token, segments = [], segment = "", ch; + + while ((ch = tokenizer_S.next()) !== "`") { + if (ch === "$" && tokenizer_S.peek() === "{") { + segments.push(segment); segment = ""; + tokenizer_S.next(); + next(); + segments.push(expression()); + if (!is("punc", "}")) { + // force error message + expect("}"); + } + continue; + } + segment += ch; + if (ch === "\\") { + segment += tokenizer_S.next(); + } + } + + segments.push(segment); + + 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)) { @@ -1290,6 +1657,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: S.token, expression: expression(),end: S.token})); } else { a.push(expression(false)); } @@ -1305,9 +1675,9 @@ function parse($TEXT, options) { }); }); - 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", "}")) @@ -1316,46 +1686,273 @@ function parse($TEXT, options) { var 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 : function_(AST_Accessor), - end : prev() - })); - continue; - } - if (name == "set") { - a.push(new AST_ObjectSetter({ - start : start, - key : as_atom_node(), - value : function_(AST_Accessor), - end : prev() - })); + if (type != "string" && type != "num" && !is("punc", ":")) { + var concise = concise_method_or_getset(name, start); + if (concise) { + a.push(concise); continue; } } - expect(":"); - a.push(new AST_ObjectKeyVal({ - start : start, - quote : start.quote, - key : name, - value : expression(false), - end : prev() - })); + + if (type == "punc" && start.value == "[") { + expect(":"); + a.push(new AST_ObjectComputedKeyVal({ + key: name, + value: expression(false) + })); + continue; + } + + if (is("operator", "=")) { + next(); + a.push(new AST_Assign({ + start: start, + // Symbol class doesn't matter. This is only meant to carry the symbol name into .as_params() since this is not normally valid. + left: new AST_SymbolRef({ + start: start, + end: start, + name: name + }), + operator: "=", + right: expression(false), + end: prev() + })); + } else if (!is("punc", ":")) { + // It's one of those object destructurings, the value is its own name + a.push(new AST_ObjectKeyVal({ + start: start, + quote: start.quote, + end: start, + key: name, + value: new AST_SymbolRef({ + start: start, + end: start, + name: name + }), + shorthand: true, + })); + } else { + expect(":"); + a.push(new AST_ObjectKeyVal({ + start : start, + quote : start.quote, + key : name, + value : expression(false), + end : prev() + })); + } } next(); - return new AST_Object({ properties: a }); + return new AST_Object({ properties: a }) }); + function class_(KindOfClass) { + var start, method, class_name, name, extends_, a = []; + + 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; + name = as_property_name(); + method = concise_method_or_getset(name, start, true); + if (!method) { unexpected(); } + a.push(method); + if (is("punc", ";")) { next(); } + } + + 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 is_static = false; + var is_generator = false; + if (is_class && name === "static" && !is("punc", "(")) { + is_static = true; + name = as_property_name(); + } + if (name === "*") { + is_generator = true; + name = as_property_name(); + } + if (is("punc", "(")) { + if (typeof name === "string") { + name = new AST_SymbolMethod({ + start: start, + name: name, + end: prev() + }); + } + return new AST_ConciseMethod({ + is_generator: is_generator, + start : start, + static : is_static, + name : name, + argnames : params_or_seq_().as_params(croak), + body : _function_body(true, is_generator), + end : prev() + }); + } + if (name == "get") { + return new AST_ObjectGetter({ + start : start, + static: is_static, + key : as_atom_node(), + value : function_(AST_Accessor), + end : prev() + }); + } + if (name == "set") { + return new AST_ObjectSetter({ + start : start, + static: is_static, + key : as_atom_node(), + value : function_(AST_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(); + } + + 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 "punc": + if (tmp.value === "[") { + var ex = expression(false); + expect("]"); + return ex; + } else unexpected(); case "name": + if (tmp.value === "yield" && S.input.has_directive("use strict") && !is_in_generator()) { + token_error(tmp, "SyntaxError: Unexpected yield identifier inside strict mode"); + } + case "string": + case "num": case "operator": case "keyword": case "atom": @@ -1381,7 +1978,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 @@ -1393,6 +1992,9 @@ function parse($TEXT, options) { if (!noerror) croak("SyntaxError: Name expected"); return null; } + if (is("name", "yield") && S.input.has_directive("use strict")) { + token_error(S.prev, "SyntaxError: Unexpected yield identifier inside strict mode"); + } var sym = _make_symbol(type); next(); return sym; @@ -1425,13 +2027,32 @@ function parse($TEXT, options) { return subscripts(new AST_Call({ start : start, expression : expr, - args : expr_list(")"), + args : call_args(), end : prev() }), true); } 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: as_symbol(AST_SymbolFunarg) + })); + } 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)) { @@ -1461,8 +2082,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({ @@ -1501,12 +2126,32 @@ 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, "SyntaxError: Unexpected yield identifier inside strict mode") + } + } + + if (start.type == "punc" && start.value == "(" && peek().value == ")") { + next(); + next(); + return arrow_function([]); + } + + var left = maybe_conditional(no_in); + var val = S.token.value; + if (is("operator") && ASSIGNMENT(val)) { if (is_assignable(left)) { next(); @@ -1520,12 +2165,35 @@ function parse($TEXT, options) { } croak("SyntaxError: Invalid assignment"); } + if (is("arrow")) { + left = new AST_SymbolFunarg({ + name: left.name, + start: left.start, + end: left.end, + }); + return arrow_function([left]) + } return left; }; 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 ((expr instanceof AST_SymbolRef || expr instanceof AST_PropAccess) && is("punc", "`")) { + return new AST_PrefixedTemplateString({ + start: start, + prefix: expr, + template_string: template_string() + }) + } if (commas && is("punc", ",")) { next(); return new AST_Seq({ diff --git a/lib/propmangle.js b/lib/propmangle.js index 08043d73..c8bb772e 100644 --- a/lib/propmangle.js +++ b/lib/propmangle.js @@ -44,10 +44,16 @@ "use strict"; function find_builtins() { + + // Compatibility fix for es5.1 and earlier where Symbol isn't defined + if (!global.Symbol) { + global.Symbol = new Function(); + } + var a = []; [ Object, Array, Function, Number, String, Boolean, Error, Math, - Date, RegExp + Date, RegExp, Symbol ].forEach(function(ctor){ Object.getOwnPropertyNames(ctor).map(add); if (ctor.prototype) { @@ -108,6 +114,9 @@ function mangle_properties(ast, options) { addStrings(node.property); } } + else if (node instanceof AST_ConciseMethod) { + add(node.name.name); + } })); // step 2: transform the tree, renaming properties @@ -127,6 +136,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 606a5a2f..d2712256 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.constant = false; this.index = index; @@ -63,11 +65,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; @@ -99,16 +107,33 @@ AST_Toplevel.DEFMETHOD("figure_out_scope", function(options){ var defun = null; var last_var_had_const_pragma = false; var nesting = 0; + var in_destructuring = null; + var in_export; var tw = new TreeWalker(function(node, descend){ - if (options.screw_ie8 && node instanceof AST_Catch) { + var create_a_block_scope = + (options.screw_ie8 && node instanceof AST_Catch) || + ((node instanceof AST_Block) && node.creates_block_scope()); + if (create_a_block_scope) { var save_scope = scope; scope = new AST_Scope(node); scope.init_scope_vars(nesting); scope.parent_scope = save_scope; + scope.is_block_scope = true; + 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(nesting); var save_scope = node.parent_scope = scope; @@ -122,6 +147,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)) { @@ -140,12 +170,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 @@ -153,15 +187,32 @@ 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_Var) { last_var_had_const_pragma = node.has_const_pragma(); } 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.constant = node instanceof AST_SymbolConst || last_var_had_const_pragma; + def.destructuring = in_destructuring; def.init = tw.parent().value; } else if (node instanceof AST_SymbolCatch) { @@ -182,6 +233,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) { @@ -191,6 +243,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; @@ -242,6 +301,14 @@ AST_Scope.DEFMETHOD("init_scope_vars", function(nesting){ this.nesting = nesting; // the nesting level of this scope (0 means toplevel) }); +AST_Block.DEFMETHOD("creates_block_scope", function() { + return ( + !(this instanceof AST_Lambda) && + !(this instanceof AST_Toplevel) && + !(this instanceof AST_Class) + ); +}); + AST_Lambda.DEFMETHOD("init_scope_vars", function(){ AST_Scope.prototype.init_scope_vars.apply(this, arguments); this.uses_arguments = false; @@ -269,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); @@ -331,7 +402,8 @@ AST_Scope.DEFMETHOD("references", function(sym){ }); 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 @@ -382,7 +454,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 }); }); @@ -431,13 +504,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; @@ -497,12 +576,16 @@ AST_Toplevel.DEFMETHOD("compute_char_frequency", function(options){ 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..ae839417 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); }); @@ -215,4 +227,8 @@ TreeTransformer.prototype = new TreeWalker; self.value = self.value.transform(tw); }); + _(AST_Expansion, function(self, tw){ + self.expression = self.expression.transform(tw); + }); + })(); diff --git a/test/compress/arrays.js b/test/compress/arrays.js index e636347f..bc573083 100644 --- a/test/compress/arrays.js +++ b/test/compress/arrays.js @@ -72,3 +72,76 @@ constant_join_2: { var f = "strstr" + variable + "foobarmoo" + foo; } } + +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] + } +} \ No newline at end of file diff --git a/test/compress/arrow.js b/test/compress/arrow.js new file mode 100644 index 00000000..1a8b17ed --- /dev/null +++ b/test/compress/arrow.js @@ -0,0 +1,92 @@ +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);" +} diff --git a/test/compress/block-scope.js b/test/compress/block-scope.js new file mode 100644 index 00000000..dd243009 --- /dev/null +++ b/test/compress/block-scope.js @@ -0,0 +1,132 @@ + +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); + }()); + } +} diff --git a/test/compress/dead-code.js b/test/compress/dead-code.js index fa4b37d6..652645d9 100644 --- a/test/compress/dead-code.js +++ b/test/compress/dead-code.js @@ -88,6 +88,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..6ea54ac1 --- /dev/null +++ b/test/compress/destructuring.js @@ -0,0 +1,43 @@ + +destructuring_arrays: { + input: { + var [aa, bb] = cc; + } + expect: { + var[aa,bb]=cc; + } +} + +destructuring_objects: { + input: { + var {aa, bb} = {aa:1, bb:2}; + } + expect: { + var{aa,bb}={aa:1,bb:2}; + } +} + +nested_destructuring_objects: { + input: { + var [{a},b] = c; + } + expect_exact: 'var[{a},b]=c;'; +} + +destructuring_vardef_in_loops: { + 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: { + input: { + ({a, b}); + [{a}]; + f({x}); + } + expect_exact: "({a,b});[{a}];f({x});" +} + diff --git a/test/compress/drop-unused.js b/test/compress/drop-unused.js index 035a428e..5c4ffde9 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 d27582f3..f2658c55 100644 --- a/test/compress/evaluate.js +++ b/test/compress/evaluate.js @@ -37,3 +37,91 @@ 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; + } +} diff --git a/test/compress/expansions.js b/test/compress/expansions.js new file mode 100644 index 00000000..a6537547 --- /dev/null +++ b/test/compress/expansions.js @@ -0,0 +1,17 @@ + +expand_arguments: { + input: { + func(a, ...rest); + func(...all); + } + expect_exact: "func(a,...rest);func(...all);" +} + +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..ba1e827c --- /dev/null +++ b/test/compress/harmony.js @@ -0,0 +1,527 @@ +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\";" +} + +arrow_function_parens: { + input: { + something && (() => {}); + } + expect_exact: "something&&(()=>{});" +} +arrow_function_parens_2: { + input: { + (() => null)(); + } + expect_exact: "(()=>null)();" +} + +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; + } +} + +computed_property_names: { + input: { + obj({ ["x" + "x"]: 6 }); + } + expect_exact: 'obj({["x"+"x"]:6});' +} + +shorthand_properties: { + mangle = true; + input: (function() { + var prop = 1; + const value = {prop}; + return value; + })(); + expect: (function() { + var n = 1; + const r = {prop:n}; + return r; + })(); +} + +typeof_arrow_functions: { + options = { + evaluate: true + } + input: { + var foo = typeof (x) => null; + } + expect_exact: "var foo=\"function\";" +} + +template_strings: { + input: { + ``; + `xx\`x`; + `${ foo + 2 }`; + ` foo ${ bar + `baz ${ qux }` }`; + } + expect_exact: "``;`xx\\`x`;`${foo+2}`;` foo ${bar+`baz ${qux}`}`;"; +} + +template_string_prefixes: { + input: { + String.raw`foo`; + foo `bar`; + } + expect_exact: "String.raw`foo`;foo`bar`;"; +} + +destructuring_arguments: { + 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; + } +} + +default_arguments: { + 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: { + 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;" +} + +concise_methods: { + input: { + x = { + foo(a, b) { + return x; + } + } + y = { + foo([{a}]) { + return a; + }, + bar(){} + } + } + expect_exact: "x={foo(a,b){return x}};y={foo([{a}]){return a},bar(){}};" +} + +concise_methods_with_computed_property: { + options = { + evaluate: true + } + input: { + var foo = { + [Symbol.iterator]() { + return { /* stuff */ } + }, + [1 + 2]() { + return 3; + } + } + } + expect: { + var foo = { + [Symbol.iterator]() { + return { /* stuff */ } + }, + [3]() { + return 3; + } + } + } +} + +concise_methods_and_mangle_props: { + mangle_props = { + regex: /_/ + }; + input: { + function x() { + obj = { + _foo() { return 1; } + } + } + } + expect: { + function x() { + obj = { + a() { return 1; } + } + } + } +} + +concise_generators: { + input: { + x = { + *foo(a, b) { + return x; + } + } + y = { + *foo([{a}]) { + yield a; + }, + bar(){} + } + } + expect_exact: "x={*foo(a,b){return x}};y={*foo([{a}]){yield a},bar(){}};" +} + +concise_methods_and_keyword_names: { + input: { + x = { + catch() {}, + throw() {} + } + } + expect: { + x={catch(){},throw(){}}; + } +} + +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{};" +} + +getter_setter_with_computed_value: { + input: { + class C { + get ['a']() { + return 'A'; + } + set ['a'](value) { + do_something(a); + } + } + } + expect: { + class C { + get ['a']() { + return 'A'; + } + set ['a'](value) { + do_something(a); + } + } + } +} + +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']() {} + } + } +} + +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. */ + } +} \ No newline at end of file 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/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-203.js b/test/compress/issue-203.js new file mode 100644 index 00000000..d894c586 --- /dev/null +++ b/test/compress/issue-203.js @@ -0,0 +1,30 @@ + +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 + } + 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/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/unicode.js b/test/compress/unicode.js index 9fb9ab8c..f08072e0 100644 --- a/test/compress/unicode.js +++ b/test/compress/unicode.js @@ -15,3 +15,88 @@ 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}";' +} + +unicode_output_es5_surrogates: { + beautify = {ascii_only: true, ecma: 5} + input: { + var \u{10000} = "6 length unicode character: \u{10FFFF}"; + } + expect_exact: 'var \\ud800\\udc00="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}";' +} + +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}"; + var \u{2f800} = "\u{100000}"; + } + 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 µþ="µþ";' +} \ No newline at end of file diff --git a/test/compress/yield.js b/test/compress/yield.js new file mode 100644 index 00000000..9fe9f6c8 --- /dev/null +++ b/test/compress/yield.js @@ -0,0 +1,142 @@ +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"; + class yield {} + } + 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"; + class yield {} + } +} \ No newline at end of file diff --git a/test/mocha/arrow.js b/test/mocha/arrow.js new file mode 100644 index 00000000..a02286c2 --- /dev/null +++ b/test/mocha/arrow.js @@ -0,0 +1,25 @@ +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); + } + }); +}); diff --git a/test/mocha/builtins.js b/test/mocha/builtins.js new file mode 100644 index 00000000..2782830a --- /dev/null +++ b/test/mocha/builtins.js @@ -0,0 +1,26 @@ +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,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); + }); +}); diff --git a/test/mocha/class.js b/test/mocha/class.js new file mode 100644 index 00000000..0df67e6a --- /dev/null +++ b/test/mocha/class.js @@ -0,0 +1,26 @@ +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 === "SyntaxError: Unexpected token: expand (...)"; + } + + for (var i = 0; i < tests.length; i++) { + assert.throws(test(tests[i]), error); + } + }); +}); 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..34fc70b3 --- /dev/null +++ b/test/mocha/function.js @@ -0,0 +1,30 @@ +var assert = require("assert"); +var uglify = require("../../"); + +describe("Function", function() { + 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 === "SyntaxError: Unexpected token: expand (...)"; + } + + for (var i = 0; i < tests.length; i++) { + assert.throws(test(tests[i]), error); + } + }); +}); 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..a9373747 --- /dev/null +++ b/test/mocha/object.js @@ -0,0 +1,20 @@ +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); + }); +}); \ No newline at end of file diff --git a/test/mocha/unicode.js b/test/mocha/unicode.js new file mode 100644 index 00000000..332adab7 --- /dev/null +++ b/test/mocha/unicode.js @@ -0,0 +1,134 @@ +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";' + ]; + + 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..1211ea9c --- /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 && + e.message === "SyntaxError: Unexpected yield identifier inside strict mode"; + } + + 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 diff --git a/test/parser.js b/test/parser.js new file mode 100644 index 00000000..84f8755c --- /dev/null +++ b/test/parser.js @@ -0,0 +1,138 @@ + +var UglifyJS = require(".."); +var ok = require('assert'); + +module.exports = function () { + console.log("--- Parser tests"); + + // Destructuring arguments + + // 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 = UglifyJS.parse('(function ({a, b}) {})').body[0].body; + var destr_fun2 = UglifyJS.parse('(function ([a, [b]]) {})').body[0].body; + + ok.equal(destr_fun1.argnames.length, 1); + ok.equal(destr_fun2.argnames.length, 1); + + var destr_fun1 = UglifyJS.parse('({a, b}) => null').body[0].body; + var destr_fun2 = UglifyJS.parse('([a, [b]]) => null').body[0].body; + + ok.equal(destr_fun1.argnames.length, 1); + ok.equal(destr_fun2.argnames.length, 1); + + var destruct1 = destr_fun1.argnames[0]; + var destruct2 = destr_fun2.argnames[0]; + + ok(destruct1 instanceof UglifyJS.AST_Destructuring); + ok(destruct2 instanceof UglifyJS.AST_Destructuring); + ok(destruct2.names[1] instanceof UglifyJS.AST_Destructuring); + + ok.equal(destruct1.start.value, '{'); + ok.equal(destruct1.end.value, '}'); + ok.equal(destruct2.start.value, '['); + ok.equal(destruct2.end.value, ']'); + + ok.equal(destruct1.is_array, false); + ok.equal(destruct2.is_array, true); + + var aAndB = [ + ['SymbolFunarg', 'a'], + ['SymbolFunarg', 'b'] + ]; + + ok.deepEqual( + [ + destruct1.names[0].TYPE, + destruct1.names[0].name], + aAndB[0]); + + ok.deepEqual( + [ + destruct2.names[1].names[0].TYPE, + destruct2.names[1].names[0].name + ], + aAndB[1]); + + ok.deepEqual( + get_args(destr_fun1.args_as_names()), + aAndB) + ok.deepEqual( + get_args(destr_fun2.args_as_names()), + aAndB) + + // Making sure we don't accidentally accept things which + // Aren't argument destructurings + + ok.throws(function () { + UglifyJS.parse('(function ([]) {})'); + }, /Invalid destructuring function parameter/); + + ok.throws(function () { + UglifyJS.parse('(function ( { a, [ b ] } ) { })') + }); + + ok.throws(function () { + UglifyJS.parse('(function (1) { })'); + }, /Invalid function parameter/); + + ok.throws(function () { + UglifyJS.parse('(function (this) { })'); + }); + + ok.throws(function () { + UglifyJS.parse('(function ([1]) { })'); + }, /Invalid function parameter/); + + ok.throws(function () { + UglifyJS.parse('(function [a] { })'); + }); + + // Destructuring variable declaration + + var decls = UglifyJS.parse('var {a,b} = foo, { c, d } = bar'); + + ok.equal(decls.body[0].TYPE, 'Var'); + ok.equal(decls.body[0].definitions.length, 2); + ok.equal(decls.body[0].definitions[0].name.TYPE, 'Destructuring'); + ok.equal(decls.body[0].definitions[0].value.TYPE, 'SymbolRef'); + + var nested_def = UglifyJS.parse('var [{x}] = foo').body[0].definitions[0]; + + ok.equal(nested_def.name.names[0].names[0].TYPE, 'SymbolVar'); + ok.equal(nested_def.name.names[0].names[0].name, 'x'); + + var holey_def = UglifyJS.parse('const [,,third] = [1,2,3]').body[0].definitions[0]; + + ok.equal(holey_def.name.names[0].TYPE, 'Hole'); + ok.equal(holey_def.name.names[2].TYPE, 'SymbolConst'); + + var expanding_def = UglifyJS.parse('var [first, ...rest] = [1,2,3]').body[0].definitions[0]; + + ok.equal(expanding_def.name.names[0].TYPE, 'SymbolVar'); + ok.equal(expanding_def.name.names[1].TYPE, 'Expansion'); + ok.equal(expanding_def.name.names[1].expression.TYPE, 'SymbolVar'); + + // generators + var generators_def = UglifyJS.parse('function* fn() {}').body[0]; + ok.equal(generators_def.is_generator, true); + + ok.throws(function () { + UglifyJS.parse('function* (){ }'); + }); + + var generators_yield_def = UglifyJS.parse('function* fn() {\nyield remote();\}').body[0].body[0]; + ok.strictEqual(generators_yield_def.body.is_star, false); +} + +// Run standalone +if (module.parent === null) { + module.exports(); +} + diff --git a/test/run-tests.js b/test/run-tests.js index 0fdee6f1..053d1b09 100755 --- a/test/run-tests.js +++ b/test/run-tests.js @@ -30,6 +30,10 @@ run_ast_conversion_tests({ iterations: 1000 }); +var run_parser_tests = require('./parser.js'); + +run_parser_tests(); + /* -----[ utils ]----- */ function tmpl() {