[Pkg-javascript-commits] [node-estraverse] 01/07: Import Upstream version 4.2.0
Praveen Arimbrathodiyil
praveen at moszumanska.debian.org
Mon Oct 10 11:05:43 UTC 2016
This is an automated email from the git hooks/post-receive script.
praveen pushed a commit to branch master
in repository node-estraverse.
commit 3275bbe5790f23551c1fb41fa23900c6ee762c33
Author: Praveen Arimbrathodiyil <praveen at debian.org>
Date: Mon Oct 10 15:53:39 2016 +0530
Import Upstream version 4.2.0
---
.babelrc | 3 +
.gitignore | 14 +
.jshintrc | 16 +
.npmignore | 15 +
.travis.yml | 12 +
LICENSE.BSD | 19 +
README.md | 153 ++
estraverse.js | 849 +++++++
gulpfile.js | 70 +
package.json | 40 +
test/checkDump.js | 35 +
test/controller.js | 67 +
test/dumper.js | 59 +
test/es6.js | 391 ++++
test/replace.js | 483 ++++
test/third_party/esprima.js | 5353 +++++++++++++++++++++++++++++++++++++++++++
test/traverse.js | 562 +++++
test/type.js | 57 +
18 files changed, 8198 insertions(+)
diff --git a/.babelrc b/.babelrc
new file mode 100644
index 0000000..dc1bc4f
--- /dev/null
+++ b/.babelrc
@@ -0,0 +1,3 @@
+{
+ "presets": ["es2015"]
+}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5b78fb8
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,14 @@
+# Emacs
+*~
+\#*\#
+
+# Node modules
+node_modules/
+
+# Cover
+.coverage_data/
+cover_html/
+coverage/
+
+npm-debug.log
+.vimrc.local
diff --git a/.jshintrc b/.jshintrc
new file mode 100644
index 0000000..f642dae
--- /dev/null
+++ b/.jshintrc
@@ -0,0 +1,16 @@
+{
+ "curly": true,
+ "eqeqeq": true,
+ "immed": true,
+ "eqnull": true,
+ "latedef": true,
+ "noarg": true,
+ "noempty": true,
+ "quotmark": "single",
+ "undef": true,
+ "unused": true,
+ "strict": true,
+ "trailing": true,
+
+ "node": true
+}
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 0000000..d2db91d
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1,15 @@
+npm-debug.log
+.DS_Store
+.vimrc.local
+t.js
+.travis.yml
+.npmignore
+/tmp/
+/.git/
+/node_modules/
+/tools/
+/test/
+/build/
+/cover_html/
+/coverage/
+/.coverage_data/
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..ebddf59
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,12 @@
+language: node_js
+node_js:
+ - "0.10"
+ - "0.11"
+ - "0.12"
+ - "iojs"
+ - 4
+ - 5
+
+matrix:
+ allow_failures:
+ - node_js: "0.11"
diff --git a/LICENSE.BSD b/LICENSE.BSD
new file mode 100644
index 0000000..3e580c3
--- /dev/null
+++ b/LICENSE.BSD
@@ -0,0 +1,19 @@
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..0ec7cb7
--- /dev/null
+++ b/README.md
@@ -0,0 +1,153 @@
+### Estraverse [](http://travis-ci.org/estools/estraverse)
+
+Estraverse ([estraverse](http://github.com/estools/estraverse)) is
+[ECMAScript](http://www.ecma-international.org/publications/standards/Ecma-262.htm)
+traversal functions from [esmangle project](http://github.com/estools/esmangle).
+
+### Documentation
+
+You can find usage docs at [wiki page](https://github.com/estools/estraverse/wiki/Usage).
+
+### Example Usage
+
+The following code will output all variables declared at the root of a file.
+
+```javascript
+estraverse.traverse(ast, {
+ enter: function (node, parent) {
+ if (node.type == 'FunctionExpression' || node.type == 'FunctionDeclaration')
+ return estraverse.VisitorOption.Skip;
+ },
+ leave: function (node, parent) {
+ if (node.type == 'VariableDeclarator')
+ console.log(node.id.name);
+ }
+});
+```
+
+We can use `this.skip`, `this.remove` and `this.break` functions instead of using Skip, Remove and Break.
+
+```javascript
+estraverse.traverse(ast, {
+ enter: function (node) {
+ this.break();
+ }
+});
+```
+
+And estraverse provides `estraverse.replace` function. When returning node from `enter`/`leave`, current node is replaced with it.
+
+```javascript
+result = estraverse.replace(tree, {
+ enter: function (node) {
+ // Replace it with replaced.
+ if (node.type === 'Literal')
+ return replaced;
+ }
+});
+```
+
+By passing `visitor.keys` mapping, we can extend estraverse traversing functionality.
+
+```javascript
+// This tree contains a user-defined `TestExpression` node.
+var tree = {
+ type: 'TestExpression',
+
+ // This 'argument' is the property containing the other **node**.
+ argument: {
+ type: 'Literal',
+ value: 20
+ },
+
+ // This 'extended' is the property not containing the other **node**.
+ extended: true
+};
+estraverse.traverse(tree, {
+ enter: function (node) { },
+
+ // Extending the existing traversing rules.
+ keys: {
+ // TargetNodeName: [ 'keys', 'containing', 'the', 'other', '**node**' ]
+ TestExpression: ['argument']
+ }
+});
+```
+
+By passing `visitor.fallback` option, we can control the behavior when encountering unknown nodes.
+
+```javascript
+// This tree contains a user-defined `TestExpression` node.
+var tree = {
+ type: 'TestExpression',
+
+ // This 'argument' is the property containing the other **node**.
+ argument: {
+ type: 'Literal',
+ value: 20
+ },
+
+ // This 'extended' is the property not containing the other **node**.
+ extended: true
+};
+estraverse.traverse(tree, {
+ enter: function (node) { },
+
+ // Iterating the child **nodes** of unknown nodes.
+ fallback: 'iteration'
+});
+```
+
+When `visitor.fallback` is a function, we can determine which keys to visit on each node.
+
+```javascript
+// This tree contains a user-defined `TestExpression` node.
+var tree = {
+ type: 'TestExpression',
+
+ // This 'argument' is the property containing the other **node**.
+ argument: {
+ type: 'Literal',
+ value: 20
+ },
+
+ // This 'extended' is the property not containing the other **node**.
+ extended: true
+};
+estraverse.traverse(tree, {
+ enter: function (node) { },
+
+ // Skip the `argument` property of each node
+ fallback: function(node) {
+ return Object.keys(node).filter(function(key) {
+ return key !== 'argument';
+ });
+ }
+});
+```
+
+### License
+
+Copyright (C) 2012-2016 [Yusuke Suzuki](http://github.com/Constellation)
+ (twitter: [@Constellation](http://twitter.com/Constellation)) and other contributors.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/estraverse.js b/estraverse.js
new file mode 100644
index 0000000..09ae478
--- /dev/null
+++ b/estraverse.js
@@ -0,0 +1,849 @@
+/*
+ Copyright (C) 2012-2013 Yusuke Suzuki <utatane.tea at gmail.com>
+ Copyright (C) 2012 Ariya Hidayat <ariya.hidayat at gmail.com>
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+/*jslint vars:false, bitwise:true*/
+/*jshint indent:4*/
+/*global exports:true*/
+(function clone(exports) {
+ 'use strict';
+
+ var Syntax,
+ isArray,
+ VisitorOption,
+ VisitorKeys,
+ objectCreate,
+ objectKeys,
+ BREAK,
+ SKIP,
+ REMOVE;
+
+ function ignoreJSHintError() { }
+
+ isArray = Array.isArray;
+ if (!isArray) {
+ isArray = function isArray(array) {
+ return Object.prototype.toString.call(array) === '[object Array]';
+ };
+ }
+
+ function deepCopy(obj) {
+ var ret = {}, key, val;
+ for (key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ val = obj[key];
+ if (typeof val === 'object' && val !== null) {
+ ret[key] = deepCopy(val);
+ } else {
+ ret[key] = val;
+ }
+ }
+ }
+ return ret;
+ }
+
+ function shallowCopy(obj) {
+ var ret = {}, key;
+ for (key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ ret[key] = obj[key];
+ }
+ }
+ return ret;
+ }
+ ignoreJSHintError(shallowCopy);
+
+ // based on LLVM libc++ upper_bound / lower_bound
+ // MIT License
+
+ function upperBound(array, func) {
+ var diff, len, i, current;
+
+ len = array.length;
+ i = 0;
+
+ while (len) {
+ diff = len >>> 1;
+ current = i + diff;
+ if (func(array[current])) {
+ len = diff;
+ } else {
+ i = current + 1;
+ len -= diff + 1;
+ }
+ }
+ return i;
+ }
+
+ function lowerBound(array, func) {
+ var diff, len, i, current;
+
+ len = array.length;
+ i = 0;
+
+ while (len) {
+ diff = len >>> 1;
+ current = i + diff;
+ if (func(array[current])) {
+ i = current + 1;
+ len -= diff + 1;
+ } else {
+ len = diff;
+ }
+ }
+ return i;
+ }
+ ignoreJSHintError(lowerBound);
+
+ objectCreate = Object.create || (function () {
+ function F() { }
+
+ return function (o) {
+ F.prototype = o;
+ return new F();
+ };
+ })();
+
+ objectKeys = Object.keys || function (o) {
+ var keys = [], key;
+ for (key in o) {
+ keys.push(key);
+ }
+ return keys;
+ };
+
+ function extend(to, from) {
+ var keys = objectKeys(from), key, i, len;
+ for (i = 0, len = keys.length; i < len; i += 1) {
+ key = keys[i];
+ to[key] = from[key];
+ }
+ return to;
+ }
+
+ Syntax = {
+ AssignmentExpression: 'AssignmentExpression',
+ AssignmentPattern: 'AssignmentPattern',
+ ArrayExpression: 'ArrayExpression',
+ ArrayPattern: 'ArrayPattern',
+ ArrowFunctionExpression: 'ArrowFunctionExpression',
+ AwaitExpression: 'AwaitExpression', // CAUTION: It's deferred to ES7.
+ BlockStatement: 'BlockStatement',
+ BinaryExpression: 'BinaryExpression',
+ BreakStatement: 'BreakStatement',
+ CallExpression: 'CallExpression',
+ CatchClause: 'CatchClause',
+ ClassBody: 'ClassBody',
+ ClassDeclaration: 'ClassDeclaration',
+ ClassExpression: 'ClassExpression',
+ ComprehensionBlock: 'ComprehensionBlock', // CAUTION: It's deferred to ES7.
+ ComprehensionExpression: 'ComprehensionExpression', // CAUTION: It's deferred to ES7.
+ ConditionalExpression: 'ConditionalExpression',
+ ContinueStatement: 'ContinueStatement',
+ DebuggerStatement: 'DebuggerStatement',
+ DirectiveStatement: 'DirectiveStatement',
+ DoWhileStatement: 'DoWhileStatement',
+ EmptyStatement: 'EmptyStatement',
+ ExportAllDeclaration: 'ExportAllDeclaration',
+ ExportDefaultDeclaration: 'ExportDefaultDeclaration',
+ ExportNamedDeclaration: 'ExportNamedDeclaration',
+ ExportSpecifier: 'ExportSpecifier',
+ ExpressionStatement: 'ExpressionStatement',
+ ForStatement: 'ForStatement',
+ ForInStatement: 'ForInStatement',
+ ForOfStatement: 'ForOfStatement',
+ FunctionDeclaration: 'FunctionDeclaration',
+ FunctionExpression: 'FunctionExpression',
+ GeneratorExpression: 'GeneratorExpression', // CAUTION: It's deferred to ES7.
+ Identifier: 'Identifier',
+ IfStatement: 'IfStatement',
+ ImportDeclaration: 'ImportDeclaration',
+ ImportDefaultSpecifier: 'ImportDefaultSpecifier',
+ ImportNamespaceSpecifier: 'ImportNamespaceSpecifier',
+ ImportSpecifier: 'ImportSpecifier',
+ Literal: 'Literal',
+ LabeledStatement: 'LabeledStatement',
+ LogicalExpression: 'LogicalExpression',
+ MemberExpression: 'MemberExpression',
+ MetaProperty: 'MetaProperty',
+ MethodDefinition: 'MethodDefinition',
+ ModuleSpecifier: 'ModuleSpecifier',
+ NewExpression: 'NewExpression',
+ ObjectExpression: 'ObjectExpression',
+ ObjectPattern: 'ObjectPattern',
+ Program: 'Program',
+ Property: 'Property',
+ RestElement: 'RestElement',
+ ReturnStatement: 'ReturnStatement',
+ SequenceExpression: 'SequenceExpression',
+ SpreadElement: 'SpreadElement',
+ Super: 'Super',
+ SwitchStatement: 'SwitchStatement',
+ SwitchCase: 'SwitchCase',
+ TaggedTemplateExpression: 'TaggedTemplateExpression',
+ TemplateElement: 'TemplateElement',
+ TemplateLiteral: 'TemplateLiteral',
+ ThisExpression: 'ThisExpression',
+ ThrowStatement: 'ThrowStatement',
+ TryStatement: 'TryStatement',
+ UnaryExpression: 'UnaryExpression',
+ UpdateExpression: 'UpdateExpression',
+ VariableDeclaration: 'VariableDeclaration',
+ VariableDeclarator: 'VariableDeclarator',
+ WhileStatement: 'WhileStatement',
+ WithStatement: 'WithStatement',
+ YieldExpression: 'YieldExpression'
+ };
+
+ VisitorKeys = {
+ AssignmentExpression: ['left', 'right'],
+ AssignmentPattern: ['left', 'right'],
+ ArrayExpression: ['elements'],
+ ArrayPattern: ['elements'],
+ ArrowFunctionExpression: ['params', 'body'],
+ AwaitExpression: ['argument'], // CAUTION: It's deferred to ES7.
+ BlockStatement: ['body'],
+ BinaryExpression: ['left', 'right'],
+ BreakStatement: ['label'],
+ CallExpression: ['callee', 'arguments'],
+ CatchClause: ['param', 'body'],
+ ClassBody: ['body'],
+ ClassDeclaration: ['id', 'superClass', 'body'],
+ ClassExpression: ['id', 'superClass', 'body'],
+ ComprehensionBlock: ['left', 'right'], // CAUTION: It's deferred to ES7.
+ ComprehensionExpression: ['blocks', 'filter', 'body'], // CAUTION: It's deferred to ES7.
+ ConditionalExpression: ['test', 'consequent', 'alternate'],
+ ContinueStatement: ['label'],
+ DebuggerStatement: [],
+ DirectiveStatement: [],
+ DoWhileStatement: ['body', 'test'],
+ EmptyStatement: [],
+ ExportAllDeclaration: ['source'],
+ ExportDefaultDeclaration: ['declaration'],
+ ExportNamedDeclaration: ['declaration', 'specifiers', 'source'],
+ ExportSpecifier: ['exported', 'local'],
+ ExpressionStatement: ['expression'],
+ ForStatement: ['init', 'test', 'update', 'body'],
+ ForInStatement: ['left', 'right', 'body'],
+ ForOfStatement: ['left', 'right', 'body'],
+ FunctionDeclaration: ['id', 'params', 'body'],
+ FunctionExpression: ['id', 'params', 'body'],
+ GeneratorExpression: ['blocks', 'filter', 'body'], // CAUTION: It's deferred to ES7.
+ Identifier: [],
+ IfStatement: ['test', 'consequent', 'alternate'],
+ ImportDeclaration: ['specifiers', 'source'],
+ ImportDefaultSpecifier: ['local'],
+ ImportNamespaceSpecifier: ['local'],
+ ImportSpecifier: ['imported', 'local'],
+ Literal: [],
+ LabeledStatement: ['label', 'body'],
+ LogicalExpression: ['left', 'right'],
+ MemberExpression: ['object', 'property'],
+ MetaProperty: ['meta', 'property'],
+ MethodDefinition: ['key', 'value'],
+ ModuleSpecifier: [],
+ NewExpression: ['callee', 'arguments'],
+ ObjectExpression: ['properties'],
+ ObjectPattern: ['properties'],
+ Program: ['body'],
+ Property: ['key', 'value'],
+ RestElement: [ 'argument' ],
+ ReturnStatement: ['argument'],
+ SequenceExpression: ['expressions'],
+ SpreadElement: ['argument'],
+ Super: [],
+ SwitchStatement: ['discriminant', 'cases'],
+ SwitchCase: ['test', 'consequent'],
+ TaggedTemplateExpression: ['tag', 'quasi'],
+ TemplateElement: [],
+ TemplateLiteral: ['quasis', 'expressions'],
+ ThisExpression: [],
+ ThrowStatement: ['argument'],
+ TryStatement: ['block', 'handler', 'finalizer'],
+ UnaryExpression: ['argument'],
+ UpdateExpression: ['argument'],
+ VariableDeclaration: ['declarations'],
+ VariableDeclarator: ['id', 'init'],
+ WhileStatement: ['test', 'body'],
+ WithStatement: ['object', 'body'],
+ YieldExpression: ['argument']
+ };
+
+ // unique id
+ BREAK = {};
+ SKIP = {};
+ REMOVE = {};
+
+ VisitorOption = {
+ Break: BREAK,
+ Skip: SKIP,
+ Remove: REMOVE
+ };
+
+ function Reference(parent, key) {
+ this.parent = parent;
+ this.key = key;
+ }
+
+ Reference.prototype.replace = function replace(node) {
+ this.parent[this.key] = node;
+ };
+
+ Reference.prototype.remove = function remove() {
+ if (isArray(this.parent)) {
+ this.parent.splice(this.key, 1);
+ return true;
+ } else {
+ this.replace(null);
+ return false;
+ }
+ };
+
+ function Element(node, path, wrap, ref) {
+ this.node = node;
+ this.path = path;
+ this.wrap = wrap;
+ this.ref = ref;
+ }
+
+ function Controller() { }
+
+ // API:
+ // return property path array from root to current node
+ Controller.prototype.path = function path() {
+ var i, iz, j, jz, result, element;
+
+ function addToPath(result, path) {
+ if (isArray(path)) {
+ for (j = 0, jz = path.length; j < jz; ++j) {
+ result.push(path[j]);
+ }
+ } else {
+ result.push(path);
+ }
+ }
+
+ // root node
+ if (!this.__current.path) {
+ return null;
+ }
+
+ // first node is sentinel, second node is root element
+ result = [];
+ for (i = 2, iz = this.__leavelist.length; i < iz; ++i) {
+ element = this.__leavelist[i];
+ addToPath(result, element.path);
+ }
+ addToPath(result, this.__current.path);
+ return result;
+ };
+
+ // API:
+ // return type of current node
+ Controller.prototype.type = function () {
+ var node = this.current();
+ return node.type || this.__current.wrap;
+ };
+
+ // API:
+ // return array of parent elements
+ Controller.prototype.parents = function parents() {
+ var i, iz, result;
+
+ // first node is sentinel
+ result = [];
+ for (i = 1, iz = this.__leavelist.length; i < iz; ++i) {
+ result.push(this.__leavelist[i].node);
+ }
+
+ return result;
+ };
+
+ // API:
+ // return current node
+ Controller.prototype.current = function current() {
+ return this.__current.node;
+ };
+
+ Controller.prototype.__execute = function __execute(callback, element) {
+ var previous, result;
+
+ result = undefined;
+
+ previous = this.__current;
+ this.__current = element;
+ this.__state = null;
+ if (callback) {
+ result = callback.call(this, element.node, this.__leavelist[this.__leavelist.length - 1].node);
+ }
+ this.__current = previous;
+
+ return result;
+ };
+
+ // API:
+ // notify control skip / break
+ Controller.prototype.notify = function notify(flag) {
+ this.__state = flag;
+ };
+
+ // API:
+ // skip child nodes of current node
+ Controller.prototype.skip = function () {
+ this.notify(SKIP);
+ };
+
+ // API:
+ // break traversals
+ Controller.prototype['break'] = function () {
+ this.notify(BREAK);
+ };
+
+ // API:
+ // remove node
+ Controller.prototype.remove = function () {
+ this.notify(REMOVE);
+ };
+
+ Controller.prototype.__initialize = function(root, visitor) {
+ this.visitor = visitor;
+ this.root = root;
+ this.__worklist = [];
+ this.__leavelist = [];
+ this.__current = null;
+ this.__state = null;
+ this.__fallback = null;
+ if (visitor.fallback === 'iteration') {
+ this.__fallback = objectKeys;
+ } else if (typeof visitor.fallback === 'function') {
+ this.__fallback = visitor.fallback;
+ }
+
+ this.__keys = VisitorKeys;
+ if (visitor.keys) {
+ this.__keys = extend(objectCreate(this.__keys), visitor.keys);
+ }
+ };
+
+ function isNode(node) {
+ if (node == null) {
+ return false;
+ }
+ return typeof node === 'object' && typeof node.type === 'string';
+ }
+
+ function isProperty(nodeType, key) {
+ return (nodeType === Syntax.ObjectExpression || nodeType === Syntax.ObjectPattern) && 'properties' === key;
+ }
+
+ Controller.prototype.traverse = function traverse(root, visitor) {
+ var worklist,
+ leavelist,
+ element,
+ node,
+ nodeType,
+ ret,
+ key,
+ current,
+ current2,
+ candidates,
+ candidate,
+ sentinel;
+
+ this.__initialize(root, visitor);
+
+ sentinel = {};
+
+ // reference
+ worklist = this.__worklist;
+ leavelist = this.__leavelist;
+
+ // initialize
+ worklist.push(new Element(root, null, null, null));
+ leavelist.push(new Element(null, null, null, null));
+
+ while (worklist.length) {
+ element = worklist.pop();
+
+ if (element === sentinel) {
+ element = leavelist.pop();
+
+ ret = this.__execute(visitor.leave, element);
+
+ if (this.__state === BREAK || ret === BREAK) {
+ return;
+ }
+ continue;
+ }
+
+ if (element.node) {
+
+ ret = this.__execute(visitor.enter, element);
+
+ if (this.__state === BREAK || ret === BREAK) {
+ return;
+ }
+
+ worklist.push(sentinel);
+ leavelist.push(element);
+
+ if (this.__state === SKIP || ret === SKIP) {
+ continue;
+ }
+
+ node = element.node;
+ nodeType = node.type || element.wrap;
+ candidates = this.__keys[nodeType];
+ if (!candidates) {
+ if (this.__fallback) {
+ candidates = this.__fallback(node);
+ } else {
+ throw new Error('Unknown node type ' + nodeType + '.');
+ }
+ }
+
+ current = candidates.length;
+ while ((current -= 1) >= 0) {
+ key = candidates[current];
+ candidate = node[key];
+ if (!candidate) {
+ continue;
+ }
+
+ if (isArray(candidate)) {
+ current2 = candidate.length;
+ while ((current2 -= 1) >= 0) {
+ if (!candidate[current2]) {
+ continue;
+ }
+ if (isProperty(nodeType, candidates[current])) {
+ element = new Element(candidate[current2], [key, current2], 'Property', null);
+ } else if (isNode(candidate[current2])) {
+ element = new Element(candidate[current2], [key, current2], null, null);
+ } else {
+ continue;
+ }
+ worklist.push(element);
+ }
+ } else if (isNode(candidate)) {
+ worklist.push(new Element(candidate, key, null, null));
+ }
+ }
+ }
+ }
+ };
+
+ Controller.prototype.replace = function replace(root, visitor) {
+ var worklist,
+ leavelist,
+ node,
+ nodeType,
+ target,
+ element,
+ current,
+ current2,
+ candidates,
+ candidate,
+ sentinel,
+ outer,
+ key;
+
+ function removeElem(element) {
+ var i,
+ key,
+ nextElem,
+ parent;
+
+ if (element.ref.remove()) {
+ // When the reference is an element of an array.
+ key = element.ref.key;
+ parent = element.ref.parent;
+
+ // If removed from array, then decrease following items' keys.
+ i = worklist.length;
+ while (i--) {
+ nextElem = worklist[i];
+ if (nextElem.ref && nextElem.ref.parent === parent) {
+ if (nextElem.ref.key < key) {
+ break;
+ }
+ --nextElem.ref.key;
+ }
+ }
+ }
+ }
+
+ this.__initialize(root, visitor);
+
+ sentinel = {};
+
+ // reference
+ worklist = this.__worklist;
+ leavelist = this.__leavelist;
+
+ // initialize
+ outer = {
+ root: root
+ };
+ element = new Element(root, null, null, new Reference(outer, 'root'));
+ worklist.push(element);
+ leavelist.push(element);
+
+ while (worklist.length) {
+ element = worklist.pop();
+
+ if (element === sentinel) {
+ element = leavelist.pop();
+
+ target = this.__execute(visitor.leave, element);
+
+ // node may be replaced with null,
+ // so distinguish between undefined and null in this place
+ if (target !== undefined && target !== BREAK && target !== SKIP && target !== REMOVE) {
+ // replace
+ element.ref.replace(target);
+ }
+
+ if (this.__state === REMOVE || target === REMOVE) {
+ removeElem(element);
+ }
+
+ if (this.__state === BREAK || target === BREAK) {
+ return outer.root;
+ }
+ continue;
+ }
+
+ target = this.__execute(visitor.enter, element);
+
+ // node may be replaced with null,
+ // so distinguish between undefined and null in this place
+ if (target !== undefined && target !== BREAK && target !== SKIP && target !== REMOVE) {
+ // replace
+ element.ref.replace(target);
+ element.node = target;
+ }
+
+ if (this.__state === REMOVE || target === REMOVE) {
+ removeElem(element);
+ element.node = null;
+ }
+
+ if (this.__state === BREAK || target === BREAK) {
+ return outer.root;
+ }
+
+ // node may be null
+ node = element.node;
+ if (!node) {
+ continue;
+ }
+
+ worklist.push(sentinel);
+ leavelist.push(element);
+
+ if (this.__state === SKIP || target === SKIP) {
+ continue;
+ }
+
+ nodeType = node.type || element.wrap;
+ candidates = this.__keys[nodeType];
+ if (!candidates) {
+ if (this.__fallback) {
+ candidates = this.__fallback(node);
+ } else {
+ throw new Error('Unknown node type ' + nodeType + '.');
+ }
+ }
+
+ current = candidates.length;
+ while ((current -= 1) >= 0) {
+ key = candidates[current];
+ candidate = node[key];
+ if (!candidate) {
+ continue;
+ }
+
+ if (isArray(candidate)) {
+ current2 = candidate.length;
+ while ((current2 -= 1) >= 0) {
+ if (!candidate[current2]) {
+ continue;
+ }
+ if (isProperty(nodeType, candidates[current])) {
+ element = new Element(candidate[current2], [key, current2], 'Property', new Reference(candidate, current2));
+ } else if (isNode(candidate[current2])) {
+ element = new Element(candidate[current2], [key, current2], null, new Reference(candidate, current2));
+ } else {
+ continue;
+ }
+ worklist.push(element);
+ }
+ } else if (isNode(candidate)) {
+ worklist.push(new Element(candidate, key, null, new Reference(node, key)));
+ }
+ }
+ }
+
+ return outer.root;
+ };
+
+ function traverse(root, visitor) {
+ var controller = new Controller();
+ return controller.traverse(root, visitor);
+ }
+
+ function replace(root, visitor) {
+ var controller = new Controller();
+ return controller.replace(root, visitor);
+ }
+
+ function extendCommentRange(comment, tokens) {
+ var target;
+
+ target = upperBound(tokens, function search(token) {
+ return token.range[0] > comment.range[0];
+ });
+
+ comment.extendedRange = [comment.range[0], comment.range[1]];
+
+ if (target !== tokens.length) {
+ comment.extendedRange[1] = tokens[target].range[0];
+ }
+
+ target -= 1;
+ if (target >= 0) {
+ comment.extendedRange[0] = tokens[target].range[1];
+ }
+
+ return comment;
+ }
+
+ function attachComments(tree, providedComments, tokens) {
+ // At first, we should calculate extended comment ranges.
+ var comments = [], comment, len, i, cursor;
+
+ if (!tree.range) {
+ throw new Error('attachComments needs range information');
+ }
+
+ // tokens array is empty, we attach comments to tree as 'leadingComments'
+ if (!tokens.length) {
+ if (providedComments.length) {
+ for (i = 0, len = providedComments.length; i < len; i += 1) {
+ comment = deepCopy(providedComments[i]);
+ comment.extendedRange = [0, tree.range[0]];
+ comments.push(comment);
+ }
+ tree.leadingComments = comments;
+ }
+ return tree;
+ }
+
+ for (i = 0, len = providedComments.length; i < len; i += 1) {
+ comments.push(extendCommentRange(deepCopy(providedComments[i]), tokens));
+ }
+
+ // This is based on John Freeman's implementation.
+ cursor = 0;
+ traverse(tree, {
+ enter: function (node) {
+ var comment;
+
+ while (cursor < comments.length) {
+ comment = comments[cursor];
+ if (comment.extendedRange[1] > node.range[0]) {
+ break;
+ }
+
+ if (comment.extendedRange[1] === node.range[0]) {
+ if (!node.leadingComments) {
+ node.leadingComments = [];
+ }
+ node.leadingComments.push(comment);
+ comments.splice(cursor, 1);
+ } else {
+ cursor += 1;
+ }
+ }
+
+ // already out of owned node
+ if (cursor === comments.length) {
+ return VisitorOption.Break;
+ }
+
+ if (comments[cursor].extendedRange[0] > node.range[1]) {
+ return VisitorOption.Skip;
+ }
+ }
+ });
+
+ cursor = 0;
+ traverse(tree, {
+ leave: function (node) {
+ var comment;
+
+ while (cursor < comments.length) {
+ comment = comments[cursor];
+ if (node.range[1] < comment.extendedRange[0]) {
+ break;
+ }
+
+ if (node.range[1] === comment.extendedRange[0]) {
+ if (!node.trailingComments) {
+ node.trailingComments = [];
+ }
+ node.trailingComments.push(comment);
+ comments.splice(cursor, 1);
+ } else {
+ cursor += 1;
+ }
+ }
+
+ // already out of owned node
+ if (cursor === comments.length) {
+ return VisitorOption.Break;
+ }
+
+ if (comments[cursor].extendedRange[0] > node.range[1]) {
+ return VisitorOption.Skip;
+ }
+ }
+ });
+
+ return tree;
+ }
+
+ exports.version = require('./package.json').version;
+ exports.Syntax = Syntax;
+ exports.traverse = traverse;
+ exports.replace = replace;
+ exports.attachComments = attachComments;
+ exports.VisitorKeys = VisitorKeys;
+ exports.VisitorOption = VisitorOption;
+ exports.Controller = Controller;
+ exports.cloneEnvironment = function () { return clone({}); };
+
+ return exports;
+}(exports));
+/* vim: set sw=4 ts=4 et tw=80 : */
diff --git a/gulpfile.js b/gulpfile.js
new file mode 100644
index 0000000..8772bbc
--- /dev/null
+++ b/gulpfile.js
@@ -0,0 +1,70 @@
+/*
+ Copyright (C) 2014 Yusuke Suzuki <utatane.tea at gmail.com>
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS'
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+'use strict';
+
+var gulp = require('gulp'),
+ git = require('gulp-git'),
+ bump = require('gulp-bump'),
+ filter = require('gulp-filter'),
+ tagVersion = require('gulp-tag-version');
+
+var TEST = [ 'test/*.js' ];
+var POWERED = [ 'powered-test/*.js' ];
+var SOURCE = [ 'src/**/*.js' ];
+
+/**
+ * Bumping version number and tagging the repository with it.
+ * Please read http://semver.org/
+ *
+ * You can use the commands
+ *
+ * gulp patch # makes v0.1.0 -> v0.1.1
+ * gulp feature # makes v0.1.1 -> v0.2.0
+ * gulp release # makes v0.2.1 -> v1.0.0
+ *
+ * To bump the version numbers accordingly after you did a patch,
+ * introduced a feature or made a backwards-incompatible release.
+ */
+
+function inc(importance) {
+ // get all the files to bump version in
+ return gulp.src(['./package.json'])
+ // bump the version number in those files
+ .pipe(bump({type: importance}))
+ // save it back to filesystem
+ .pipe(gulp.dest('./'))
+ // commit the changed version number
+ .pipe(git.commit('Bumps package version'))
+ // read only one file to get the version number
+ .pipe(filter('package.json'))
+ // **tag it in the repository**
+ .pipe(tagVersion({
+ prefix: ''
+ }));
+}
+
+gulp.task('patch', [ ], function () { return inc('patch'); })
+gulp.task('minor', [ ], function () { return inc('minor'); })
+gulp.task('major', [ ], function () { return inc('major'); })
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..f8567e3
--- /dev/null
+++ b/package.json
@@ -0,0 +1,40 @@
+{
+ "name": "estraverse",
+ "description": "ECMAScript JS AST traversal functions",
+ "homepage": "https://github.com/estools/estraverse",
+ "main": "estraverse.js",
+ "version": "4.2.0",
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "maintainers": [
+ {
+ "name": "Yusuke Suzuki",
+ "email": "utatane.tea at gmail.com",
+ "web": "http://github.com/Constellation"
+ }
+ ],
+ "repository": {
+ "type": "git",
+ "url": "http://github.com/estools/estraverse.git"
+ },
+ "devDependencies": {
+ "babel-preset-es2015": "^6.3.13",
+ "babel-register": "^6.3.13",
+ "chai": "^2.1.1",
+ "espree": "^1.11.0",
+ "gulp": "^3.8.10",
+ "gulp-bump": "^0.2.2",
+ "gulp-filter": "^2.0.0",
+ "gulp-git": "^1.0.1",
+ "gulp-tag-version": "^1.2.1",
+ "jshint": "^2.5.6",
+ "mocha": "^2.1.0"
+ },
+ "license": "BSD-2-Clause",
+ "scripts": {
+ "test": "npm run-script lint && npm run-script unit-test",
+ "lint": "jshint estraverse.js",
+ "unit-test": "mocha --compilers js:babel-register"
+ }
+}
diff --git a/test/checkDump.js b/test/checkDump.js
new file mode 100644
index 0000000..f1732db
--- /dev/null
+++ b/test/checkDump.js
@@ -0,0 +1,35 @@
+// Copyright (C) 2013 Yusuke Suzuki <utatane.tea at gmail.com>
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import { expect } from 'chai';
+
+export default function checkDump(dump, expected) {
+ expect(normalize(expected)).to.be.equal(normalize(dump));
+}
+
+function normalize(dump) {
+ return dump
+ .trim()
+ .split('\n')
+ .map(line => line.trim())
+ .join('\n');
+}
diff --git a/test/controller.js b/test/controller.js
new file mode 100644
index 0000000..aadc51f
--- /dev/null
+++ b/test/controller.js
@@ -0,0 +1,67 @@
+// Copyright (C) 2013 Yusuke Suzuki <utatane.tea at gmail.com>
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+import { Controller } from '../';
+import Dumper from './dumper';
+import checkDump from './checkDump';
+
+describe('controller', function() {
+ it('traverse', function() {
+ const controller = new Controller();
+ const dumper = new Dumper();
+ const tree = {
+ type: 'ObjectExpression',
+ properties: [{
+ key: {
+ type: 'Identifier',
+ name: 'a'
+ },
+ value: {
+ type: 'Identifier',
+ name: 'a'
+ }
+ }]
+ };
+
+ controller.traverse(tree, {
+ enter(node) {
+ dumper.log(`enter - ${node.type}`);
+ },
+
+ leave(node) {
+ dumper.log(`leave - ${node.type}`);
+ }
+ });
+
+ checkDump(dumper.result(), `
+ enter - ObjectExpression
+ enter - undefined
+ enter - Identifier
+ leave - Identifier
+ enter - Identifier
+ leave - Identifier
+ leave - undefined
+ leave - ObjectExpression
+ `);
+ });
+});
diff --git a/test/dumper.js b/test/dumper.js
new file mode 100644
index 0000000..2887308
--- /dev/null
+++ b/test/dumper.js
@@ -0,0 +1,59 @@
+// Copyright (C) 2013 Yusuke Suzuki <utatane.tea at gmail.com>
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+import { traverse, VisitorOption } from '..';
+
+export default class Dumper {
+ constructor() {
+ this.logs = [];
+ }
+
+ log(str) {
+ this.logs.push(str);
+ }
+
+ result() {
+ return this.logs.join('\n');
+ }
+
+ static dump(tree, keys, fallback) {
+ const dumper = new Dumper();
+
+ traverse(tree, {
+ enter(node) {
+ dumper.log(`enter - ${node.type}`);
+ return VisitorOption[node.$enter];
+ },
+
+ leave(node) {
+ dumper.log(`leave - ${node.type}`);
+ return VisitorOption[node.$leave];
+ },
+
+ keys,
+ fallback
+ });
+
+ return dumper.result();
+ }
+};
diff --git a/test/es6.js b/test/es6.js
new file mode 100644
index 0000000..5af8e69
--- /dev/null
+++ b/test/es6.js
@@ -0,0 +1,391 @@
+// -*- coding: utf-8 -*-
+// Copyright (C) 2015 Yusuke Suzuki <utatane.tea at gmail.com>
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+import Dumper from './dumper';
+import checkDump from './checkDump';
+import { parse as esprima } from './third_party/esprima';
+import { parse as espree } from 'espree';
+
+function classDeclaration() {
+ const program = esprima(`
+ class Hello {
+ };
+ `);
+ return program.body[0];
+}
+
+describe('rest expression', function() {
+ it('argument', function() {
+ const tree = {
+ type: 'RestElement',
+ argument: {
+ type: 'Identifier',
+ name: 'hello'
+ }
+ };
+
+ checkDump(Dumper.dump(tree), `
+ enter - RestElement
+ enter - Identifier
+ leave - Identifier
+ leave - RestElement
+ `);
+ });
+});
+
+describe('class', function() {
+ it('declaration#1', function() {
+ const tree = esprima(`
+ class Hello extends World {
+ method() {
+ }
+ };
+ `);
+
+ checkDump(Dumper.dump(tree), `
+ enter - Program
+ enter - ClassDeclaration
+ enter - Identifier
+ leave - Identifier
+ enter - Identifier
+ leave - Identifier
+ enter - ClassBody
+ enter - MethodDefinition
+ enter - Identifier
+ leave - Identifier
+ enter - FunctionExpression
+ enter - BlockStatement
+ leave - BlockStatement
+ leave - FunctionExpression
+ leave - MethodDefinition
+ leave - ClassBody
+ leave - ClassDeclaration
+ enter - EmptyStatement
+ leave - EmptyStatement
+ leave - Program
+ `);
+ });
+
+ it('declaration#2', function() {
+ const tree = esprima(`
+ class Hello extends ok() {
+ method() {
+ }
+ };
+ `);
+
+ checkDump(Dumper.dump(tree), `
+ enter - Program
+ enter - ClassDeclaration
+ enter - Identifier
+ leave - Identifier
+ enter - CallExpression
+ enter - Identifier
+ leave - Identifier
+ leave - CallExpression
+ enter - ClassBody
+ enter - MethodDefinition
+ enter - Identifier
+ leave - Identifier
+ enter - FunctionExpression
+ enter - BlockStatement
+ leave - BlockStatement
+ leave - FunctionExpression
+ leave - MethodDefinition
+ leave - ClassBody
+ leave - ClassDeclaration
+ enter - EmptyStatement
+ leave - EmptyStatement
+ leave - Program
+ `);
+ });
+});
+
+describe('export', function() {
+ it('named declaration #1', function() {
+ const tree = {
+ type: 'ExportNamedDeclaration',
+ declaration: {
+ type: 'VariableDeclaration',
+ declarations: [{
+ type: 'VariableDeclarator',
+ id: {
+ type: 'Identifier',
+ name: 'hello'
+ },
+ init: {
+ type: 'Literal',
+ value: 6
+ }
+ }]
+ }
+ };
+
+ checkDump(Dumper.dump(tree), `
+ enter - ExportNamedDeclaration
+ enter - VariableDeclaration
+ enter - VariableDeclarator
+ enter - Identifier
+ leave - Identifier
+ enter - Literal
+ leave - Literal
+ leave - VariableDeclarator
+ leave - VariableDeclaration
+ leave - ExportNamedDeclaration
+ `);
+ });
+
+ it('named declaration #2', function() {
+ const tree = {
+ type: 'ExportNamedDeclaration',
+ declaration: null,
+ specifiers: [{
+ type: 'ExportSpecifier',
+ exported: {
+ type: 'Identifier',
+ name: 'foo'
+ },
+ local: {
+ type: 'Identifier',
+ name: 'bar'
+ }
+
+ }],
+ source: {
+ type: 'Literal',
+ value: 'hello'
+ }
+ };
+
+ checkDump(Dumper.dump(tree), `
+ enter - ExportNamedDeclaration
+ enter - ExportSpecifier
+ enter - Identifier
+ leave - Identifier
+ enter - Identifier
+ leave - Identifier
+ leave - ExportSpecifier
+ enter - Literal
+ leave - Literal
+ leave - ExportNamedDeclaration
+ `);
+ });
+
+ it('all declaration #1', function() {
+ const tree = {
+ type: 'ExportAllDeclaration',
+ source: {
+ type: 'Literal',
+ value: 'hello'
+ }
+ };
+
+ checkDump(Dumper.dump(tree), `
+ enter - ExportAllDeclaration
+ enter - Literal
+ leave - Literal
+ leave - ExportAllDeclaration
+ `);
+ });
+
+ it('default declaration #1', function() {
+ const tree = {
+ type: 'ExportDefaultDeclaration',
+ declaration: classDeclaration()
+ };
+
+ checkDump(Dumper.dump(tree), `
+ enter - ExportDefaultDeclaration
+ enter - ClassDeclaration
+ enter - Identifier
+ leave - Identifier
+ enter - ClassBody
+ leave - ClassBody
+ leave - ClassDeclaration
+ leave - ExportDefaultDeclaration
+ `);
+ });
+
+ it('default declaration #1', function() {
+ const tree = {
+ type: 'ExportDefaultDeclaration',
+ declaration: classDeclaration()
+ };
+
+ checkDump(Dumper.dump(tree), `
+ enter - ExportDefaultDeclaration
+ enter - ClassDeclaration
+ enter - Identifier
+ leave - Identifier
+ enter - ClassBody
+ leave - ClassBody
+ leave - ClassDeclaration
+ leave - ExportDefaultDeclaration
+ `);
+ });
+});
+
+
+describe('import', function() {
+ it('default specifier #1', function() {
+ const tree = espree(`import Cocoa from 'rabbit-house'`, {
+ ecmaFeatures: {
+ modules: true
+ }
+ });
+
+ checkDump(Dumper.dump(tree), `
+ enter - Program
+ enter - ImportDeclaration
+ enter - ImportDefaultSpecifier
+ enter - Identifier
+ leave - Identifier
+ leave - ImportDefaultSpecifier
+ enter - Literal
+ leave - Literal
+ leave - ImportDeclaration
+ leave - Program
+ `);
+ });
+
+ it('named specifier #1', function() {
+ const tree = espree(`import {Cocoa, Cappuccino as Chino} from 'rabbit-house'`, {
+ ecmaFeatures: {
+ modules: true
+ }
+ });
+
+ checkDump(Dumper.dump(tree), `
+ enter - Program
+ enter - ImportDeclaration
+ enter - ImportSpecifier
+ enter - Identifier
+ leave - Identifier
+ enter - Identifier
+ leave - Identifier
+ leave - ImportSpecifier
+ enter - ImportSpecifier
+ enter - Identifier
+ leave - Identifier
+ enter - Identifier
+ leave - Identifier
+ leave - ImportSpecifier
+ enter - Literal
+ leave - Literal
+ leave - ImportDeclaration
+ leave - Program
+ `);
+ });
+
+ it('namespace specifier #1', function() {
+ const tree = espree(`import * as RabbitHouse from 'rabbit-house'`, {
+ ecmaFeatures: {
+ modules: true
+ }
+ });
+
+ checkDump(Dumper.dump(tree), `
+ enter - Program
+ enter - ImportDeclaration
+ enter - ImportNamespaceSpecifier
+ enter - Identifier
+ leave - Identifier
+ leave - ImportNamespaceSpecifier
+ enter - Literal
+ leave - Literal
+ leave - ImportDeclaration
+ leave - Program
+ `);
+ });
+});
+
+describe('pattern', function() {
+ it('assignment pattern#1', function() {
+ const tree = {
+ type: 'AssignmentPattern',
+ left: {
+ type: 'Identifier',
+ name: 'hello'
+ },
+ right: {
+ type: 'Literal',
+ value: 'world'
+ }
+ };
+
+ checkDump(Dumper.dump(tree), `
+ enter - AssignmentPattern
+ enter - Identifier
+ leave - Identifier
+ enter - Literal
+ leave - Literal
+ leave - AssignmentPattern
+ `);
+ });
+});
+
+describe('super', function() {
+ it('super expression#1', function() {
+ const tree = {type: 'Super'};
+
+ checkDump(Dumper.dump(tree), `
+ enter - Super
+ leave - Super
+ `);
+ });
+});
+
+describe('meta property', function() {
+ it('MetaProperty in constructor #1', function() {
+ const tree = {
+ type: 'UnaryExpression',
+ operator: 'typeof',
+ prefix: true,
+ argument: {
+ type: 'MetaProperty',
+ meta: {
+ type: 'Identifier',
+ name: 'new'
+ },
+ property: {
+ type: 'Identifier',
+ name: 'target'
+ }
+ }
+ };
+
+ checkDump(Dumper.dump(tree), `
+ enter - UnaryExpression
+ enter - MetaProperty
+ enter - Identifier
+ leave - Identifier
+ enter - Identifier
+ leave - Identifier
+ leave - MetaProperty
+ leave - UnaryExpression
+ `);
+ });
+});
+
+// vim: set sw=4 ts=4 et tw=80 :
diff --git a/test/replace.js b/test/replace.js
new file mode 100644
index 0000000..90a954f
--- /dev/null
+++ b/test/replace.js
@@ -0,0 +1,483 @@
+// Copyright (C) 2014 Ingvar Stepanyan <me at rreverser.com>
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+import { replace, VisitorOption } from '..';
+import { expect } from 'chai';
+
+describe('replace', function() {
+ it('can simplify expressions', function() {
+ let tree = {
+ type: 'ObjectExpression',
+ properties: [{
+ type: 'Property',
+ key: {
+ type: 'Identifier',
+ name: 'a'
+ },
+ value: {
+ type: 'BinaryExpression',
+ operator: '*',
+ left: {
+ type: 'Literal',
+ value: 2
+ },
+ right: {
+ type: 'Literal',
+ value: 3
+ }
+ }
+ }]
+ };
+
+ tree = replace(tree, {
+ enter(node) {
+ if (node.type === 'BinaryExpression' && node.left.type === 'Literal' && node.right.type === 'Literal') {
+ return {
+ type: 'Literal',
+ value: eval(JSON.stringify(node.left.value) + node.operator + JSON.stringify(node.right.value))
+ };
+ }
+ }
+ });
+
+ expect(tree).to.be.eql({
+ type: 'ObjectExpression',
+ properties: [{
+ type: 'Property',
+ key: {
+ type: 'Identifier',
+ name: 'a'
+ },
+ value: {
+ type: 'Literal',
+ value: 6
+ }
+ }]
+ });
+ });
+
+ it('can remove nodes', function() {
+ let tree = {
+ type: 'ObjectExpression',
+ properties: [{
+ type: 'Property',
+ key: {
+ type: 'Identifier',
+ name: 'a'
+ },
+ value: {
+ type: 'ArrayExpression',
+ elements: [
+ {type: 'Literal', value: 1},
+ {type: 'Literal', value: 2},
+ {type: 'Literal', value: 3},
+ {type: 'Literal', value: 4}
+ ]
+ }
+ }]
+ };
+
+ tree = replace(tree, {
+ enter(node) {
+ if (node.type === 'Identifier' && node.name === 'a') {
+ this.remove();
+ }
+ if (node.type === 'Literal' && node.value === 2) {
+ return VisitorOption.Remove;
+ }
+ }
+ });
+
+ expect(tree).to.be.eql({
+ type: 'ObjectExpression',
+ properties: [{
+ type: 'Property',
+ key: null,
+ value: {
+ type: 'ArrayExpression',
+ elements: [
+ {type: 'Literal', value: 1},
+ {type: 'Literal', value: 3},
+ {type: 'Literal', value: 4}
+ ]
+ }
+ }]
+ });
+ });
+
+ it('can remove all nodes in an array in enter phase', function() {
+ let tree = {
+ type: 'ObjectExpression',
+ properties: [{
+ type: 'Property',
+ key: {
+ type: 'Identifier',
+ name: 'a'
+ },
+ value: {
+ type: 'ArrayExpression',
+ elements: [
+ {type: 'Literal', value: 1},
+ {type: 'Literal', value: 2},
+ {type: 'Literal', value: 3},
+ {type: 'Literal', value: 4}
+ ]
+ }
+ }]
+ };
+
+ tree = replace(tree, {
+ enter(node) {
+ if (node.type === 'Literal') {
+ return this.remove();
+ }
+ }
+ });
+
+ expect(tree).to.be.eql({
+ type: 'ObjectExpression',
+ properties: [{
+ type: 'Property',
+ key: {
+ type: 'Identifier',
+ name: 'a'
+ },
+ value: {
+ type: 'ArrayExpression',
+ elements: []
+ }
+ }]
+ });
+
+ tree = {
+ type: 'FunctionExpression',
+ id: {
+ type: 'Identifier',
+ name: 'foo'
+ },
+ params: [],
+ defaults: [],
+ body: {
+ type: 'BlockStatement',
+ body: [{
+ type: 'ExpressionStatement',
+ expression: {
+ type: 'CallExpression',
+ callee: {
+ type: 'Identifier',
+ name: 'debug'
+ },
+ arguments: [{
+ type: 'Literal',
+ value: 'foo',
+ raw: '"foo"'
+ }]
+ }
+ },
+ {
+ type: 'ExpressionStatement',
+ expression: {
+ type: 'CallExpression',
+ callee: {
+ type: 'Identifier',
+ name: 'debug'
+ },
+ arguments: [{
+ type: 'Literal',
+ value: 'bar',
+ raw: '"bar"'
+ }]
+ }
+ }]
+ },
+ rest: null,
+ generator: false,
+ expression: false
+ };
+
+ tree = replace(tree, {
+ enter(node) {
+ if (node.type === 'ExpressionStatement' && node.expression.type === 'CallExpression' && node.expression.callee.name === 'debug') {
+ return this.remove();
+ }
+ }
+ });
+
+ expect(tree).to.be.eql({
+ type: 'FunctionExpression',
+ id: {
+ type: 'Identifier',
+ name: 'foo'
+ },
+ params: [],
+ defaults: [],
+ body: {
+ type: 'BlockStatement',
+ body: []
+ },
+ rest: null,
+ generator: false,
+ expression: false
+ });
+ });
+
+ it('can remove all nodes in an array in leave phase', function() {
+ let tree = {
+ type: 'ObjectExpression',
+ properties: [{
+ type: 'Property',
+ key: {
+ type: 'Identifier',
+ name: 'a'
+ },
+ value: {
+ type: 'ArrayExpression',
+ elements: [
+ {type: 'Literal', value: 1},
+ {type: 'Literal', value: 2},
+ {type: 'Literal', value: 3},
+ {type: 'Literal', value: 4}
+ ]
+ }
+ }]
+ };
+
+ tree = replace(tree, {
+ leave(node) {
+ if (node.type === 'Literal') {
+ return this.remove();
+ }
+ }
+ });
+
+ expect(tree).to.be.eql({
+ type: 'ObjectExpression',
+ properties: [{
+ type: 'Property',
+ key: {
+ type: 'Identifier',
+ name: 'a'
+ },
+ value: {
+ type: 'ArrayExpression',
+ elements: []
+ }
+ }]
+ });
+
+ tree = {
+ type: 'FunctionExpression',
+ id: {
+ type: 'Identifier',
+ name: 'foo'
+ },
+ params: [],
+ defaults: [],
+ body: {
+ type: 'BlockStatement',
+ body: [{
+ type: 'ExpressionStatement',
+ expression: {
+ type: 'CallExpression',
+ callee: {
+ type: 'Identifier',
+ name: 'debug'
+ },
+ arguments: [{
+ type: 'Literal',
+ value: 'foo',
+ raw: '"foo"'
+ }]
+ }
+ },
+ {
+ type: 'ExpressionStatement',
+ expression: {
+ type: 'CallExpression',
+ callee: {
+ type: 'Identifier',
+ name: 'debug'
+ },
+ arguments: [{
+ type: 'Literal',
+ value: 'bar',
+ raw: '"bar"'
+ }]
+ }
+ }]
+ },
+ rest: null,
+ generator: false,
+ expression: false
+ };
+
+ tree = replace(tree, {
+ leave(node) {
+ if (node.type === 'ExpressionStatement' && node.expression.type === 'CallExpression' && node.expression.callee.name === 'debug') {
+ this.remove();
+ }
+ }
+ });
+
+ expect(tree).to.be.eql({
+ type: 'FunctionExpression',
+ id: {
+ type: 'Identifier',
+ name: 'foo'
+ },
+ params: [],
+ defaults: [],
+ body: {
+ type: 'BlockStatement',
+ body: []
+ },
+ rest: null,
+ generator: false,
+ expression: false
+ });
+ });
+
+ it('respects skip', function() {
+ let tree = {
+ type: 'ObjectExpression',
+ properties: [{
+ type: 'Property',
+ key: {
+ type: 'Identifier',
+ name: 'a'
+ },
+ value: {
+ type: 'BinaryExpression',
+ operator: '*',
+ left: {
+ type: 'Literal',
+ value: 2
+ },
+ right: {
+ type: 'Literal',
+ value: 3
+ }
+ }
+ }]
+ };
+
+ const old = JSON.parse(JSON.stringify(tree));
+
+ tree = replace(tree, {
+ enter(node) {
+ switch (node.type) {
+ case 'ObjectExpression':
+ return VisitorOption.Skip;
+ case 'BinaryExpression':
+ return VisitorOption.Remove;
+ }
+ }
+ });
+
+ expect(tree).to.be.eql(old);
+ });
+
+ it('can remove unknown nodes', function() {
+ let tree = {
+ type: 'XXXExpression',
+ properties: [{
+ type: 'Property',
+ key: {
+ type: 'Identifier',
+ name: 'a'
+ },
+ value: {
+ type: 'ArrayExpression',
+ elements: [
+ {type: 'Literal', value: 1},
+ {type: 'Literal', value: 2},
+ {type: 'Literal', value: 3},
+ {type: 'Literal', value: 4}
+ ]
+ }
+ }]
+ };
+
+ tree = replace(tree, {
+ enter(node) {
+ if (node.type === 'Identifier' && node.name === 'a') {
+ this.remove();
+ }
+ if (node.type === 'Literal' && node.value === 2) {
+ return VisitorOption.Remove;
+ }
+ },
+ fallback: 'iteration'
+ });
+
+ expect(tree).to.be.eql({
+ type: 'XXXExpression',
+ properties: [{
+ type: 'Property',
+ key: null,
+ value: {
+ type: 'ArrayExpression',
+ elements: [
+ {type: 'Literal', value: 1},
+ {type: 'Literal', value: 3},
+ {type: 'Literal', value: 4}
+ ]
+ }
+ }]
+ });
+ });
+
+ it('throw unkown node type error when unknown nodes', function() {
+ const tree = {
+ type: 'XXXExpression',
+ properties: [{
+ type: 'Property',
+ key: {
+ type: 'Identifier',
+ name: 'a'
+ },
+ value: {
+ type: 'ArrayExpression',
+ elements: [
+ {type: 'Literal', value: 1},
+ {type: 'Literal', value: 2},
+ {type: 'Literal', value: 3},
+ {type: 'Literal', value: 4}
+ ]
+ }
+ }]
+ };
+
+ expect(() => replace(tree, {
+ enter(node) {
+ if (node.type === 'Identifier' && node.name === 'a') {
+ this.remove();
+ }
+ if (node.type === 'Literal' && node.value === 2) {
+ return VisitorOption.Remove;
+ }
+ }
+ })
+ ).to.throw('Unknown node type XXXExpression.');
+ });
+});
diff --git a/test/third_party/esprima.js b/test/third_party/esprima.js
new file mode 100644
index 0000000..a0ac203
--- /dev/null
+++ b/test/third_party/esprima.js
@@ -0,0 +1,5353 @@
+/*
+ Copyright (C) 2013 Ariya Hidayat <ariya.hidayat at gmail.com>
+ Copyright (C) 2013 Thaddee Tyl <thaddee.tyl at gmail.com>
+ Copyright (C) 2012 Ariya Hidayat <ariya.hidayat at gmail.com>
+ Copyright (C) 2012 Mathias Bynens <mathias at qiwi.be>
+ Copyright (C) 2012 Joost-Wim Boekesteijn <joost-wim at boekesteijn.nl>
+ Copyright (C) 2012 Kris Kowal <kris.kowal at cixar.com>
+ Copyright (C) 2012 Yusuke Suzuki <utatane.tea at gmail.com>
+ Copyright (C) 2012 Arpad Borsos <arpad.borsos at googlemail.com>
+ Copyright (C) 2011 Ariya Hidayat <ariya.hidayat at gmail.com>
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+(function (root, factory) {
+ 'use strict';
+
+ // Universal Module Definition (UMD) to support AMD, CommonJS/Node.js,
+ // Rhino, and plain browser loading.
+
+ /* istanbul ignore next */
+ if (typeof define === 'function' && define.amd) {
+ define(['exports'], factory);
+ } else if (typeof exports !== 'undefined') {
+ factory(exports);
+ } else {
+ factory((root.esprima = {}));
+ }
+}(this, function (exports) {
+ 'use strict';
+
+ var Token,
+ TokenName,
+ FnExprTokens,
+ Syntax,
+ PropertyKind,
+ Messages,
+ Regex,
+ SyntaxTreeDelegate,
+ ClassPropertyType,
+ source,
+ strict,
+ index,
+ lineNumber,
+ lineStart,
+ length,
+ delegate,
+ lookahead,
+ state,
+ extra;
+
+ Token = {
+ BooleanLiteral: 1,
+ EOF: 2,
+ Identifier: 3,
+ Keyword: 4,
+ NullLiteral: 5,
+ NumericLiteral: 6,
+ Punctuator: 7,
+ StringLiteral: 8,
+ RegularExpression: 9,
+ Template: 10
+ };
+
+ TokenName = {};
+ TokenName[Token.BooleanLiteral] = 'Boolean';
+ TokenName[Token.EOF] = '<end>';
+ TokenName[Token.Identifier] = 'Identifier';
+ TokenName[Token.Keyword] = 'Keyword';
+ TokenName[Token.NullLiteral] = 'Null';
+ TokenName[Token.NumericLiteral] = 'Numeric';
+ TokenName[Token.Punctuator] = 'Punctuator';
+ TokenName[Token.StringLiteral] = 'String';
+ TokenName[Token.RegularExpression] = 'RegularExpression';
+
+ // A function following one of those tokens is an expression.
+ FnExprTokens = ['(', '{', '[', 'in', 'typeof', 'instanceof', 'new',
+ 'return', 'case', 'delete', 'throw', 'void',
+ // assignment operators
+ '=', '+=', '-=', '*=', '/=', '%=', '<<=', '>>=', '>>>=',
+ '&=', '|=', '^=', ',',
+ // binary/unary operators
+ '+', '-', '*', '/', '%', '++', '--', '<<', '>>', '>>>', '&',
+ '|', '^', '!', '~', '&&', '||', '?', ':', '===', '==', '>=',
+ '<=', '<', '>', '!=', '!=='];
+
+ Syntax = {
+ ArrayExpression: 'ArrayExpression',
+ ArrayPattern: 'ArrayPattern',
+ ArrowFunctionExpression: 'ArrowFunctionExpression',
+ AssignmentExpression: 'AssignmentExpression',
+ BinaryExpression: 'BinaryExpression',
+ BlockStatement: 'BlockStatement',
+ BreakStatement: 'BreakStatement',
+ CallExpression: 'CallExpression',
+ CatchClause: 'CatchClause',
+ ClassBody: 'ClassBody',
+ ClassDeclaration: 'ClassDeclaration',
+ ClassExpression: 'ClassExpression',
+ ComprehensionBlock: 'ComprehensionBlock',
+ ComprehensionExpression: 'ComprehensionExpression',
+ ConditionalExpression: 'ConditionalExpression',
+ ContinueStatement: 'ContinueStatement',
+ DebuggerStatement: 'DebuggerStatement',
+ DoWhileStatement: 'DoWhileStatement',
+ EmptyStatement: 'EmptyStatement',
+ ExportDeclaration: 'ExportDeclaration',
+ ExportBatchSpecifier: 'ExportBatchSpecifier',
+ ExportSpecifier: 'ExportSpecifier',
+ ExpressionStatement: 'ExpressionStatement',
+ ForInStatement: 'ForInStatement',
+ ForOfStatement: 'ForOfStatement',
+ ForStatement: 'ForStatement',
+ FunctionDeclaration: 'FunctionDeclaration',
+ FunctionExpression: 'FunctionExpression',
+ Identifier: 'Identifier',
+ IfStatement: 'IfStatement',
+ ImportDeclaration: 'ImportDeclaration',
+ ImportDefaultSpecifier: 'ImportDefaultSpecifier',
+ ImportNamespaceSpecifier: 'ImportNamespaceSpecifier',
+ ImportSpecifier: 'ImportSpecifier',
+ LabeledStatement: 'LabeledStatement',
+ Literal: 'Literal',
+ LogicalExpression: 'LogicalExpression',
+ MemberExpression: 'MemberExpression',
+ MethodDefinition: 'MethodDefinition',
+ NewExpression: 'NewExpression',
+ ObjectExpression: 'ObjectExpression',
+ ObjectPattern: 'ObjectPattern',
+ Program: 'Program',
+ Property: 'Property',
+ ReturnStatement: 'ReturnStatement',
+ SequenceExpression: 'SequenceExpression',
+ SpreadElement: 'SpreadElement',
+ SwitchCase: 'SwitchCase',
+ SwitchStatement: 'SwitchStatement',
+ TaggedTemplateExpression: 'TaggedTemplateExpression',
+ TemplateElement: 'TemplateElement',
+ TemplateLiteral: 'TemplateLiteral',
+ ThisExpression: 'ThisExpression',
+ ThrowStatement: 'ThrowStatement',
+ TryStatement: 'TryStatement',
+ UnaryExpression: 'UnaryExpression',
+ UpdateExpression: 'UpdateExpression',
+ VariableDeclaration: 'VariableDeclaration',
+ VariableDeclarator: 'VariableDeclarator',
+ WhileStatement: 'WhileStatement',
+ WithStatement: 'WithStatement',
+ YieldExpression: 'YieldExpression'
+ };
+
+ PropertyKind = {
+ Data: 1,
+ Get: 2,
+ Set: 4
+ };
+
+ ClassPropertyType = {
+ 'static': 'static',
+ prototype: 'prototype'
+ };
+
+ // Error messages should be identical to V8.
+ Messages = {
+ UnexpectedToken: 'Unexpected token %0',
+ UnexpectedNumber: 'Unexpected number',
+ UnexpectedString: 'Unexpected string',
+ UnexpectedIdentifier: 'Unexpected identifier',
+ UnexpectedReserved: 'Unexpected reserved word',
+ UnexpectedTemplate: 'Unexpected quasi %0',
+ UnexpectedEOS: 'Unexpected end of input',
+ NewlineAfterThrow: 'Illegal newline after throw',
+ InvalidRegExp: 'Invalid regular expression',
+ UnterminatedRegExp: 'Invalid regular expression: missing /',
+ InvalidLHSInAssignment: 'Invalid left-hand side in assignment',
+ InvalidLHSInFormalsList: 'Invalid left-hand side in formals list',
+ InvalidLHSInForIn: 'Invalid left-hand side in for-in',
+ MultipleDefaultsInSwitch: 'More than one default clause in switch statement',
+ NoCatchOrFinally: 'Missing catch or finally after try',
+ UnknownLabel: 'Undefined label \'%0\'',
+ Redeclaration: '%0 \'%1\' has already been declared',
+ IllegalContinue: 'Illegal continue statement',
+ IllegalBreak: 'Illegal break statement',
+ IllegalDuplicateClassProperty: 'Illegal duplicate property in class definition',
+ IllegalClassConstructorProperty: 'Illegal constructor property in class definition',
+ IllegalReturn: 'Illegal return statement',
+ IllegalYield: 'Illegal yield expression',
+ IllegalSpread: 'Illegal spread element',
+ StrictModeWith: 'Strict mode code may not include a with statement',
+ StrictCatchVariable: 'Catch variable may not be eval or arguments in strict mode',
+ StrictVarName: 'Variable name may not be eval or arguments in strict mode',
+ StrictParamName: 'Parameter name eval or arguments is not allowed in strict mode',
+ StrictParamDupe: 'Strict mode function may not have duplicate parameter names',
+ ParameterAfterRestParameter: 'Rest parameter must be final parameter of an argument list',
+ DefaultRestParameter: 'Rest parameter can not have a default value',
+ ElementAfterSpreadElement: 'Spread must be the final element of an element list',
+ ObjectPatternAsRestParameter: 'Invalid rest parameter',
+ ObjectPatternAsSpread: 'Invalid spread argument',
+ StrictFunctionName: 'Function name may not be eval or arguments in strict mode',
+ StrictOctalLiteral: 'Octal literals are not allowed in strict mode.',
+ StrictDelete: 'Delete of an unqualified identifier in strict mode.',
+ StrictDuplicateProperty: 'Duplicate data property in object literal not allowed in strict mode',
+ AccessorDataProperty: 'Object literal may not have data and accessor property with the same name',
+ AccessorGetSet: 'Object literal may not have multiple get/set accessors with the same name',
+ StrictLHSAssignment: 'Assignment to eval or arguments is not allowed in strict mode',
+ StrictLHSPostfix: 'Postfix increment/decrement may not have eval or arguments operand in strict mode',
+ StrictLHSPrefix: 'Prefix increment/decrement may not have eval or arguments operand in strict mode',
+ StrictReservedWord: 'Use of future reserved word in strict mode',
+ MissingFromClause: 'Missing from clause',
+ NoAsAfterImportNamespace: 'Missing as after import *',
+ InvalidModuleSpecifier: 'Invalid module specifier',
+ IllegalImportDeclaration: 'Illegal import declaration',
+ IllegalExportDeclaration: 'Illegal export declaration',
+ NoUninitializedConst: 'Const must be initialized',
+ ComprehensionRequiresBlock: 'Comprehension must have at least one block',
+ ComprehensionError: 'Comprehension Error',
+ EachNotAllowed: 'Each is not supported'
+ };
+
+ // See also tools/generate-unicode-regex.py.
+ Regex = {
+ NonAsciiIdentifierStart: new RegExp('[\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0370-\u0374\u0376\u0377\u037a-\u037d\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u048a-\u0527\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- [...]
+ NonAsciiIdentifierPart: new RegExp('[\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0300-\u0374\u0376\u0377\u037a-\u037d\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u0483-\u0487\u048a-\u0527\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\u08 [...]
+ };
+
+ // Ensure the condition is true, otherwise throw an error.
+ // This is only to have a better contract semantic, i.e. another safety net
+ // to catch a logic error. The condition shall be fulfilled in normal case.
+ // Do NOT use this to enforce a certain condition on any user input.
+
+ function assert(condition, message) {
+ /* istanbul ignore if */
+ if (!condition) {
+ throw new Error('ASSERT: ' + message);
+ }
+ }
+
+ function StringMap() {
+ this.$data = {};
+ }
+
+ StringMap.prototype.get = function (key) {
+ key = '$' + key;
+ return this.$data[key];
+ };
+
+ StringMap.prototype.set = function (key, value) {
+ key = '$' + key;
+ this.$data[key] = value;
+ return this;
+ };
+
+ StringMap.prototype.has = function (key) {
+ key = '$' + key;
+ return Object.prototype.hasOwnProperty.call(this.$data, key);
+ };
+
+ StringMap.prototype.delete = function (key) {
+ key = '$' + key;
+ return delete this.$data[key];
+ };
+
+ function isDecimalDigit(ch) {
+ return (ch >= 48 && ch <= 57); // 0..9
+ }
+
+ function isHexDigit(ch) {
+ return '0123456789abcdefABCDEF'.indexOf(ch) >= 0;
+ }
+
+ function isOctalDigit(ch) {
+ return '01234567'.indexOf(ch) >= 0;
+ }
+
+
+ // 7.2 White Space
+
+ function isWhiteSpace(ch) {
+ return (ch === 32) || // space
+ (ch === 9) || // tab
+ (ch === 0xB) ||
+ (ch === 0xC) ||
+ (ch === 0xA0) ||
+ (ch >= 0x1680 && '\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u3000\uFEFF'.indexOf(String.fromCharCode(ch)) > 0);
+ }
+
+ // 7.3 Line Terminators
+
+ function isLineTerminator(ch) {
+ return (ch === 10) || (ch === 13) || (ch === 0x2028) || (ch === 0x2029);
+ }
+
+ // 7.6 Identifier Names and Identifiers
+
+ function isIdentifierStart(ch) {
+ return (ch === 36) || (ch === 95) || // $ (dollar) and _ (underscore)
+ (ch >= 65 && ch <= 90) || // A..Z
+ (ch >= 97 && ch <= 122) || // a..z
+ (ch === 92) || // \ (backslash)
+ ((ch >= 0x80) && Regex.NonAsciiIdentifierStart.test(String.fromCharCode(ch)));
+ }
+
+ function isIdentifierPart(ch) {
+ return (ch === 36) || (ch === 95) || // $ (dollar) and _ (underscore)
+ (ch >= 65 && ch <= 90) || // A..Z
+ (ch >= 97 && ch <= 122) || // a..z
+ (ch >= 48 && ch <= 57) || // 0..9
+ (ch === 92) || // \ (backslash)
+ ((ch >= 0x80) && Regex.NonAsciiIdentifierPart.test(String.fromCharCode(ch)));
+ }
+
+ // 7.6.1.2 Future Reserved Words
+
+ function isFutureReservedWord(id) {
+ switch (id) {
+ case 'class':
+ case 'enum':
+ case 'export':
+ case 'extends':
+ case 'import':
+ case 'super':
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ function isStrictModeReservedWord(id) {
+ switch (id) {
+ case 'implements':
+ case 'interface':
+ case 'package':
+ case 'private':
+ case 'protected':
+ case 'public':
+ case 'static':
+ case 'yield':
+ case 'let':
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ function isRestrictedWord(id) {
+ return id === 'eval' || id === 'arguments';
+ }
+
+ // 7.6.1.1 Keywords
+
+ function isKeyword(id) {
+ if (strict && isStrictModeReservedWord(id)) {
+ return true;
+ }
+
+ // 'const' is specialized as Keyword in V8.
+ // 'yield' is only treated as a keyword in strict mode.
+ // 'let' is for compatiblity with SpiderMonkey and ES.next.
+ // Some others are from future reserved words.
+
+ switch (id.length) {
+ case 2:
+ return (id === 'if') || (id === 'in') || (id === 'do');
+ case 3:
+ return (id === 'var') || (id === 'for') || (id === 'new') ||
+ (id === 'try') || (id === 'let');
+ case 4:
+ return (id === 'this') || (id === 'else') || (id === 'case') ||
+ (id === 'void') || (id === 'with') || (id === 'enum');
+ case 5:
+ return (id === 'while') || (id === 'break') || (id === 'catch') ||
+ (id === 'throw') || (id === 'const') ||
+ (id === 'class') || (id === 'super');
+ case 6:
+ return (id === 'return') || (id === 'typeof') || (id === 'delete') ||
+ (id === 'switch') || (id === 'export') || (id === 'import');
+ case 7:
+ return (id === 'default') || (id === 'finally') || (id === 'extends');
+ case 8:
+ return (id === 'function') || (id === 'continue') || (id === 'debugger');
+ case 10:
+ return (id === 'instanceof');
+ default:
+ return false;
+ }
+ }
+
+ // 7.4 Comments
+
+ function addComment(type, value, start, end, loc) {
+ var comment;
+ assert(typeof start === 'number', 'Comment must have valid position');
+
+ // Because the way the actual token is scanned, often the comments
+ // (if any) are skipped twice during the lexical analysis.
+ // Thus, we need to skip adding a comment if the comment array already
+ // handled it.
+ if (state.lastCommentStart >= start) {
+ return;
+ }
+ state.lastCommentStart = start;
+
+ comment = {
+ type: type,
+ value: value
+ };
+ if (extra.range) {
+ comment.range = [start, end];
+ }
+ if (extra.loc) {
+ comment.loc = loc;
+ }
+ extra.comments.push(comment);
+ if (extra.attachComment) {
+ extra.leadingComments.push(comment);
+ extra.trailingComments.push(comment);
+ }
+ }
+
+ function skipSingleLineComment() {
+ var start, loc, ch, comment;
+
+ start = index - 2;
+ loc = {
+ start: {
+ line: lineNumber,
+ column: index - lineStart - 2
+ }
+ };
+
+ while (index < length) {
+ ch = source.charCodeAt(index);
+ ++index;
+ if (isLineTerminator(ch)) {
+ if (extra.comments) {
+ comment = source.slice(start + 2, index - 1);
+ loc.end = {
+ line: lineNumber,
+ column: index - lineStart - 1
+ };
+ addComment('Line', comment, start, index - 1, loc);
+ }
+ if (ch === 13 && source.charCodeAt(index) === 10) {
+ ++index;
+ }
+ ++lineNumber;
+ lineStart = index;
+ return;
+ }
+ }
+
+ if (extra.comments) {
+ comment = source.slice(start + 2, index);
+ loc.end = {
+ line: lineNumber,
+ column: index - lineStart
+ };
+ addComment('Line', comment, start, index, loc);
+ }
+ }
+
+ function skipMultiLineComment() {
+ var start, loc, ch, comment;
+
+ if (extra.comments) {
+ start = index - 2;
+ loc = {
+ start: {
+ line: lineNumber,
+ column: index - lineStart - 2
+ }
+ };
+ }
+
+ while (index < length) {
+ ch = source.charCodeAt(index);
+ if (isLineTerminator(ch)) {
+ if (ch === 13 && source.charCodeAt(index + 1) === 10) {
+ ++index;
+ }
+ ++lineNumber;
+ ++index;
+ lineStart = index;
+ if (index >= length) {
+ throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
+ }
+ } else if (ch === 42) {
+ // Block comment ends with '*/' (char #42, char #47).
+ if (source.charCodeAt(index + 1) === 47) {
+ ++index;
+ ++index;
+ if (extra.comments) {
+ comment = source.slice(start + 2, index - 2);
+ loc.end = {
+ line: lineNumber,
+ column: index - lineStart
+ };
+ addComment('Block', comment, start, index, loc);
+ }
+ return;
+ }
+ ++index;
+ } else {
+ ++index;
+ }
+ }
+
+ throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
+ }
+
+ function skipComment() {
+ var ch;
+
+ while (index < length) {
+ ch = source.charCodeAt(index);
+
+ if (isWhiteSpace(ch)) {
+ ++index;
+ } else if (isLineTerminator(ch)) {
+ ++index;
+ if (ch === 13 && source.charCodeAt(index) === 10) {
+ ++index;
+ }
+ ++lineNumber;
+ lineStart = index;
+ } else if (ch === 47) { // 47 is '/'
+ ch = source.charCodeAt(index + 1);
+ if (ch === 47) {
+ ++index;
+ ++index;
+ skipSingleLineComment();
+ } else if (ch === 42) { // 42 is '*'
+ ++index;
+ ++index;
+ skipMultiLineComment();
+ } else {
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+ }
+
+ function scanHexEscape(prefix) {
+ var i, len, ch, code = 0;
+
+ len = (prefix === 'u') ? 4 : 2;
+ for (i = 0; i < len; ++i) {
+ if (index < length && isHexDigit(source[index])) {
+ ch = source[index++];
+ code = code * 16 + '0123456789abcdef'.indexOf(ch.toLowerCase());
+ } else {
+ return '';
+ }
+ }
+ return String.fromCharCode(code);
+ }
+
+ function scanUnicodeCodePointEscape() {
+ var ch, code, cu1, cu2;
+
+ ch = source[index];
+ code = 0;
+
+ // At least, one hex digit is required.
+ if (ch === '}') {
+ throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
+ }
+
+ while (index < length) {
+ ch = source[index++];
+ if (!isHexDigit(ch)) {
+ break;
+ }
+ code = code * 16 + '0123456789abcdef'.indexOf(ch.toLowerCase());
+ }
+
+ if (code > 0x10FFFF || ch !== '}') {
+ throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
+ }
+
+ // UTF-16 Encoding
+ if (code <= 0xFFFF) {
+ return String.fromCharCode(code);
+ }
+ cu1 = ((code - 0x10000) >> 10) + 0xD800;
+ cu2 = ((code - 0x10000) & 1023) + 0xDC00;
+ return String.fromCharCode(cu1, cu2);
+ }
+
+ function getEscapedIdentifier() {
+ var ch, id;
+
+ ch = source.charCodeAt(index++);
+ id = String.fromCharCode(ch);
+
+ // '\u' (char #92, char #117) denotes an escaped character.
+ if (ch === 92) {
+ if (source.charCodeAt(index) !== 117) {
+ throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
+ }
+ ++index;
+ ch = scanHexEscape('u');
+ if (!ch || ch === '\\' || !isIdentifierStart(ch.charCodeAt(0))) {
+ throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
+ }
+ id = ch;
+ }
+
+ while (index < length) {
+ ch = source.charCodeAt(index);
+ if (!isIdentifierPart(ch)) {
+ break;
+ }
+ ++index;
+ id += String.fromCharCode(ch);
+
+ // '\u' (char #92, char #117) denotes an escaped character.
+ if (ch === 92) {
+ id = id.substr(0, id.length - 1);
+ if (source.charCodeAt(index) !== 117) {
+ throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
+ }
+ ++index;
+ ch = scanHexEscape('u');
+ if (!ch || ch === '\\' || !isIdentifierPart(ch.charCodeAt(0))) {
+ throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
+ }
+ id += ch;
+ }
+ }
+
+ return id;
+ }
+
+ function getIdentifier() {
+ var start, ch;
+
+ start = index++;
+ while (index < length) {
+ ch = source.charCodeAt(index);
+ if (ch === 92) {
+ // Blackslash (char #92) marks Unicode escape sequence.
+ index = start;
+ return getEscapedIdentifier();
+ }
+ if (isIdentifierPart(ch)) {
+ ++index;
+ } else {
+ break;
+ }
+ }
+
+ return source.slice(start, index);
+ }
+
+ function scanIdentifier() {
+ var start, id, type;
+
+ start = index;
+
+ // Backslash (char #92) starts an escaped character.
+ id = (source.charCodeAt(index) === 92) ? getEscapedIdentifier() : getIdentifier();
+
+ // There is no keyword or literal with only one character.
+ // Thus, it must be an identifier.
+ if (id.length === 1) {
+ type = Token.Identifier;
+ } else if (isKeyword(id)) {
+ type = Token.Keyword;
+ } else if (id === 'null') {
+ type = Token.NullLiteral;
+ } else if (id === 'true' || id === 'false') {
+ type = Token.BooleanLiteral;
+ } else {
+ type = Token.Identifier;
+ }
+
+ return {
+ type: type,
+ value: id,
+ lineNumber: lineNumber,
+ lineStart: lineStart,
+ range: [start, index]
+ };
+ }
+
+
+ // 7.7 Punctuators
+
+ function scanPunctuator() {
+ var start = index,
+ code = source.charCodeAt(index),
+ code2,
+ ch1 = source[index],
+ ch2,
+ ch3,
+ ch4;
+
+ switch (code) {
+ // Check for most common single-character punctuators.
+ case 40: // ( open bracket
+ case 41: // ) close bracket
+ case 59: // ; semicolon
+ case 44: // , comma
+ case 123: // { open curly brace
+ case 125: // } close curly brace
+ case 91: // [
+ case 93: // ]
+ case 58: // :
+ case 63: // ?
+ case 126: // ~
+ ++index;
+ if (extra.tokenize) {
+ if (code === 40) {
+ extra.openParenToken = extra.tokens.length;
+ } else if (code === 123) {
+ extra.openCurlyToken = extra.tokens.length;
+ }
+ }
+ return {
+ type: Token.Punctuator,
+ value: String.fromCharCode(code),
+ lineNumber: lineNumber,
+ lineStart: lineStart,
+ range: [start, index]
+ };
+
+ default:
+ code2 = source.charCodeAt(index + 1);
+
+ // '=' (char #61) marks an assignment or comparison operator.
+ if (code2 === 61) {
+ switch (code) {
+ case 37: // %
+ case 38: // &
+ case 42: // *:
+ case 43: // +
+ case 45: // -
+ case 47: // /
+ case 60: // <
+ case 62: // >
+ case 94: // ^
+ case 124: // |
+ index += 2;
+ return {
+ type: Token.Punctuator,
+ value: String.fromCharCode(code) + String.fromCharCode(code2),
+ lineNumber: lineNumber,
+ lineStart: lineStart,
+ range: [start, index]
+ };
+
+ case 33: // !
+ case 61: // =
+ index += 2;
+
+ // !== and ===
+ if (source.charCodeAt(index) === 61) {
+ ++index;
+ }
+ return {
+ type: Token.Punctuator,
+ value: source.slice(start, index),
+ lineNumber: lineNumber,
+ lineStart: lineStart,
+ range: [start, index]
+ };
+ default:
+ break;
+ }
+ }
+ break;
+ }
+
+ // Peek more characters.
+
+ ch2 = source[index + 1];
+ ch3 = source[index + 2];
+ ch4 = source[index + 3];
+
+ // 4-character punctuator: >>>=
+
+ if (ch1 === '>' && ch2 === '>' && ch3 === '>') {
+ if (ch4 === '=') {
+ index += 4;
+ return {
+ type: Token.Punctuator,
+ value: '>>>=',
+ lineNumber: lineNumber,
+ lineStart: lineStart,
+ range: [start, index]
+ };
+ }
+ }
+
+ // 3-character punctuators: === !== >>> <<= >>=
+
+ if (ch1 === '>' && ch2 === '>' && ch3 === '>') {
+ index += 3;
+ return {
+ type: Token.Punctuator,
+ value: '>>>',
+ lineNumber: lineNumber,
+ lineStart: lineStart,
+ range: [start, index]
+ };
+ }
+
+ if (ch1 === '<' && ch2 === '<' && ch3 === '=') {
+ index += 3;
+ return {
+ type: Token.Punctuator,
+ value: '<<=',
+ lineNumber: lineNumber,
+ lineStart: lineStart,
+ range: [start, index]
+ };
+ }
+
+ if (ch1 === '>' && ch2 === '>' && ch3 === '=') {
+ index += 3;
+ return {
+ type: Token.Punctuator,
+ value: '>>=',
+ lineNumber: lineNumber,
+ lineStart: lineStart,
+ range: [start, index]
+ };
+ }
+
+ if (ch1 === '.' && ch2 === '.' && ch3 === '.') {
+ index += 3;
+ return {
+ type: Token.Punctuator,
+ value: '...',
+ lineNumber: lineNumber,
+ lineStart: lineStart,
+ range: [start, index]
+ };
+ }
+
+ // Other 2-character punctuators: ++ -- << >> && ||
+
+ if (ch1 === ch2 && ('+-<>&|'.indexOf(ch1) >= 0)) {
+ index += 2;
+ return {
+ type: Token.Punctuator,
+ value: ch1 + ch2,
+ lineNumber: lineNumber,
+ lineStart: lineStart,
+ range: [start, index]
+ };
+ }
+
+ if (ch1 === '=' && ch2 === '>') {
+ index += 2;
+ return {
+ type: Token.Punctuator,
+ value: '=>',
+ lineNumber: lineNumber,
+ lineStart: lineStart,
+ range: [start, index]
+ };
+ }
+
+ if ('<>=!+-*%&|^/'.indexOf(ch1) >= 0) {
+ ++index;
+ return {
+ type: Token.Punctuator,
+ value: ch1,
+ lineNumber: lineNumber,
+ lineStart: lineStart,
+ range: [start, index]
+ };
+ }
+
+ if (ch1 === '.') {
+ ++index;
+ return {
+ type: Token.Punctuator,
+ value: ch1,
+ lineNumber: lineNumber,
+ lineStart: lineStart,
+ range: [start, index]
+ };
+ }
+
+ throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
+ }
+
+ // 7.8.3 Numeric Literals
+
+ function scanHexLiteral(start) {
+ var number = '';
+
+ while (index < length) {
+ if (!isHexDigit(source[index])) {
+ break;
+ }
+ number += source[index++];
+ }
+
+ if (number.length === 0) {
+ throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
+ }
+
+ if (isIdentifierStart(source.charCodeAt(index))) {
+ throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
+ }
+
+ return {
+ type: Token.NumericLiteral,
+ value: parseInt('0x' + number, 16),
+ lineNumber: lineNumber,
+ lineStart: lineStart,
+ range: [start, index]
+ };
+ }
+
+ function scanBinaryLiteral(start) {
+ var ch, number;
+
+ number = '';
+
+ while (index < length) {
+ ch = source[index];
+ if (ch !== '0' && ch !== '1') {
+ break;
+ }
+ number += source[index++];
+ }
+
+ if (number.length === 0) {
+ // only 0b or 0B
+ throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
+ }
+
+ if (index < length) {
+ ch = source.charCodeAt(index);
+ /* istanbul ignore else */
+ if (isIdentifierStart(ch) || isDecimalDigit(ch)) {
+ throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
+ }
+ }
+
+ return {
+ type: Token.NumericLiteral,
+ value: parseInt(number, 2),
+ lineNumber: lineNumber,
+ lineStart: lineStart,
+ range: [start, index]
+ };
+ }
+
+ function scanOctalLiteral(prefix, start) {
+ var number, octal;
+
+ if (isOctalDigit(prefix)) {
+ octal = true;
+ number = '0' + source[index++];
+ } else {
+ octal = false;
+ ++index;
+ number = '';
+ }
+
+ while (index < length) {
+ if (!isOctalDigit(source[index])) {
+ break;
+ }
+ number += source[index++];
+ }
+
+ if (!octal && number.length === 0) {
+ // only 0o or 0O
+ throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
+ }
+
+ if (isIdentifierStart(source.charCodeAt(index)) || isDecimalDigit(source.charCodeAt(index))) {
+ throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
+ }
+
+ return {
+ type: Token.NumericLiteral,
+ value: parseInt(number, 8),
+ octal: octal,
+ lineNumber: lineNumber,
+ lineStart: lineStart,
+ range: [start, index]
+ };
+ }
+
+ function scanNumericLiteral() {
+ var number, start, ch;
+
+ ch = source[index];
+ assert(isDecimalDigit(ch.charCodeAt(0)) || (ch === '.'),
+ 'Numeric literal must start with a decimal digit or a decimal point');
+
+ start = index;
+ number = '';
+ if (ch !== '.') {
+ number = source[index++];
+ ch = source[index];
+
+ // Hex number starts with '0x'.
+ // Octal number starts with '0'.
+ // Octal number in ES6 starts with '0o'.
+ // Binary number in ES6 starts with '0b'.
+ if (number === '0') {
+ if (ch === 'x' || ch === 'X') {
+ ++index;
+ return scanHexLiteral(start);
+ }
+ if (ch === 'b' || ch === 'B') {
+ ++index;
+ return scanBinaryLiteral(start);
+ }
+ if (ch === 'o' || ch === 'O' || isOctalDigit(ch)) {
+ return scanOctalLiteral(ch, start);
+ }
+ // decimal number starts with '0' such as '09' is illegal.
+ if (ch && isDecimalDigit(ch.charCodeAt(0))) {
+ throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
+ }
+ }
+
+ while (isDecimalDigit(source.charCodeAt(index))) {
+ number += source[index++];
+ }
+ ch = source[index];
+ }
+
+ if (ch === '.') {
+ number += source[index++];
+ while (isDecimalDigit(source.charCodeAt(index))) {
+ number += source[index++];
+ }
+ ch = source[index];
+ }
+
+ if (ch === 'e' || ch === 'E') {
+ number += source[index++];
+
+ ch = source[index];
+ if (ch === '+' || ch === '-') {
+ number += source[index++];
+ }
+ if (isDecimalDigit(source.charCodeAt(index))) {
+ while (isDecimalDigit(source.charCodeAt(index))) {
+ number += source[index++];
+ }
+ } else {
+ throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
+ }
+ }
+
+ if (isIdentifierStart(source.charCodeAt(index))) {
+ throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
+ }
+
+ return {
+ type: Token.NumericLiteral,
+ value: parseFloat(number),
+ lineNumber: lineNumber,
+ lineStart: lineStart,
+ range: [start, index]
+ };
+ }
+
+ // 7.8.4 String Literals
+
+ function scanStringLiteral() {
+ var str = '', quote, start, ch, code, unescaped, restore, octal = false;
+
+ quote = source[index];
+ assert((quote === '\'' || quote === '"'),
+ 'String literal must starts with a quote');
+
+ start = index;
+ ++index;
+
+ while (index < length) {
+ ch = source[index++];
+
+ if (ch === quote) {
+ quote = '';
+ break;
+ } else if (ch === '\\') {
+ ch = source[index++];
+ if (!ch || !isLineTerminator(ch.charCodeAt(0))) {
+ switch (ch) {
+ case 'n':
+ str += '\n';
+ break;
+ case 'r':
+ str += '\r';
+ break;
+ case 't':
+ str += '\t';
+ break;
+ case 'u':
+ case 'x':
+ if (source[index] === '{') {
+ ++index;
+ str += scanUnicodeCodePointEscape();
+ } else {
+ restore = index;
+ unescaped = scanHexEscape(ch);
+ if (unescaped) {
+ str += unescaped;
+ } else {
+ index = restore;
+ str += ch;
+ }
+ }
+ break;
+ case 'b':
+ str += '\b';
+ break;
+ case 'f':
+ str += '\f';
+ break;
+ case 'v':
+ str += '\x0B';
+ break;
+
+ default:
+ if (isOctalDigit(ch)) {
+ code = '01234567'.indexOf(ch);
+
+ // \0 is not octal escape sequence
+ if (code !== 0) {
+ octal = true;
+ }
+
+ /* istanbul ignore else */
+ if (index < length && isOctalDigit(source[index])) {
+ octal = true;
+ code = code * 8 + '01234567'.indexOf(source[index++]);
+
+ // 3 digits are only allowed when string starts
+ // with 0, 1, 2, 3
+ if ('0123'.indexOf(ch) >= 0 &&
+ index < length &&
+ isOctalDigit(source[index])) {
+ code = code * 8 + '01234567'.indexOf(source[index++]);
+ }
+ }
+ str += String.fromCharCode(code);
+ } else {
+ str += ch;
+ }
+ break;
+ }
+ } else {
+ ++lineNumber;
+ if (ch === '\r' && source[index] === '\n') {
+ ++index;
+ }
+ lineStart = index;
+ }
+ } else if (isLineTerminator(ch.charCodeAt(0))) {
+ break;
+ } else {
+ str += ch;
+ }
+ }
+
+ if (quote !== '') {
+ throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
+ }
+
+ return {
+ type: Token.StringLiteral,
+ value: str,
+ octal: octal,
+ lineNumber: lineNumber,
+ lineStart: lineStart,
+ range: [start, index]
+ };
+ }
+
+ function scanTemplate() {
+ var cooked = '', ch, start, terminated, tail, restore, unescaped, code, octal;
+
+ terminated = false;
+ tail = false;
+ start = index;
+
+ ++index;
+
+ while (index < length) {
+ ch = source[index++];
+ if (ch === '`') {
+ tail = true;
+ terminated = true;
+ break;
+ } else if (ch === '$') {
+ if (source[index] === '{') {
+ ++index;
+ terminated = true;
+ break;
+ }
+ cooked += ch;
+ } else if (ch === '\\') {
+ ch = source[index++];
+ if (!isLineTerminator(ch.charCodeAt(0))) {
+ switch (ch) {
+ case 'n':
+ cooked += '\n';
+ break;
+ case 'r':
+ cooked += '\r';
+ break;
+ case 't':
+ cooked += '\t';
+ break;
+ case 'u':
+ case 'x':
+ if (source[index] === '{') {
+ ++index;
+ cooked += scanUnicodeCodePointEscape();
+ } else {
+ restore = index;
+ unescaped = scanHexEscape(ch);
+ if (unescaped) {
+ cooked += unescaped;
+ } else {
+ index = restore;
+ cooked += ch;
+ }
+ }
+ break;
+ case 'b':
+ cooked += '\b';
+ break;
+ case 'f':
+ cooked += '\f';
+ break;
+ case 'v':
+ cooked += '\v';
+ break;
+
+ default:
+ if (isOctalDigit(ch)) {
+ code = '01234567'.indexOf(ch);
+
+ // \0 is not octal escape sequence
+ if (code !== 0) {
+ octal = true;
+ }
+
+ /* istanbul ignore else */
+ if (index < length && isOctalDigit(source[index])) {
+ octal = true;
+ code = code * 8 + '01234567'.indexOf(source[index++]);
+
+ // 3 digits are only allowed when string starts
+ // with 0, 1, 2, 3
+ if ('0123'.indexOf(ch) >= 0 &&
+ index < length &&
+ isOctalDigit(source[index])) {
+ code = code * 8 + '01234567'.indexOf(source[index++]);
+ }
+ }
+ cooked += String.fromCharCode(code);
+ } else {
+ cooked += ch;
+ }
+ break;
+ }
+ } else {
+ ++lineNumber;
+ if (ch === '\r' && source[index] === '\n') {
+ ++index;
+ }
+ lineStart = index;
+ }
+ } else if (isLineTerminator(ch.charCodeAt(0))) {
+ ++lineNumber;
+ if (ch === '\r' && source[index] === '\n') {
+ ++index;
+ }
+ lineStart = index;
+ cooked += '\n';
+ } else {
+ cooked += ch;
+ }
+ }
+
+ if (!terminated) {
+ throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
+ }
+
+ return {
+ type: Token.Template,
+ value: {
+ cooked: cooked,
+ raw: source.slice(start + 1, index - ((tail) ? 1 : 2))
+ },
+ tail: tail,
+ octal: octal,
+ lineNumber: lineNumber,
+ lineStart: lineStart,
+ range: [start, index]
+ };
+ }
+
+ function scanTemplateElement(option) {
+ var startsWith, template;
+
+ lookahead = null;
+ skipComment();
+
+ startsWith = (option.head) ? '`' : '}';
+
+ if (source[index] !== startsWith) {
+ throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
+ }
+
+ template = scanTemplate();
+
+ peek();
+
+ return template;
+ }
+
+ function testRegExp(pattern, flags) {
+ var tmp = pattern,
+ value;
+
+ if (flags.indexOf('u') >= 0) {
+ // Replace each astral symbol and every Unicode code point
+ // escape sequence with a single ASCII symbol to avoid throwing on
+ // regular expressions that are only valid in combination with the
+ // `/u` flag.
+ // Note: replacing with the ASCII symbol `x` might cause false
+ // negatives in unlikely scenarios. For example, `[\u{61}-b]` is a
+ // perfectly valid pattern that is equivalent to `[a-b]`, but it
+ // would be replaced by `[x-b]` which throws an error.
+ tmp = tmp
+ .replace(/\\u\{([0-9a-fA-F]+)\}/g, function ($0, $1) {
+ if (parseInt($1, 16) <= 0x10FFFF) {
+ return 'x';
+ }
+ throwError({}, Messages.InvalidRegExp);
+ })
+ .replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, 'x');
+ }
+
+ // First, detect invalid regular expressions.
+ try {
+ value = new RegExp(tmp);
+ } catch (e) {
+ throwError({}, Messages.InvalidRegExp);
+ }
+
+ // Return a regular expression object for this pattern-flag pair, or
+ // `null` in case the current environment doesn't support the flags it
+ // uses.
+ try {
+ return new RegExp(pattern, flags);
+ } catch (exception) {
+ return null;
+ }
+ }
+
+ function scanRegExpBody() {
+ var ch, str, classMarker, terminated, body;
+
+ ch = source[index];
+ assert(ch === '/', 'Regular expression literal must start with a slash');
+ str = source[index++];
+
+ classMarker = false;
+ terminated = false;
+ while (index < length) {
+ ch = source[index++];
+ str += ch;
+ if (ch === '\\') {
+ ch = source[index++];
+ // ECMA-262 7.8.5
+ if (isLineTerminator(ch.charCodeAt(0))) {
+ throwError({}, Messages.UnterminatedRegExp);
+ }
+ str += ch;
+ } else if (isLineTerminator(ch.charCodeAt(0))) {
+ throwError({}, Messages.UnterminatedRegExp);
+ } else if (classMarker) {
+ if (ch === ']') {
+ classMarker = false;
+ }
+ } else {
+ if (ch === '/') {
+ terminated = true;
+ break;
+ } else if (ch === '[') {
+ classMarker = true;
+ }
+ }
+ }
+
+ if (!terminated) {
+ throwError({}, Messages.UnterminatedRegExp);
+ }
+
+ // Exclude leading and trailing slash.
+ body = str.substr(1, str.length - 2);
+ return {
+ value: body,
+ literal: str
+ };
+ }
+
+ function scanRegExpFlags() {
+ var ch, str, flags, restore;
+
+ str = '';
+ flags = '';
+ while (index < length) {
+ ch = source[index];
+ if (!isIdentifierPart(ch.charCodeAt(0))) {
+ break;
+ }
+
+ ++index;
+ if (ch === '\\' && index < length) {
+ ch = source[index];
+ if (ch === 'u') {
+ ++index;
+ restore = index;
+ ch = scanHexEscape('u');
+ if (ch) {
+ flags += ch;
+ for (str += '\\u'; restore < index; ++restore) {
+ str += source[restore];
+ }
+ } else {
+ index = restore;
+ flags += 'u';
+ str += '\\u';
+ }
+ throwErrorTolerant({}, Messages.UnexpectedToken, 'ILLEGAL');
+ } else {
+ str += '\\';
+ throwErrorTolerant({}, Messages.UnexpectedToken, 'ILLEGAL');
+ }
+ } else {
+ flags += ch;
+ str += ch;
+ }
+ }
+
+ return {
+ value: flags,
+ literal: str
+ };
+ }
+
+ function scanRegExp() {
+ var start, body, flags, value;
+
+ lookahead = null;
+ skipComment();
+ start = index;
+
+ body = scanRegExpBody();
+ flags = scanRegExpFlags();
+ value = testRegExp(body.value, flags.value);
+
+ if (extra.tokenize) {
+ return {
+ type: Token.RegularExpression,
+ value: value,
+ regex: {
+ pattern: body.value,
+ flags: flags.value
+ },
+ lineNumber: lineNumber,
+ lineStart: lineStart,
+ range: [start, index]
+ };
+ }
+
+ return {
+ literal: body.literal + flags.literal,
+ value: value,
+ regex: {
+ pattern: body.value,
+ flags: flags.value
+ },
+ range: [start, index]
+ };
+ }
+
+ function isIdentifierName(token) {
+ return token.type === Token.Identifier ||
+ token.type === Token.Keyword ||
+ token.type === Token.BooleanLiteral ||
+ token.type === Token.NullLiteral;
+ }
+
+ function advanceSlash() {
+ var prevToken,
+ checkToken;
+ // Using the following algorithm:
+ // https://github.com/mozilla/sweet.js/wiki/design
+ prevToken = extra.tokens[extra.tokens.length - 1];
+ if (!prevToken) {
+ // Nothing before that: it cannot be a division.
+ return scanRegExp();
+ }
+ if (prevToken.type === 'Punctuator') {
+ if (prevToken.value === ')') {
+ checkToken = extra.tokens[extra.openParenToken - 1];
+ if (checkToken &&
+ checkToken.type === 'Keyword' &&
+ (checkToken.value === 'if' ||
+ checkToken.value === 'while' ||
+ checkToken.value === 'for' ||
+ checkToken.value === 'with')) {
+ return scanRegExp();
+ }
+ return scanPunctuator();
+ }
+ if (prevToken.value === '}') {
+ // Dividing a function by anything makes little sense,
+ // but we have to check for that.
+ if (extra.tokens[extra.openCurlyToken - 3] &&
+ extra.tokens[extra.openCurlyToken - 3].type === 'Keyword') {
+ // Anonymous function.
+ checkToken = extra.tokens[extra.openCurlyToken - 4];
+ if (!checkToken) {
+ return scanPunctuator();
+ }
+ } else if (extra.tokens[extra.openCurlyToken - 4] &&
+ extra.tokens[extra.openCurlyToken - 4].type === 'Keyword') {
+ // Named function.
+ checkToken = extra.tokens[extra.openCurlyToken - 5];
+ if (!checkToken) {
+ return scanRegExp();
+ }
+ } else {
+ return scanPunctuator();
+ }
+ // checkToken determines whether the function is
+ // a declaration or an expression.
+ if (FnExprTokens.indexOf(checkToken.value) >= 0) {
+ // It is an expression.
+ return scanPunctuator();
+ }
+ // It is a declaration.
+ return scanRegExp();
+ }
+ return scanRegExp();
+ }
+ if (prevToken.type === 'Keyword' && prevToken.value !== 'this') {
+ return scanRegExp();
+ }
+ return scanPunctuator();
+ }
+
+ function advance() {
+ var ch;
+
+ skipComment();
+
+ if (index >= length) {
+ return {
+ type: Token.EOF,
+ lineNumber: lineNumber,
+ lineStart: lineStart,
+ range: [index, index]
+ };
+ }
+
+ ch = source.charCodeAt(index);
+
+ // Very common: ( and ) and ;
+ if (ch === 40 || ch === 41 || ch === 58) {
+ return scanPunctuator();
+ }
+
+ // String literal starts with single quote (#39) or double quote (#34).
+ if (ch === 39 || ch === 34) {
+ return scanStringLiteral();
+ }
+
+ if (ch === 96) {
+ return scanTemplate();
+ }
+ if (isIdentifierStart(ch)) {
+ return scanIdentifier();
+ }
+
+ // Dot (.) char #46 can also start a floating-point number, hence the need
+ // to check the next character.
+ if (ch === 46) {
+ if (isDecimalDigit(source.charCodeAt(index + 1))) {
+ return scanNumericLiteral();
+ }
+ return scanPunctuator();
+ }
+
+ if (isDecimalDigit(ch)) {
+ return scanNumericLiteral();
+ }
+
+ // Slash (/) char #47 can also start a regex.
+ if (extra.tokenize && ch === 47) {
+ return advanceSlash();
+ }
+
+ return scanPunctuator();
+ }
+
+ function lex() {
+ var token;
+
+ token = lookahead;
+ index = token.range[1];
+ lineNumber = token.lineNumber;
+ lineStart = token.lineStart;
+
+ lookahead = advance();
+
+ index = token.range[1];
+ lineNumber = token.lineNumber;
+ lineStart = token.lineStart;
+
+ return token;
+ }
+
+ function peek() {
+ var pos, line, start;
+
+ pos = index;
+ line = lineNumber;
+ start = lineStart;
+ lookahead = advance();
+ index = pos;
+ lineNumber = line;
+ lineStart = start;
+ }
+
+ function lookahead2() {
+ var adv, pos, line, start, result;
+
+ // If we are collecting the tokens, don't grab the next one yet.
+ /* istanbul ignore next */
+ adv = (typeof extra.advance === 'function') ? extra.advance : advance;
+
+ pos = index;
+ line = lineNumber;
+ start = lineStart;
+
+ // Scan for the next immediate token.
+ /* istanbul ignore if */
+ if (lookahead === null) {
+ lookahead = adv();
+ }
+ index = lookahead.range[1];
+ lineNumber = lookahead.lineNumber;
+ lineStart = lookahead.lineStart;
+
+ // Grab the token right after.
+ result = adv();
+ index = pos;
+ lineNumber = line;
+ lineStart = start;
+
+ return result;
+ }
+
+ function markerCreate() {
+ if (!extra.loc && !extra.range) {
+ return undefined;
+ }
+ skipComment();
+ return {offset: index, line: lineNumber, col: index - lineStart};
+ }
+
+ function processComment(node) {
+ var lastChild,
+ trailingComments,
+ bottomRight = extra.bottomRightStack,
+ last = bottomRight[bottomRight.length - 1];
+
+ if (node.type === Syntax.Program) {
+ /* istanbul ignore else */
+ if (node.body.length > 0) {
+ return;
+ }
+ }
+
+ if (extra.trailingComments.length > 0) {
+ if (extra.trailingComments[0].range[0] >= node.range[1]) {
+ trailingComments = extra.trailingComments;
+ extra.trailingComments = [];
+ } else {
+ extra.trailingComments.length = 0;
+ }
+ } else {
+ if (last && last.trailingComments && last.trailingComments[0].range[0] >= node.range[1]) {
+ trailingComments = last.trailingComments;
+ delete last.trailingComments;
+ }
+ }
+
+ // Eating the stack.
+ if (last) {
+ while (last && last.range[0] >= node.range[0]) {
+ lastChild = last;
+ last = bottomRight.pop();
+ }
+ }
+
+ if (lastChild) {
+ if (lastChild.leadingComments && lastChild.leadingComments[lastChild.leadingComments.length - 1].range[1] <= node.range[0]) {
+ node.leadingComments = lastChild.leadingComments;
+ delete lastChild.leadingComments;
+ }
+ } else if (extra.leadingComments.length > 0 && extra.leadingComments[extra.leadingComments.length - 1].range[1] <= node.range[0]) {
+ node.leadingComments = extra.leadingComments;
+ extra.leadingComments = [];
+ }
+
+ if (trailingComments) {
+ node.trailingComments = trailingComments;
+ }
+
+ bottomRight.push(node);
+ }
+
+ function markerApply(marker, node) {
+ if (extra.range) {
+ node.range = [marker.offset, index];
+ }
+ if (extra.loc) {
+ node.loc = {
+ start: {
+ line: marker.line,
+ column: marker.col
+ },
+ end: {
+ line: lineNumber,
+ column: index - lineStart
+ }
+ };
+ node = delegate.postProcess(node);
+ }
+ if (extra.attachComment) {
+ processComment(node);
+ }
+ return node;
+ }
+
+ SyntaxTreeDelegate = {
+
+ name: 'SyntaxTree',
+
+ postProcess: function (node) {
+ return node;
+ },
+
+ createArrayExpression: function (elements) {
+ return {
+ type: Syntax.ArrayExpression,
+ elements: elements
+ };
+ },
+
+ createAssignmentExpression: function (operator, left, right) {
+ return {
+ type: Syntax.AssignmentExpression,
+ operator: operator,
+ left: left,
+ right: right
+ };
+ },
+
+ createBinaryExpression: function (operator, left, right) {
+ var type = (operator === '||' || operator === '&&') ? Syntax.LogicalExpression :
+ Syntax.BinaryExpression;
+ return {
+ type: type,
+ operator: operator,
+ left: left,
+ right: right
+ };
+ },
+
+ createBlockStatement: function (body) {
+ return {
+ type: Syntax.BlockStatement,
+ body: body
+ };
+ },
+
+ createBreakStatement: function (label) {
+ return {
+ type: Syntax.BreakStatement,
+ label: label
+ };
+ },
+
+ createCallExpression: function (callee, args) {
+ return {
+ type: Syntax.CallExpression,
+ callee: callee,
+ 'arguments': args
+ };
+ },
+
+ createCatchClause: function (param, body) {
+ return {
+ type: Syntax.CatchClause,
+ param: param,
+ body: body
+ };
+ },
+
+ createConditionalExpression: function (test, consequent, alternate) {
+ return {
+ type: Syntax.ConditionalExpression,
+ test: test,
+ consequent: consequent,
+ alternate: alternate
+ };
+ },
+
+ createContinueStatement: function (label) {
+ return {
+ type: Syntax.ContinueStatement,
+ label: label
+ };
+ },
+
+ createDebuggerStatement: function () {
+ return {
+ type: Syntax.DebuggerStatement
+ };
+ },
+
+ createDoWhileStatement: function (body, test) {
+ return {
+ type: Syntax.DoWhileStatement,
+ body: body,
+ test: test
+ };
+ },
+
+ createEmptyStatement: function () {
+ return {
+ type: Syntax.EmptyStatement
+ };
+ },
+
+ createExpressionStatement: function (expression) {
+ return {
+ type: Syntax.ExpressionStatement,
+ expression: expression
+ };
+ },
+
+ createForStatement: function (init, test, update, body) {
+ return {
+ type: Syntax.ForStatement,
+ init: init,
+ test: test,
+ update: update,
+ body: body
+ };
+ },
+
+ createForInStatement: function (left, right, body) {
+ return {
+ type: Syntax.ForInStatement,
+ left: left,
+ right: right,
+ body: body,
+ each: false
+ };
+ },
+
+ createForOfStatement: function (left, right, body) {
+ return {
+ type: Syntax.ForOfStatement,
+ left: left,
+ right: right,
+ body: body
+ };
+ },
+
+ createFunctionDeclaration: function (id, params, defaults, body, rest, generator, expression) {
+ return {
+ type: Syntax.FunctionDeclaration,
+ id: id,
+ params: params,
+ defaults: defaults,
+ body: body,
+ rest: rest,
+ generator: generator,
+ expression: expression
+ };
+ },
+
+ createFunctionExpression: function (id, params, defaults, body, rest, generator, expression) {
+ return {
+ type: Syntax.FunctionExpression,
+ id: id,
+ params: params,
+ defaults: defaults,
+ body: body,
+ rest: rest,
+ generator: generator,
+ expression: expression
+ };
+ },
+
+ createIdentifier: function (name) {
+ return {
+ type: Syntax.Identifier,
+ name: name
+ };
+ },
+
+ createIfStatement: function (test, consequent, alternate) {
+ return {
+ type: Syntax.IfStatement,
+ test: test,
+ consequent: consequent,
+ alternate: alternate
+ };
+ },
+
+ createLabeledStatement: function (label, body) {
+ return {
+ type: Syntax.LabeledStatement,
+ label: label,
+ body: body
+ };
+ },
+
+ createLiteral: function (token) {
+ var object = {
+ type: Syntax.Literal,
+ value: token.value,
+ raw: source.slice(token.range[0], token.range[1])
+ };
+ if (token.regex) {
+ object.regex = token.regex;
+ }
+ return object;
+ },
+
+ createMemberExpression: function (accessor, object, property) {
+ return {
+ type: Syntax.MemberExpression,
+ computed: accessor === '[',
+ object: object,
+ property: property
+ };
+ },
+
+ createNewExpression: function (callee, args) {
+ return {
+ type: Syntax.NewExpression,
+ callee: callee,
+ 'arguments': args
+ };
+ },
+
+ createObjectExpression: function (properties) {
+ return {
+ type: Syntax.ObjectExpression,
+ properties: properties
+ };
+ },
+
+ createPostfixExpression: function (operator, argument) {
+ return {
+ type: Syntax.UpdateExpression,
+ operator: operator,
+ argument: argument,
+ prefix: false
+ };
+ },
+
+ createProgram: function (body) {
+ return {
+ type: Syntax.Program,
+ body: body
+ };
+ },
+
+ createProperty: function (kind, key, value, method, shorthand, computed) {
+ return {
+ type: Syntax.Property,
+ key: key,
+ value: value,
+ kind: kind,
+ method: method,
+ shorthand: shorthand,
+ computed: computed
+ };
+ },
+
+ createReturnStatement: function (argument) {
+ return {
+ type: Syntax.ReturnStatement,
+ argument: argument
+ };
+ },
+
+ createSequenceExpression: function (expressions) {
+ return {
+ type: Syntax.SequenceExpression,
+ expressions: expressions
+ };
+ },
+
+ createSwitchCase: function (test, consequent) {
+ return {
+ type: Syntax.SwitchCase,
+ test: test,
+ consequent: consequent
+ };
+ },
+
+ createSwitchStatement: function (discriminant, cases) {
+ return {
+ type: Syntax.SwitchStatement,
+ discriminant: discriminant,
+ cases: cases
+ };
+ },
+
+ createThisExpression: function () {
+ return {
+ type: Syntax.ThisExpression
+ };
+ },
+
+ createThrowStatement: function (argument) {
+ return {
+ type: Syntax.ThrowStatement,
+ argument: argument
+ };
+ },
+
+ createTryStatement: function (block, guardedHandlers, handlers, finalizer) {
+ return {
+ type: Syntax.TryStatement,
+ block: block,
+ guardedHandlers: guardedHandlers,
+ handlers: handlers,
+ finalizer: finalizer
+ };
+ },
+
+ createUnaryExpression: function (operator, argument) {
+ if (operator === '++' || operator === '--') {
+ return {
+ type: Syntax.UpdateExpression,
+ operator: operator,
+ argument: argument,
+ prefix: true
+ };
+ }
+ return {
+ type: Syntax.UnaryExpression,
+ operator: operator,
+ argument: argument,
+ prefix: true
+ };
+ },
+
+ createVariableDeclaration: function (declarations, kind) {
+ return {
+ type: Syntax.VariableDeclaration,
+ declarations: declarations,
+ kind: kind
+ };
+ },
+
+ createVariableDeclarator: function (id, init) {
+ return {
+ type: Syntax.VariableDeclarator,
+ id: id,
+ init: init
+ };
+ },
+
+ createWhileStatement: function (test, body) {
+ return {
+ type: Syntax.WhileStatement,
+ test: test,
+ body: body
+ };
+ },
+
+ createWithStatement: function (object, body) {
+ return {
+ type: Syntax.WithStatement,
+ object: object,
+ body: body
+ };
+ },
+
+ createTemplateElement: function (value, tail) {
+ return {
+ type: Syntax.TemplateElement,
+ value: value,
+ tail: tail
+ };
+ },
+
+ createTemplateLiteral: function (quasis, expressions) {
+ return {
+ type: Syntax.TemplateLiteral,
+ quasis: quasis,
+ expressions: expressions
+ };
+ },
+
+ createSpreadElement: function (argument) {
+ return {
+ type: Syntax.SpreadElement,
+ argument: argument
+ };
+ },
+
+ createTaggedTemplateExpression: function (tag, quasi) {
+ return {
+ type: Syntax.TaggedTemplateExpression,
+ tag: tag,
+ quasi: quasi
+ };
+ },
+
+ createArrowFunctionExpression: function (params, defaults, body, rest, expression) {
+ return {
+ type: Syntax.ArrowFunctionExpression,
+ id: null,
+ params: params,
+ defaults: defaults,
+ body: body,
+ rest: rest,
+ generator: false,
+ expression: expression
+ };
+ },
+
+ createMethodDefinition: function (propertyType, kind, key, value, computed) {
+ return {
+ type: Syntax.MethodDefinition,
+ key: key,
+ value: value,
+ kind: kind,
+ 'static': propertyType === ClassPropertyType.static,
+ computed: computed
+ };
+ },
+
+ createClassBody: function (body) {
+ return {
+ type: Syntax.ClassBody,
+ body: body
+ };
+ },
+
+ createClassExpression: function (id, superClass, body) {
+ return {
+ type: Syntax.ClassExpression,
+ id: id,
+ superClass: superClass,
+ body: body
+ };
+ },
+
+ createClassDeclaration: function (id, superClass, body) {
+ return {
+ type: Syntax.ClassDeclaration,
+ id: id,
+ superClass: superClass,
+ body: body
+ };
+ },
+
+ createExportSpecifier: function (id, name) {
+ return {
+ type: Syntax.ExportSpecifier,
+ id: id,
+ name: name
+ };
+ },
+
+ createExportBatchSpecifier: function () {
+ return {
+ type: Syntax.ExportBatchSpecifier
+ };
+ },
+
+ createImportDefaultSpecifier: function (id) {
+ return {
+ type: Syntax.ImportDefaultSpecifier,
+ id: id
+ };
+ },
+
+ createImportNamespaceSpecifier: function (id) {
+ return {
+ type: Syntax.ImportNamespaceSpecifier,
+ id: id
+ };
+ },
+
+ createExportDeclaration: function (isDefault, declaration, specifiers, src) {
+ return {
+ type: Syntax.ExportDeclaration,
+ 'default': !!isDefault,
+ declaration: declaration,
+ specifiers: specifiers,
+ source: src
+ };
+ },
+
+ createImportSpecifier: function (id, name) {
+ return {
+ type: Syntax.ImportSpecifier,
+ id: id,
+ name: name
+ };
+ },
+
+ createImportDeclaration: function (specifiers, src) {
+ return {
+ type: Syntax.ImportDeclaration,
+ specifiers: specifiers,
+ source: src
+ };
+ },
+
+ createYieldExpression: function (argument, dlg) {
+ return {
+ type: Syntax.YieldExpression,
+ argument: argument,
+ delegate: dlg
+ };
+ },
+
+ createComprehensionExpression: function (filter, blocks, body) {
+ return {
+ type: Syntax.ComprehensionExpression,
+ filter: filter,
+ blocks: blocks,
+ body: body
+ };
+ }
+
+ };
+
+ // Return true if there is a line terminator before the next token.
+
+ function peekLineTerminator() {
+ var pos, line, start, found;
+
+ pos = index;
+ line = lineNumber;
+ start = lineStart;
+ skipComment();
+ found = lineNumber !== line;
+ index = pos;
+ lineNumber = line;
+ lineStart = start;
+
+ return found;
+ }
+
+ // Throw an exception
+
+ function throwError(token, messageFormat) {
+ var error,
+ args = Array.prototype.slice.call(arguments, 2),
+ msg = messageFormat.replace(
+ /%(\d)/g,
+ function (whole, idx) {
+ assert(idx < args.length, 'Message reference must be in range');
+ return args[idx];
+ }
+ );
+
+ if (typeof token.lineNumber === 'number') {
+ error = new Error('Line ' + token.lineNumber + ': ' + msg);
+ error.index = token.range[0];
+ error.lineNumber = token.lineNumber;
+ error.column = token.range[0] - lineStart + 1;
+ } else {
+ error = new Error('Line ' + lineNumber + ': ' + msg);
+ error.index = index;
+ error.lineNumber = lineNumber;
+ error.column = index - lineStart + 1;
+ }
+
+ error.description = msg;
+ throw error;
+ }
+
+ function throwErrorTolerant() {
+ try {
+ throwError.apply(null, arguments);
+ } catch (e) {
+ if (extra.errors) {
+ extra.errors.push(e);
+ } else {
+ throw e;
+ }
+ }
+ }
+
+
+ // Throw an exception because of the token.
+
+ function throwUnexpected(token) {
+ if (token.type === Token.EOF) {
+ throwError(token, Messages.UnexpectedEOS);
+ }
+
+ if (token.type === Token.NumericLiteral) {
+ throwError(token, Messages.UnexpectedNumber);
+ }
+
+ if (token.type === Token.StringLiteral) {
+ throwError(token, Messages.UnexpectedString);
+ }
+
+ if (token.type === Token.Identifier) {
+ throwError(token, Messages.UnexpectedIdentifier);
+ }
+
+ if (token.type === Token.Keyword) {
+ if (isFutureReservedWord(token.value)) {
+ throwError(token, Messages.UnexpectedReserved);
+ } else if (strict && isStrictModeReservedWord(token.value)) {
+ throwErrorTolerant(token, Messages.StrictReservedWord);
+ return;
+ }
+ throwError(token, Messages.UnexpectedToken, token.value);
+ }
+
+ if (token.type === Token.Template) {
+ throwError(token, Messages.UnexpectedTemplate, token.value.raw);
+ }
+
+ // BooleanLiteral, NullLiteral, or Punctuator.
+ throwError(token, Messages.UnexpectedToken, token.value);
+ }
+
+ // Expect the next token to match the specified punctuator.
+ // If not, an exception will be thrown.
+
+ function expect(value) {
+ var token = lex();
+ if (token.type !== Token.Punctuator || token.value !== value) {
+ throwUnexpected(token);
+ }
+ }
+
+ // Expect the next token to match the specified keyword.
+ // If not, an exception will be thrown.
+
+ function expectKeyword(keyword) {
+ var token = lex();
+ if (token.type !== Token.Keyword || token.value !== keyword) {
+ throwUnexpected(token);
+ }
+ }
+
+ // Return true if the next token matches the specified punctuator.
+
+ function match(value) {
+ return lookahead.type === Token.Punctuator && lookahead.value === value;
+ }
+
+ // Return true if the next token matches the specified keyword
+
+ function matchKeyword(keyword) {
+ return lookahead.type === Token.Keyword && lookahead.value === keyword;
+ }
+
+
+ // Return true if the next token matches the specified contextual keyword
+
+ function matchContextualKeyword(keyword) {
+ return lookahead.type === Token.Identifier && lookahead.value === keyword;
+ }
+
+ // Return true if the next token is an assignment operator
+
+ function matchAssign() {
+ var op;
+
+ if (lookahead.type !== Token.Punctuator) {
+ return false;
+ }
+ op = lookahead.value;
+ return op === '=' ||
+ op === '*=' ||
+ op === '/=' ||
+ op === '%=' ||
+ op === '+=' ||
+ op === '-=' ||
+ op === '<<=' ||
+ op === '>>=' ||
+ op === '>>>=' ||
+ op === '&=' ||
+ op === '^=' ||
+ op === '|=';
+ }
+
+ function consumeSemicolon() {
+ var line, oldIndex = index, oldLineNumber = lineNumber,
+ oldLineStart = lineStart, oldLookahead = lookahead;
+
+ // Catch the very common case first: immediately a semicolon (char #59).
+ if (source.charCodeAt(index) === 59) {
+ lex();
+ return;
+ }
+
+ line = lineNumber;
+ skipComment();
+ if (lineNumber !== line) {
+ index = oldIndex;
+ lineNumber = oldLineNumber;
+ lineStart = oldLineStart;
+ lookahead = oldLookahead;
+ return;
+ }
+
+ if (match(';')) {
+ lex();
+ return;
+ }
+
+ if (lookahead.type !== Token.EOF && !match('}')) {
+ throwUnexpected(lookahead);
+ }
+ }
+
+ // Return true if provided expression is LeftHandSideExpression
+
+ function isLeftHandSide(expr) {
+ return expr.type === Syntax.Identifier || expr.type === Syntax.MemberExpression;
+ }
+
+ function isAssignableLeftHandSide(expr) {
+ return isLeftHandSide(expr) || expr.type === Syntax.ObjectPattern || expr.type === Syntax.ArrayPattern;
+ }
+
+ // 11.1.4 Array Initialiser
+
+ function parseArrayInitialiser() {
+ var elements = [], blocks = [], filter = null, tmp, possiblecomprehension = true,
+ marker = markerCreate();
+
+ expect('[');
+ while (!match(']')) {
+ if (lookahead.value === 'for' &&
+ lookahead.type === Token.Keyword) {
+ if (!possiblecomprehension) {
+ throwError({}, Messages.ComprehensionError);
+ }
+ matchKeyword('for');
+ tmp = parseForStatement({ignoreBody: true});
+ tmp.of = tmp.type === Syntax.ForOfStatement;
+ tmp.type = Syntax.ComprehensionBlock;
+ if (tmp.left.kind) { // can't be let or const
+ throwError({}, Messages.ComprehensionError);
+ }
+ blocks.push(tmp);
+ } else if (lookahead.value === 'if' &&
+ lookahead.type === Token.Keyword) {
+ if (!possiblecomprehension) {
+ throwError({}, Messages.ComprehensionError);
+ }
+ expectKeyword('if');
+ expect('(');
+ filter = parseExpression();
+ expect(')');
+ } else if (lookahead.value === ',' &&
+ lookahead.type === Token.Punctuator) {
+ possiblecomprehension = false; // no longer allowed.
+ lex();
+ elements.push(null);
+ } else {
+ tmp = parseSpreadOrAssignmentExpression();
+ elements.push(tmp);
+ if (tmp && tmp.type === Syntax.SpreadElement) {
+ if (!match(']')) {
+ throwError({}, Messages.ElementAfterSpreadElement);
+ }
+ } else if (!(match(']') || matchKeyword('for') || matchKeyword('if'))) {
+ expect(','); // this lexes.
+ possiblecomprehension = false;
+ }
+ }
+ }
+
+ expect(']');
+
+ if (filter && !blocks.length) {
+ throwError({}, Messages.ComprehensionRequiresBlock);
+ }
+
+ if (blocks.length) {
+ if (elements.length !== 1) {
+ throwError({}, Messages.ComprehensionError);
+ }
+ return markerApply(marker, delegate.createComprehensionExpression(filter, blocks, elements[0]));
+ }
+ return markerApply(marker, delegate.createArrayExpression(elements));
+ }
+
+ // 11.1.5 Object Initialiser
+
+ function parsePropertyFunction(options) {
+ var previousStrict, previousYieldAllowed, params, defaults, body,
+ marker = markerCreate();
+
+ previousStrict = strict;
+ previousYieldAllowed = state.yieldAllowed;
+ state.yieldAllowed = options.generator;
+ params = options.params || [];
+ defaults = options.defaults || [];
+
+ body = parseConciseBody();
+ if (options.name && strict && isRestrictedWord(params[0].name)) {
+ throwErrorTolerant(options.name, Messages.StrictParamName);
+ }
+ strict = previousStrict;
+ state.yieldAllowed = previousYieldAllowed;
+
+ return markerApply(marker, delegate.createFunctionExpression(
+ null,
+ params,
+ defaults,
+ body,
+ options.rest || null,
+ options.generator,
+ body.type !== Syntax.BlockStatement
+ ));
+ }
+
+
+ function parsePropertyMethodFunction(options) {
+ var previousStrict, tmp, method;
+
+ previousStrict = strict;
+ strict = true;
+
+ tmp = parseParams();
+
+ if (tmp.stricted) {
+ throwErrorTolerant(tmp.stricted, tmp.message);
+ }
+
+
+ method = parsePropertyFunction({
+ params: tmp.params,
+ defaults: tmp.defaults,
+ rest: tmp.rest,
+ generator: options.generator
+ });
+
+ strict = previousStrict;
+
+ return method;
+ }
+
+
+ function parseObjectPropertyKey() {
+ var marker = markerCreate(),
+ token = lex(),
+ propertyKey,
+ result;
+
+ // Note: This function is called only from parseObjectProperty(), where
+ // EOF and Punctuator tokens are already filtered out.
+
+ if (token.type === Token.StringLiteral || token.type === Token.NumericLiteral) {
+ if (strict && token.octal) {
+ throwErrorTolerant(token, Messages.StrictOctalLiteral);
+ }
+ return markerApply(marker, delegate.createLiteral(token));
+ }
+
+ if (token.type === Token.Punctuator && token.value === '[') {
+ // For computed properties we should skip the [ and ], and
+ // capture in marker only the assignment expression itself.
+ marker = markerCreate();
+ propertyKey = parseAssignmentExpression();
+ result = markerApply(marker, propertyKey);
+ expect(']');
+ return result;
+ }
+
+ return markerApply(marker, delegate.createIdentifier(token.value));
+ }
+
+ function parseObjectProperty() {
+ var token, key, id, param, computed,
+ marker = markerCreate();
+
+ token = lookahead;
+ computed = (token.value === '[' && token.type === Token.Punctuator);
+
+ if (token.type === Token.Identifier || computed) {
+
+ id = parseObjectPropertyKey();
+
+ // Property Assignment: Getter and Setter.
+
+ if (token.value === 'get' && !(match(':') || match('('))) {
+ computed = (lookahead.value === '[');
+ key = parseObjectPropertyKey();
+ expect('(');
+ expect(')');
+ return markerApply(marker, delegate.createProperty('get', key, parsePropertyFunction({ generator: false }), false, false, computed));
+ }
+ if (token.value === 'set' && !(match(':') || match('('))) {
+ computed = (lookahead.value === '[');
+ key = parseObjectPropertyKey();
+ expect('(');
+ token = lookahead;
+ param = [ parseVariableIdentifier() ];
+ expect(')');
+ return markerApply(marker, delegate.createProperty('set', key, parsePropertyFunction({ params: param, generator: false, name: token }), false, false, computed));
+ }
+ if (match(':')) {
+ lex();
+ return markerApply(marker, delegate.createProperty('init', id, parseAssignmentExpression(), false, false, computed));
+ }
+ if (match('(')) {
+ return markerApply(marker, delegate.createProperty('init', id, parsePropertyMethodFunction({ generator: false }), true, false, computed));
+ }
+ if (computed) {
+ // Computed properties can only be used with full notation.
+ throwUnexpected(lookahead);
+ }
+ return markerApply(marker, delegate.createProperty('init', id, id, false, true, false));
+ }
+ if (token.type === Token.EOF || token.type === Token.Punctuator) {
+ if (!match('*')) {
+ throwUnexpected(token);
+ }
+ lex();
+
+ computed = (lookahead.type === Token.Punctuator && lookahead.value === '[');
+
+ id = parseObjectPropertyKey();
+
+ if (!match('(')) {
+ throwUnexpected(lex());
+ }
+
+ return markerApply(marker, delegate.createProperty('init', id, parsePropertyMethodFunction({ generator: true }), true, false, computed));
+ }
+ key = parseObjectPropertyKey();
+ if (match(':')) {
+ lex();
+ return markerApply(marker, delegate.createProperty('init', key, parseAssignmentExpression(), false, false, false));
+ }
+ if (match('(')) {
+ return markerApply(marker, delegate.createProperty('init', key, parsePropertyMethodFunction({ generator: false }), true, false, false));
+ }
+ throwUnexpected(lex());
+ }
+
+ function getFieldName(key) {
+ var toString = String;
+ if (key.type === Syntax.Identifier) {
+ return key.name;
+ }
+ return toString(key.value);
+ }
+
+ function parseObjectInitialiser() {
+ var properties = [], property, name, kind, storedKind, map = new StringMap(),
+ marker = markerCreate();
+
+ expect('{');
+
+ while (!match('}')) {
+ property = parseObjectProperty();
+
+ if (!property.computed) {
+ name = getFieldName(property.key);
+ kind = (property.kind === 'init') ? PropertyKind.Data : (property.kind === 'get') ? PropertyKind.Get : PropertyKind.Set;
+
+ if (map.has(name)) {
+ storedKind = map.get(name);
+ if (storedKind === PropertyKind.Data) {
+ if (strict && kind === PropertyKind.Data) {
+ throwErrorTolerant({}, Messages.StrictDuplicateProperty);
+ } else if (kind !== PropertyKind.Data) {
+ throwErrorTolerant({}, Messages.AccessorDataProperty);
+ }
+ } else {
+ if (kind === PropertyKind.Data) {
+ throwErrorTolerant({}, Messages.AccessorDataProperty);
+ } else if (storedKind & kind) {
+ throwErrorTolerant({}, Messages.AccessorGetSet);
+ }
+ }
+ map.set(name, storedKind | kind);
+ } else {
+ map.set(name, kind);
+ }
+ }
+
+ properties.push(property);
+
+ if (!match('}')) {
+ expect(',');
+ }
+ }
+
+ expect('}');
+
+ return markerApply(marker, delegate.createObjectExpression(properties));
+ }
+
+ function parseTemplateElement(option) {
+ var marker = markerCreate(),
+ token = scanTemplateElement(option);
+ if (strict && token.octal) {
+ throwError(token, Messages.StrictOctalLiteral);
+ }
+ return markerApply(marker, delegate.createTemplateElement({ raw: token.value.raw, cooked: token.value.cooked }, token.tail));
+ }
+
+ function parseTemplateLiteral() {
+ var quasi, quasis, expressions, marker = markerCreate();
+
+ quasi = parseTemplateElement({ head: true });
+ quasis = [ quasi ];
+ expressions = [];
+
+ while (!quasi.tail) {
+ expressions.push(parseExpression());
+ quasi = parseTemplateElement({ head: false });
+ quasis.push(quasi);
+ }
+
+ return markerApply(marker, delegate.createTemplateLiteral(quasis, expressions));
+ }
+
+ // 11.1.6 The Grouping Operator
+
+ function parseGroupExpression() {
+ var expr;
+
+ expect('(');
+
+ ++state.parenthesizedCount;
+
+ expr = parseExpression();
+
+ expect(')');
+
+ return expr;
+ }
+
+
+ // 11.1 Primary Expressions
+
+ function parsePrimaryExpression() {
+ var marker, type, token, expr;
+
+ type = lookahead.type;
+
+ if (type === Token.Identifier) {
+ marker = markerCreate();
+ return markerApply(marker, delegate.createIdentifier(lex().value));
+ }
+
+ if (type === Token.StringLiteral || type === Token.NumericLiteral) {
+ if (strict && lookahead.octal) {
+ throwErrorTolerant(lookahead, Messages.StrictOctalLiteral);
+ }
+ marker = markerCreate();
+ return markerApply(marker, delegate.createLiteral(lex()));
+ }
+
+ if (type === Token.Keyword) {
+ if (matchKeyword('this')) {
+ marker = markerCreate();
+ lex();
+ return markerApply(marker, delegate.createThisExpression());
+ }
+
+ if (matchKeyword('function')) {
+ return parseFunctionExpression();
+ }
+
+ if (matchKeyword('class')) {
+ return parseClassExpression();
+ }
+
+ if (matchKeyword('super')) {
+ marker = markerCreate();
+ lex();
+ return markerApply(marker, delegate.createIdentifier('super'));
+ }
+ }
+
+ if (type === Token.BooleanLiteral) {
+ marker = markerCreate();
+ token = lex();
+ token.value = (token.value === 'true');
+ return markerApply(marker, delegate.createLiteral(token));
+ }
+
+ if (type === Token.NullLiteral) {
+ marker = markerCreate();
+ token = lex();
+ token.value = null;
+ return markerApply(marker, delegate.createLiteral(token));
+ }
+
+ if (match('[')) {
+ return parseArrayInitialiser();
+ }
+
+ if (match('{')) {
+ return parseObjectInitialiser();
+ }
+
+ if (match('(')) {
+ return parseGroupExpression();
+ }
+
+ if (match('/') || match('/=')) {
+ marker = markerCreate();
+ expr = delegate.createLiteral(scanRegExp());
+ peek();
+ return markerApply(marker, expr);
+ }
+
+ if (type === Token.Template) {
+ return parseTemplateLiteral();
+ }
+
+ throwUnexpected(lex());
+ }
+
+ // 11.2 Left-Hand-Side Expressions
+
+ function parseArguments() {
+ var args = [], arg;
+
+ expect('(');
+
+ if (!match(')')) {
+ while (index < length) {
+ arg = parseSpreadOrAssignmentExpression();
+ args.push(arg);
+
+ if (match(')')) {
+ break;
+ } else if (arg.type === Syntax.SpreadElement) {
+ throwError({}, Messages.ElementAfterSpreadElement);
+ }
+
+ expect(',');
+ }
+ }
+
+ expect(')');
+
+ return args;
+ }
+
+ function parseSpreadOrAssignmentExpression() {
+ if (match('...')) {
+ var marker = markerCreate();
+ lex();
+ return markerApply(marker, delegate.createSpreadElement(parseAssignmentExpression()));
+ }
+ return parseAssignmentExpression();
+ }
+
+ function parseNonComputedProperty() {
+ var marker = markerCreate(),
+ token = lex();
+
+ if (!isIdentifierName(token)) {
+ throwUnexpected(token);
+ }
+
+ return markerApply(marker, delegate.createIdentifier(token.value));
+ }
+
+ function parseNonComputedMember() {
+ expect('.');
+
+ return parseNonComputedProperty();
+ }
+
+ function parseComputedMember() {
+ var expr;
+
+ expect('[');
+
+ expr = parseExpression();
+
+ expect(']');
+
+ return expr;
+ }
+
+ function parseNewExpression() {
+ var callee, args, marker = markerCreate();
+
+ expectKeyword('new');
+ callee = parseLeftHandSideExpression();
+ args = match('(') ? parseArguments() : [];
+
+ return markerApply(marker, delegate.createNewExpression(callee, args));
+ }
+
+ function parseLeftHandSideExpressionAllowCall() {
+ var expr, args, marker = markerCreate();
+
+ expr = matchKeyword('new') ? parseNewExpression() : parsePrimaryExpression();
+
+ while (match('.') || match('[') || match('(') || lookahead.type === Token.Template) {
+ if (match('(')) {
+ args = parseArguments();
+ expr = markerApply(marker, delegate.createCallExpression(expr, args));
+ } else if (match('[')) {
+ expr = markerApply(marker, delegate.createMemberExpression('[', expr, parseComputedMember()));
+ } else if (match('.')) {
+ expr = markerApply(marker, delegate.createMemberExpression('.', expr, parseNonComputedMember()));
+ } else {
+ expr = markerApply(marker, delegate.createTaggedTemplateExpression(expr, parseTemplateLiteral()));
+ }
+ }
+
+ return expr;
+ }
+
+ function parseLeftHandSideExpression() {
+ var expr, marker = markerCreate();
+
+ expr = matchKeyword('new') ? parseNewExpression() : parsePrimaryExpression();
+
+ while (match('.') || match('[') || lookahead.type === Token.Template) {
+ if (match('[')) {
+ expr = markerApply(marker, delegate.createMemberExpression('[', expr, parseComputedMember()));
+ } else if (match('.')) {
+ expr = markerApply(marker, delegate.createMemberExpression('.', expr, parseNonComputedMember()));
+ } else {
+ expr = markerApply(marker, delegate.createTaggedTemplateExpression(expr, parseTemplateLiteral()));
+ }
+ }
+
+ return expr;
+ }
+
+ // 11.3 Postfix Expressions
+
+ function parsePostfixExpression() {
+ var marker = markerCreate(),
+ expr = parseLeftHandSideExpressionAllowCall(),
+ token;
+
+ if (lookahead.type !== Token.Punctuator) {
+ return expr;
+ }
+
+ if ((match('++') || match('--')) && !peekLineTerminator()) {
+ // 11.3.1, 11.3.2
+ if (strict && expr.type === Syntax.Identifier && isRestrictedWord(expr.name)) {
+ throwErrorTolerant({}, Messages.StrictLHSPostfix);
+ }
+
+ if (!isLeftHandSide(expr)) {
+ throwError({}, Messages.InvalidLHSInAssignment);
+ }
+
+ token = lex();
+ expr = markerApply(marker, delegate.createPostfixExpression(token.value, expr));
+ }
+
+ return expr;
+ }
+
+ // 11.4 Unary Operators
+
+ function parseUnaryExpression() {
+ var marker, token, expr;
+
+ if (lookahead.type !== Token.Punctuator && lookahead.type !== Token.Keyword) {
+ return parsePostfixExpression();
+ }
+
+ if (match('++') || match('--')) {
+ marker = markerCreate();
+ token = lex();
+ expr = parseUnaryExpression();
+ // 11.4.4, 11.4.5
+ if (strict && expr.type === Syntax.Identifier && isRestrictedWord(expr.name)) {
+ throwErrorTolerant({}, Messages.StrictLHSPrefix);
+ }
+
+ if (!isLeftHandSide(expr)) {
+ throwError({}, Messages.InvalidLHSInAssignment);
+ }
+
+ return markerApply(marker, delegate.createUnaryExpression(token.value, expr));
+ }
+
+ if (match('+') || match('-') || match('~') || match('!')) {
+ marker = markerCreate();
+ token = lex();
+ expr = parseUnaryExpression();
+ return markerApply(marker, delegate.createUnaryExpression(token.value, expr));
+ }
+
+ if (matchKeyword('delete') || matchKeyword('void') || matchKeyword('typeof')) {
+ marker = markerCreate();
+ token = lex();
+ expr = parseUnaryExpression();
+ expr = markerApply(marker, delegate.createUnaryExpression(token.value, expr));
+ if (strict && expr.operator === 'delete' && expr.argument.type === Syntax.Identifier) {
+ throwErrorTolerant({}, Messages.StrictDelete);
+ }
+ return expr;
+ }
+
+ return parsePostfixExpression();
+ }
+
+ function binaryPrecedence(token, allowIn) {
+ var prec = 0;
+
+ if (token.type !== Token.Punctuator && token.type !== Token.Keyword) {
+ return 0;
+ }
+
+ switch (token.value) {
+ case '||':
+ prec = 1;
+ break;
+
+ case '&&':
+ prec = 2;
+ break;
+
+ case '|':
+ prec = 3;
+ break;
+
+ case '^':
+ prec = 4;
+ break;
+
+ case '&':
+ prec = 5;
+ break;
+
+ case '==':
+ case '!=':
+ case '===':
+ case '!==':
+ prec = 6;
+ break;
+
+ case '<':
+ case '>':
+ case '<=':
+ case '>=':
+ case 'instanceof':
+ prec = 7;
+ break;
+
+ case 'in':
+ prec = allowIn ? 7 : 0;
+ break;
+
+ case '<<':
+ case '>>':
+ case '>>>':
+ prec = 8;
+ break;
+
+ case '+':
+ case '-':
+ prec = 9;
+ break;
+
+ case '*':
+ case '/':
+ case '%':
+ prec = 11;
+ break;
+
+ default:
+ break;
+ }
+
+ return prec;
+ }
+
+ // 11.5 Multiplicative Operators
+ // 11.6 Additive Operators
+ // 11.7 Bitwise Shift Operators
+ // 11.8 Relational Operators
+ // 11.9 Equality Operators
+ // 11.10 Binary Bitwise Operators
+ // 11.11 Binary Logical Operators
+
+ function parseBinaryExpression() {
+ var expr, token, prec, previousAllowIn, stack, right, operator, left, i,
+ marker, markers;
+
+ previousAllowIn = state.allowIn;
+ state.allowIn = true;
+
+ marker = markerCreate();
+ left = parseUnaryExpression();
+
+ token = lookahead;
+ prec = binaryPrecedence(token, previousAllowIn);
+ if (prec === 0) {
+ return left;
+ }
+ token.prec = prec;
+ lex();
+
+ markers = [marker, markerCreate()];
+ right = parseUnaryExpression();
+
+ stack = [left, token, right];
+
+ while ((prec = binaryPrecedence(lookahead, previousAllowIn)) > 0) {
+
+ // Reduce: make a binary expression from the three topmost entries.
+ while ((stack.length > 2) && (prec <= stack[stack.length - 2].prec)) {
+ right = stack.pop();
+ operator = stack.pop().value;
+ left = stack.pop();
+ expr = delegate.createBinaryExpression(operator, left, right);
+ markers.pop();
+ marker = markers.pop();
+ markerApply(marker, expr);
+ stack.push(expr);
+ markers.push(marker);
+ }
+
+ // Shift.
+ token = lex();
+ token.prec = prec;
+ stack.push(token);
+ markers.push(markerCreate());
+ expr = parseUnaryExpression();
+ stack.push(expr);
+ }
+
+ state.allowIn = previousAllowIn;
+
+ // Final reduce to clean-up the stack.
+ i = stack.length - 1;
+ expr = stack[i];
+ markers.pop();
+ while (i > 1) {
+ expr = delegate.createBinaryExpression(stack[i - 1].value, stack[i - 2], expr);
+ i -= 2;
+ marker = markers.pop();
+ markerApply(marker, expr);
+ }
+
+ return expr;
+ }
+
+
+ // 11.12 Conditional Operator
+
+ function parseConditionalExpression() {
+ var expr, previousAllowIn, consequent, alternate, marker = markerCreate();
+ expr = parseBinaryExpression();
+
+ if (match('?')) {
+ lex();
+ previousAllowIn = state.allowIn;
+ state.allowIn = true;
+ consequent = parseAssignmentExpression();
+ state.allowIn = previousAllowIn;
+ expect(':');
+ alternate = parseAssignmentExpression();
+
+ expr = markerApply(marker, delegate.createConditionalExpression(expr, consequent, alternate));
+ }
+
+ return expr;
+ }
+
+ // 11.13 Assignment Operators
+
+ // 12.14.5 AssignmentPattern
+
+ function reinterpretAsAssignmentBindingPattern(expr) {
+ var i, len, property, element;
+
+ if (expr.type === Syntax.ObjectExpression) {
+ expr.type = Syntax.ObjectPattern;
+ for (i = 0, len = expr.properties.length; i < len; i += 1) {
+ property = expr.properties[i];
+ if (property.kind !== 'init') {
+ throwError({}, Messages.InvalidLHSInAssignment);
+ }
+ reinterpretAsAssignmentBindingPattern(property.value);
+ }
+ } else if (expr.type === Syntax.ArrayExpression) {
+ expr.type = Syntax.ArrayPattern;
+ for (i = 0, len = expr.elements.length; i < len; i += 1) {
+ element = expr.elements[i];
+ /* istanbul ignore else */
+ if (element) {
+ reinterpretAsAssignmentBindingPattern(element);
+ }
+ }
+ } else if (expr.type === Syntax.Identifier) {
+ if (isRestrictedWord(expr.name)) {
+ throwError({}, Messages.InvalidLHSInAssignment);
+ }
+ } else if (expr.type === Syntax.SpreadElement) {
+ reinterpretAsAssignmentBindingPattern(expr.argument);
+ if (expr.argument.type === Syntax.ObjectPattern) {
+ throwError({}, Messages.ObjectPatternAsSpread);
+ }
+ } else {
+ /* istanbul ignore else */
+ if (expr.type !== Syntax.MemberExpression && expr.type !== Syntax.CallExpression && expr.type !== Syntax.NewExpression) {
+ throwError({}, Messages.InvalidLHSInAssignment);
+ }
+ }
+ }
+
+ // 13.2.3 BindingPattern
+
+ function reinterpretAsDestructuredParameter(options, expr) {
+ var i, len, property, element;
+
+ if (expr.type === Syntax.ObjectExpression) {
+ expr.type = Syntax.ObjectPattern;
+ for (i = 0, len = expr.properties.length; i < len; i += 1) {
+ property = expr.properties[i];
+ if (property.kind !== 'init') {
+ throwError({}, Messages.InvalidLHSInFormalsList);
+ }
+ reinterpretAsDestructuredParameter(options, property.value);
+ }
+ } else if (expr.type === Syntax.ArrayExpression) {
+ expr.type = Syntax.ArrayPattern;
+ for (i = 0, len = expr.elements.length; i < len; i += 1) {
+ element = expr.elements[i];
+ if (element) {
+ reinterpretAsDestructuredParameter(options, element);
+ }
+ }
+ } else if (expr.type === Syntax.Identifier) {
+ validateParam(options, expr, expr.name);
+ } else if (expr.type === Syntax.SpreadElement) {
+ // BindingRestElement only allows BindingIdentifier
+ if (expr.argument.type !== Syntax.Identifier) {
+ throwError({}, Messages.InvalidLHSInFormalsList);
+ }
+ validateParam(options, expr.argument, expr.argument.name);
+ } else {
+ throwError({}, Messages.InvalidLHSInFormalsList);
+ }
+ }
+
+ function reinterpretAsCoverFormalsList(expressions) {
+ var i, len, param, params, defaults, defaultCount, options, rest;
+
+ params = [];
+ defaults = [];
+ defaultCount = 0;
+ rest = null;
+ options = {
+ paramSet: new StringMap()
+ };
+
+ for (i = 0, len = expressions.length; i < len; i += 1) {
+ param = expressions[i];
+ if (param.type === Syntax.Identifier) {
+ params.push(param);
+ defaults.push(null);
+ validateParam(options, param, param.name);
+ } else if (param.type === Syntax.ObjectExpression || param.type === Syntax.ArrayExpression) {
+ reinterpretAsDestructuredParameter(options, param);
+ params.push(param);
+ defaults.push(null);
+ } else if (param.type === Syntax.SpreadElement) {
+ assert(i === len - 1, 'It is guaranteed that SpreadElement is last element by parseExpression');
+ if (param.argument.type !== Syntax.Identifier) {
+ throwError({}, Messages.InvalidLHSInFormalsList);
+ }
+ reinterpretAsDestructuredParameter(options, param.argument);
+ rest = param.argument;
+ } else if (param.type === Syntax.AssignmentExpression) {
+ params.push(param.left);
+ defaults.push(param.right);
+ ++defaultCount;
+ validateParam(options, param.left, param.left.name);
+ } else {
+ return null;
+ }
+ }
+
+ if (options.message === Messages.StrictParamDupe) {
+ throwError(
+ strict ? options.stricted : options.firstRestricted,
+ options.message
+ );
+ }
+
+ if (defaultCount === 0) {
+ defaults = [];
+ }
+
+ return {
+ params: params,
+ defaults: defaults,
+ rest: rest,
+ stricted: options.stricted,
+ firstRestricted: options.firstRestricted,
+ message: options.message
+ };
+ }
+
+ function parseArrowFunctionExpression(options, marker) {
+ var previousStrict, previousYieldAllowed, body;
+
+ expect('=>');
+
+ previousStrict = strict;
+ previousYieldAllowed = state.yieldAllowed;
+ state.yieldAllowed = false;
+ body = parseConciseBody();
+
+ if (strict && options.firstRestricted) {
+ throwError(options.firstRestricted, options.message);
+ }
+ if (strict && options.stricted) {
+ throwErrorTolerant(options.stricted, options.message);
+ }
+
+ strict = previousStrict;
+ state.yieldAllowed = previousYieldAllowed;
+
+ return markerApply(marker, delegate.createArrowFunctionExpression(
+ options.params,
+ options.defaults,
+ body,
+ options.rest,
+ body.type !== Syntax.BlockStatement
+ ));
+ }
+
+ function parseAssignmentExpression() {
+ var marker, expr, token, params, oldParenthesizedCount,
+ startsWithParen = false;
+
+ // Note that 'yield' is treated as a keyword in strict mode, but a
+ // contextual keyword (identifier) in non-strict mode, so we need
+ // to use matchKeyword and matchContextualKeyword appropriately.
+ if ((state.yieldAllowed && matchContextualKeyword('yield')) || (strict && matchKeyword('yield'))) {
+ return parseYieldExpression();
+ }
+
+ oldParenthesizedCount = state.parenthesizedCount;
+
+ marker = markerCreate();
+
+ if (match('(')) {
+ token = lookahead2();
+ if ((token.type === Token.Punctuator && token.value === ')') || token.value === '...') {
+ params = parseParams();
+ if (!match('=>')) {
+ throwUnexpected(lex());
+ }
+ return parseArrowFunctionExpression(params, marker);
+ }
+ startsWithParen = true;
+ }
+
+ token = lookahead;
+ expr = parseConditionalExpression();
+
+ if (match('=>') &&
+ (state.parenthesizedCount === oldParenthesizedCount ||
+ state.parenthesizedCount === (oldParenthesizedCount + 1))) {
+ if (expr.type === Syntax.Identifier) {
+ params = reinterpretAsCoverFormalsList([ expr ]);
+ } else if (expr.type === Syntax.AssignmentExpression ||
+ expr.type === Syntax.ArrayExpression ||
+ expr.type === Syntax.ObjectExpression) {
+ if (!startsWithParen) {
+ throwUnexpected(lex());
+ }
+ params = reinterpretAsCoverFormalsList([ expr ]);
+ } else if (expr.type === Syntax.SequenceExpression) {
+ params = reinterpretAsCoverFormalsList(expr.expressions);
+ }
+ if (params) {
+ return parseArrowFunctionExpression(params, marker);
+ }
+ }
+
+ if (matchAssign()) {
+ // 11.13.1
+ if (strict && expr.type === Syntax.Identifier && isRestrictedWord(expr.name)) {
+ throwErrorTolerant(token, Messages.StrictLHSAssignment);
+ }
+
+ // ES.next draf 11.13 Runtime Semantics step 1
+ if (match('=') && (expr.type === Syntax.ObjectExpression || expr.type === Syntax.ArrayExpression)) {
+ reinterpretAsAssignmentBindingPattern(expr);
+ } else if (!isLeftHandSide(expr)) {
+ throwError({}, Messages.InvalidLHSInAssignment);
+ }
+
+ expr = markerApply(marker, delegate.createAssignmentExpression(lex().value, expr, parseAssignmentExpression()));
+ }
+
+ return expr;
+ }
+
+ // 11.14 Comma Operator
+
+ function parseExpression() {
+ var marker, expr, expressions, sequence, spreadFound;
+
+ marker = markerCreate();
+ expr = parseAssignmentExpression();
+ expressions = [ expr ];
+
+ if (match(',')) {
+ while (index < length) {
+ if (!match(',')) {
+ break;
+ }
+
+ lex();
+ expr = parseSpreadOrAssignmentExpression();
+ expressions.push(expr);
+
+ if (expr.type === Syntax.SpreadElement) {
+ spreadFound = true;
+ if (!match(')')) {
+ throwError({}, Messages.ElementAfterSpreadElement);
+ }
+ break;
+ }
+ }
+
+ sequence = markerApply(marker, delegate.createSequenceExpression(expressions));
+ }
+
+ if (spreadFound && lookahead2().value !== '=>') {
+ throwError({}, Messages.IllegalSpread);
+ }
+
+ return sequence || expr;
+ }
+
+ // 12.1 Block
+
+ function parseStatementList() {
+ var list = [],
+ statement;
+
+ while (index < length) {
+ if (match('}')) {
+ break;
+ }
+ statement = parseSourceElement();
+ if (typeof statement === 'undefined') {
+ break;
+ }
+ list.push(statement);
+ }
+
+ return list;
+ }
+
+ function parseBlock() {
+ var block, marker = markerCreate();
+
+ expect('{');
+
+ block = parseStatementList();
+
+ expect('}');
+
+ return markerApply(marker, delegate.createBlockStatement(block));
+ }
+
+ // 12.2 Variable Statement
+
+ function parseVariableIdentifier() {
+ var marker = markerCreate(),
+ token = lex();
+
+ if (token.type !== Token.Identifier) {
+ throwUnexpected(token);
+ }
+
+ return markerApply(marker, delegate.createIdentifier(token.value));
+ }
+
+ function parseVariableDeclaration(kind) {
+ var id,
+ marker = markerCreate(),
+ init = null;
+ if (match('{')) {
+ id = parseObjectInitialiser();
+ reinterpretAsAssignmentBindingPattern(id);
+ } else if (match('[')) {
+ id = parseArrayInitialiser();
+ reinterpretAsAssignmentBindingPattern(id);
+ } else {
+ /* istanbul ignore next */
+ id = state.allowKeyword ? parseNonComputedProperty() : parseVariableIdentifier();
+ // 12.2.1
+ if (strict && isRestrictedWord(id.name)) {
+ throwErrorTolerant({}, Messages.StrictVarName);
+ }
+ }
+
+ if (kind === 'const') {
+ if (!match('=')) {
+ throwError({}, Messages.NoUninitializedConst);
+ }
+ expect('=');
+ init = parseAssignmentExpression();
+ } else if (match('=')) {
+ lex();
+ init = parseAssignmentExpression();
+ }
+
+ return markerApply(marker, delegate.createVariableDeclarator(id, init));
+ }
+
+ function parseVariableDeclarationList(kind) {
+ var list = [];
+
+ do {
+ list.push(parseVariableDeclaration(kind));
+ if (!match(',')) {
+ break;
+ }
+ lex();
+ } while (index < length);
+
+ return list;
+ }
+
+ function parseVariableStatement() {
+ var declarations, marker = markerCreate();
+
+ expectKeyword('var');
+
+ declarations = parseVariableDeclarationList();
+
+ consumeSemicolon();
+
+ return markerApply(marker, delegate.createVariableDeclaration(declarations, 'var'));
+ }
+
+ // kind may be `const` or `let`
+ // Both are experimental and not in the specification yet.
+ // see http://wiki.ecmascript.org/doku.php?id=harmony:const
+ // and http://wiki.ecmascript.org/doku.php?id=harmony:let
+ function parseConstLetDeclaration(kind) {
+ var declarations, marker = markerCreate();
+
+ expectKeyword(kind);
+
+ declarations = parseVariableDeclarationList(kind);
+
+ consumeSemicolon();
+
+ return markerApply(marker, delegate.createVariableDeclaration(declarations, kind));
+ }
+
+ // people.mozilla.org/~jorendorff/es6-draft.html
+
+ function parseModuleSpecifier() {
+ var marker = markerCreate(),
+ specifier;
+
+ if (lookahead.type !== Token.StringLiteral) {
+ throwError({}, Messages.InvalidModuleSpecifier);
+ }
+ specifier = delegate.createLiteral(lex());
+ return markerApply(marker, specifier);
+ }
+
+ function parseExportBatchSpecifier() {
+ var marker = markerCreate();
+ expect('*');
+ return markerApply(marker, delegate.createExportBatchSpecifier());
+ }
+
+ function parseExportSpecifier() {
+ var id, name = null, marker = markerCreate();
+ if (matchKeyword('default')) {
+ lex();
+ id = markerApply(marker, delegate.createIdentifier('default'));
+ // export {default} from "something";
+ } else {
+ id = parseVariableIdentifier();
+ }
+ if (matchContextualKeyword('as')) {
+ lex();
+ name = parseNonComputedProperty();
+ }
+
+ return markerApply(marker, delegate.createExportSpecifier(id, name));
+ }
+
+ function parseExportDeclaration() {
+ var declaration = null,
+ possibleIdentifierToken, sourceElement,
+ isExportFromIdentifier,
+ src = null, specifiers = [],
+ marker = markerCreate();
+
+ expectKeyword('export');
+
+ if (matchKeyword('default')) {
+ // covers:
+ // export default ...
+ lex();
+ if (matchKeyword('function') || matchKeyword('class')) {
+ possibleIdentifierToken = lookahead2();
+ if (isIdentifierName(possibleIdentifierToken)) {
+ // covers:
+ // export default function foo () {}
+ // export default class foo {}
+ sourceElement = parseSourceElement();
+ return markerApply(marker, delegate.createExportDeclaration(true, sourceElement, [sourceElement.id], null));
+ }
+ // covers:
+ // export default function () {}
+ // export default class {}
+ switch (lookahead.value) {
+ case 'class':
+ return markerApply(marker, delegate.createExportDeclaration(true, parseClassExpression(), [], null));
+ case 'function':
+ return markerApply(marker, delegate.createExportDeclaration(true, parseFunctionExpression(), [], null));
+ }
+ }
+
+ if (matchContextualKeyword('from')) {
+ throwError({}, Messages.UnexpectedToken, lookahead.value);
+ }
+
+ // covers:
+ // export default {};
+ // export default [];
+ if (match('{')) {
+ declaration = parseObjectInitialiser();
+ } else if (match('[')) {
+ declaration = parseArrayInitialiser();
+ } else {
+ declaration = parseAssignmentExpression();
+ }
+ consumeSemicolon();
+ return markerApply(marker, delegate.createExportDeclaration(true, declaration, [], null));
+ }
+
+ // non-default export
+ if (lookahead.type === Token.Keyword) {
+ // covers:
+ // export var f = 1;
+ switch (lookahead.value) {
+ case 'let':
+ case 'const':
+ case 'var':
+ case 'class':
+ case 'function':
+ return markerApply(marker, delegate.createExportDeclaration(false, parseSourceElement(), specifiers, null));
+ }
+ }
+
+ if (match('*')) {
+ // covers:
+ // export * from "foo";
+ specifiers.push(parseExportBatchSpecifier());
+
+ if (!matchContextualKeyword('from')) {
+ throwError({}, lookahead.value ?
+ Messages.UnexpectedToken : Messages.MissingFromClause, lookahead.value);
+ }
+ lex();
+ src = parseModuleSpecifier();
+ consumeSemicolon();
+
+ return markerApply(marker, delegate.createExportDeclaration(false, null, specifiers, src));
+ }
+
+ expect('{');
+ if (!match('}')) {
+ do {
+ isExportFromIdentifier = isExportFromIdentifier || matchKeyword('default');
+ specifiers.push(parseExportSpecifier());
+ } while (match(',') && lex());
+ }
+ expect('}');
+
+ if (matchContextualKeyword('from')) {
+ // covering:
+ // export {default} from "foo";
+ // export {foo} from "foo";
+ lex();
+ src = parseModuleSpecifier();
+ consumeSemicolon();
+ } else if (isExportFromIdentifier) {
+ // covering:
+ // export {default}; // missing fromClause
+ throwError({}, lookahead.value ?
+ Messages.UnexpectedToken : Messages.MissingFromClause, lookahead.value);
+ } else {
+ // cover
+ // export {foo};
+ consumeSemicolon();
+ }
+ return markerApply(marker, delegate.createExportDeclaration(false, declaration, specifiers, src));
+ }
+
+
+ function parseImportSpecifier() {
+ // import {<foo as bar>} ...;
+ var id, name = null, marker = markerCreate();
+
+ id = parseNonComputedProperty();
+ if (matchContextualKeyword('as')) {
+ lex();
+ name = parseVariableIdentifier();
+ }
+
+ return markerApply(marker, delegate.createImportSpecifier(id, name));
+ }
+
+ function parseNamedImports() {
+ var specifiers = [];
+ // {foo, bar as bas}
+ expect('{');
+ if (!match('}')) {
+ do {
+ specifiers.push(parseImportSpecifier());
+ } while (match(',') && lex());
+ }
+ expect('}');
+ return specifiers;
+ }
+
+ function parseImportDefaultSpecifier() {
+ // import <foo> ...;
+ var id, marker = markerCreate();
+
+ id = parseNonComputedProperty();
+
+ return markerApply(marker, delegate.createImportDefaultSpecifier(id));
+ }
+
+ function parseImportNamespaceSpecifier() {
+ // import <* as foo> ...;
+ var id, marker = markerCreate();
+
+ expect('*');
+ if (!matchContextualKeyword('as')) {
+ throwError({}, Messages.NoAsAfterImportNamespace);
+ }
+ lex();
+ id = parseNonComputedProperty();
+
+ return markerApply(marker, delegate.createImportNamespaceSpecifier(id));
+ }
+
+ function parseImportDeclaration() {
+ var specifiers, src, marker = markerCreate();
+
+ expectKeyword('import');
+ specifiers = [];
+
+ if (lookahead.type === Token.StringLiteral) {
+ // covers:
+ // import "foo";
+ src = parseModuleSpecifier();
+ consumeSemicolon();
+ return markerApply(marker, delegate.createImportDeclaration(specifiers, src));
+ }
+
+ if (!matchKeyword('default') && isIdentifierName(lookahead)) {
+ // covers:
+ // import foo
+ // import foo, ...
+ specifiers.push(parseImportDefaultSpecifier());
+ if (match(',')) {
+ lex();
+ }
+ }
+ if (match('*')) {
+ // covers:
+ // import foo, * as foo
+ // import * as foo
+ specifiers.push(parseImportNamespaceSpecifier());
+ } else if (match('{')) {
+ // covers:
+ // import foo, {bar}
+ // import {bar}
+ specifiers = specifiers.concat(parseNamedImports());
+ }
+
+ if (!matchContextualKeyword('from')) {
+ throwError({}, lookahead.value ?
+ Messages.UnexpectedToken : Messages.MissingFromClause, lookahead.value);
+ }
+ lex();
+ src = parseModuleSpecifier();
+ consumeSemicolon();
+
+ return markerApply(marker, delegate.createImportDeclaration(specifiers, src));
+ }
+
+ // 12.3 Empty Statement
+
+ function parseEmptyStatement() {
+ var marker = markerCreate();
+ expect(';');
+ return markerApply(marker, delegate.createEmptyStatement());
+ }
+
+ // 12.4 Expression Statement
+
+ function parseExpressionStatement() {
+ var marker = markerCreate(), expr = parseExpression();
+ consumeSemicolon();
+ return markerApply(marker, delegate.createExpressionStatement(expr));
+ }
+
+ // 12.5 If statement
+
+ function parseIfStatement() {
+ var test, consequent, alternate, marker = markerCreate();
+
+ expectKeyword('if');
+
+ expect('(');
+
+ test = parseExpression();
+
+ expect(')');
+
+ consequent = parseStatement();
+
+ if (matchKeyword('else')) {
+ lex();
+ alternate = parseStatement();
+ } else {
+ alternate = null;
+ }
+
+ return markerApply(marker, delegate.createIfStatement(test, consequent, alternate));
+ }
+
+ // 12.6 Iteration Statements
+
+ function parseDoWhileStatement() {
+ var body, test, oldInIteration, marker = markerCreate();
+
+ expectKeyword('do');
+
+ oldInIteration = state.inIteration;
+ state.inIteration = true;
+
+ body = parseStatement();
+
+ state.inIteration = oldInIteration;
+
+ expectKeyword('while');
+
+ expect('(');
+
+ test = parseExpression();
+
+ expect(')');
+
+ if (match(';')) {
+ lex();
+ }
+
+ return markerApply(marker, delegate.createDoWhileStatement(body, test));
+ }
+
+ function parseWhileStatement() {
+ var test, body, oldInIteration, marker = markerCreate();
+
+ expectKeyword('while');
+
+ expect('(');
+
+ test = parseExpression();
+
+ expect(')');
+
+ oldInIteration = state.inIteration;
+ state.inIteration = true;
+
+ body = parseStatement();
+
+ state.inIteration = oldInIteration;
+
+ return markerApply(marker, delegate.createWhileStatement(test, body));
+ }
+
+ function parseForVariableDeclaration() {
+ var marker = markerCreate(),
+ token = lex(),
+ declarations = parseVariableDeclarationList();
+
+ return markerApply(marker, delegate.createVariableDeclaration(declarations, token.value));
+ }
+
+ function parseForStatement(opts) {
+ var init, test, update, left, right, body, operator, oldInIteration,
+ marker = markerCreate();
+ init = test = update = null;
+ expectKeyword('for');
+
+ // http://wiki.ecmascript.org/doku.php?id=proposals:iterators_and_generators&s=each
+ if (matchContextualKeyword('each')) {
+ throwError({}, Messages.EachNotAllowed);
+ }
+
+ expect('(');
+
+ if (match(';')) {
+ lex();
+ } else {
+ if (matchKeyword('var') || matchKeyword('let') || matchKeyword('const')) {
+ state.allowIn = false;
+ init = parseForVariableDeclaration();
+ state.allowIn = true;
+
+ if (init.declarations.length === 1) {
+ if (matchKeyword('in') || matchContextualKeyword('of')) {
+ operator = lookahead;
+ if (!((operator.value === 'in' || init.kind !== 'var') && init.declarations[0].init)) {
+ lex();
+ left = init;
+ right = parseExpression();
+ init = null;
+ }
+ }
+ }
+ } else {
+ state.allowIn = false;
+ init = parseExpression();
+ state.allowIn = true;
+
+ if (matchContextualKeyword('of')) {
+ operator = lex();
+ left = init;
+ right = parseExpression();
+ init = null;
+ } else if (matchKeyword('in')) {
+ // LeftHandSideExpression
+ if (!isAssignableLeftHandSide(init)) {
+ throwError({}, Messages.InvalidLHSInForIn);
+ }
+ operator = lex();
+ left = init;
+ right = parseExpression();
+ init = null;
+ }
+ }
+
+ if (typeof left === 'undefined') {
+ expect(';');
+ }
+ }
+
+ if (typeof left === 'undefined') {
+
+ if (!match(';')) {
+ test = parseExpression();
+ }
+ expect(';');
+
+ if (!match(')')) {
+ update = parseExpression();
+ }
+ }
+
+ expect(')');
+
+ oldInIteration = state.inIteration;
+ state.inIteration = true;
+
+ if (!(opts !== undefined && opts.ignoreBody)) {
+ body = parseStatement();
+ }
+
+ state.inIteration = oldInIteration;
+
+ if (typeof left === 'undefined') {
+ return markerApply(marker, delegate.createForStatement(init, test, update, body));
+ }
+
+ if (operator.value === 'in') {
+ return markerApply(marker, delegate.createForInStatement(left, right, body));
+ }
+ return markerApply(marker, delegate.createForOfStatement(left, right, body));
+ }
+
+ // 12.7 The continue statement
+
+ function parseContinueStatement() {
+ var label = null, marker = markerCreate();
+
+ expectKeyword('continue');
+
+ // Optimize the most common form: 'continue;'.
+ if (source.charCodeAt(index) === 59) {
+ lex();
+
+ if (!state.inIteration) {
+ throwError({}, Messages.IllegalContinue);
+ }
+
+ return markerApply(marker, delegate.createContinueStatement(null));
+ }
+
+ if (peekLineTerminator()) {
+ if (!state.inIteration) {
+ throwError({}, Messages.IllegalContinue);
+ }
+
+ return markerApply(marker, delegate.createContinueStatement(null));
+ }
+
+ if (lookahead.type === Token.Identifier) {
+ label = parseVariableIdentifier();
+
+ if (!state.labelSet.has(label.name)) {
+ throwError({}, Messages.UnknownLabel, label.name);
+ }
+ }
+
+ consumeSemicolon();
+
+ if (label === null && !state.inIteration) {
+ throwError({}, Messages.IllegalContinue);
+ }
+
+ return markerApply(marker, delegate.createContinueStatement(label));
+ }
+
+ // 12.8 The break statement
+
+ function parseBreakStatement() {
+ var label = null, marker = markerCreate();
+
+ expectKeyword('break');
+
+ // Catch the very common case first: immediately a semicolon (char #59).
+ if (source.charCodeAt(index) === 59) {
+ lex();
+
+ if (!(state.inIteration || state.inSwitch)) {
+ throwError({}, Messages.IllegalBreak);
+ }
+
+ return markerApply(marker, delegate.createBreakStatement(null));
+ }
+
+ if (peekLineTerminator()) {
+ if (!(state.inIteration || state.inSwitch)) {
+ throwError({}, Messages.IllegalBreak);
+ }
+
+ return markerApply(marker, delegate.createBreakStatement(null));
+ }
+
+ if (lookahead.type === Token.Identifier) {
+ label = parseVariableIdentifier();
+
+ if (!state.labelSet.has(label.name)) {
+ throwError({}, Messages.UnknownLabel, label.name);
+ }
+ }
+
+ consumeSemicolon();
+
+ if (label === null && !(state.inIteration || state.inSwitch)) {
+ throwError({}, Messages.IllegalBreak);
+ }
+
+ return markerApply(marker, delegate.createBreakStatement(label));
+ }
+
+ // 12.9 The return statement
+
+ function parseReturnStatement() {
+ var argument = null, marker = markerCreate();
+
+ expectKeyword('return');
+
+ if (!state.inFunctionBody) {
+ throwErrorTolerant({}, Messages.IllegalReturn);
+ }
+
+ // 'return' followed by a space and an identifier is very common.
+ if (source.charCodeAt(index) === 32) {
+ if (isIdentifierStart(source.charCodeAt(index + 1))) {
+ argument = parseExpression();
+ consumeSemicolon();
+ return markerApply(marker, delegate.createReturnStatement(argument));
+ }
+ }
+
+ if (peekLineTerminator()) {
+ return markerApply(marker, delegate.createReturnStatement(null));
+ }
+
+ if (!match(';')) {
+ if (!match('}') && lookahead.type !== Token.EOF) {
+ argument = parseExpression();
+ }
+ }
+
+ consumeSemicolon();
+
+ return markerApply(marker, delegate.createReturnStatement(argument));
+ }
+
+ // 12.10 The with statement
+
+ function parseWithStatement() {
+ var object, body, marker = markerCreate();
+
+ if (strict) {
+ throwErrorTolerant({}, Messages.StrictModeWith);
+ }
+
+ expectKeyword('with');
+
+ expect('(');
+
+ object = parseExpression();
+
+ expect(')');
+
+ body = parseStatement();
+
+ return markerApply(marker, delegate.createWithStatement(object, body));
+ }
+
+ // 12.10 The swith statement
+
+ function parseSwitchCase() {
+ var test,
+ consequent = [],
+ sourceElement,
+ marker = markerCreate();
+
+ if (matchKeyword('default')) {
+ lex();
+ test = null;
+ } else {
+ expectKeyword('case');
+ test = parseExpression();
+ }
+ expect(':');
+
+ while (index < length) {
+ if (match('}') || matchKeyword('default') || matchKeyword('case')) {
+ break;
+ }
+ sourceElement = parseSourceElement();
+ if (typeof sourceElement === 'undefined') {
+ break;
+ }
+ consequent.push(sourceElement);
+ }
+
+ return markerApply(marker, delegate.createSwitchCase(test, consequent));
+ }
+
+ function parseSwitchStatement() {
+ var discriminant, cases, clause, oldInSwitch, defaultFound, marker = markerCreate();
+
+ expectKeyword('switch');
+
+ expect('(');
+
+ discriminant = parseExpression();
+
+ expect(')');
+
+ expect('{');
+
+ cases = [];
+
+ if (match('}')) {
+ lex();
+ return markerApply(marker, delegate.createSwitchStatement(discriminant, cases));
+ }
+
+ oldInSwitch = state.inSwitch;
+ state.inSwitch = true;
+ defaultFound = false;
+
+ while (index < length) {
+ if (match('}')) {
+ break;
+ }
+ clause = parseSwitchCase();
+ if (clause.test === null) {
+ if (defaultFound) {
+ throwError({}, Messages.MultipleDefaultsInSwitch);
+ }
+ defaultFound = true;
+ }
+ cases.push(clause);
+ }
+
+ state.inSwitch = oldInSwitch;
+
+ expect('}');
+
+ return markerApply(marker, delegate.createSwitchStatement(discriminant, cases));
+ }
+
+ // 12.13 The throw statement
+
+ function parseThrowStatement() {
+ var argument, marker = markerCreate();
+
+ expectKeyword('throw');
+
+ if (peekLineTerminator()) {
+ throwError({}, Messages.NewlineAfterThrow);
+ }
+
+ argument = parseExpression();
+
+ consumeSemicolon();
+
+ return markerApply(marker, delegate.createThrowStatement(argument));
+ }
+
+ // 12.14 The try statement
+
+ function parseCatchClause() {
+ var param, body, marker = markerCreate();
+
+ expectKeyword('catch');
+
+ expect('(');
+ if (match(')')) {
+ throwUnexpected(lookahead);
+ }
+
+ param = parseExpression();
+ // 12.14.1
+ if (strict && param.type === Syntax.Identifier && isRestrictedWord(param.name)) {
+ throwErrorTolerant({}, Messages.StrictCatchVariable);
+ }
+
+ expect(')');
+ body = parseBlock();
+ return markerApply(marker, delegate.createCatchClause(param, body));
+ }
+
+ function parseTryStatement() {
+ var block, handlers = [], finalizer = null, marker = markerCreate();
+
+ expectKeyword('try');
+
+ block = parseBlock();
+
+ if (matchKeyword('catch')) {
+ handlers.push(parseCatchClause());
+ }
+
+ if (matchKeyword('finally')) {
+ lex();
+ finalizer = parseBlock();
+ }
+
+ if (handlers.length === 0 && !finalizer) {
+ throwError({}, Messages.NoCatchOrFinally);
+ }
+
+ return markerApply(marker, delegate.createTryStatement(block, [], handlers, finalizer));
+ }
+
+ // 12.15 The debugger statement
+
+ function parseDebuggerStatement() {
+ var marker = markerCreate();
+ expectKeyword('debugger');
+
+ consumeSemicolon();
+
+ return markerApply(marker, delegate.createDebuggerStatement());
+ }
+
+ // 12 Statements
+
+ function parseStatement() {
+ var type = lookahead.type,
+ marker,
+ expr,
+ labeledBody;
+
+ if (type === Token.EOF) {
+ throwUnexpected(lookahead);
+ }
+
+ if (type === Token.Punctuator) {
+ switch (lookahead.value) {
+ case ';':
+ return parseEmptyStatement();
+ case '{':
+ return parseBlock();
+ case '(':
+ return parseExpressionStatement();
+ default:
+ break;
+ }
+ }
+
+ if (type === Token.Keyword) {
+ switch (lookahead.value) {
+ case 'break':
+ return parseBreakStatement();
+ case 'continue':
+ return parseContinueStatement();
+ case 'debugger':
+ return parseDebuggerStatement();
+ case 'do':
+ return parseDoWhileStatement();
+ case 'for':
+ return parseForStatement();
+ case 'function':
+ return parseFunctionDeclaration();
+ case 'class':
+ return parseClassDeclaration();
+ case 'if':
+ return parseIfStatement();
+ case 'return':
+ return parseReturnStatement();
+ case 'switch':
+ return parseSwitchStatement();
+ case 'throw':
+ return parseThrowStatement();
+ case 'try':
+ return parseTryStatement();
+ case 'var':
+ return parseVariableStatement();
+ case 'while':
+ return parseWhileStatement();
+ case 'with':
+ return parseWithStatement();
+ default:
+ break;
+ }
+ }
+
+ marker = markerCreate();
+ expr = parseExpression();
+
+ // 12.12 Labelled Statements
+ if ((expr.type === Syntax.Identifier) && match(':')) {
+ lex();
+
+ if (state.labelSet.has(expr.name)) {
+ throwError({}, Messages.Redeclaration, 'Label', expr.name);
+ }
+
+ state.labelSet.set(expr.name, true);
+ labeledBody = parseStatement();
+ state.labelSet.delete(expr.name);
+ return markerApply(marker, delegate.createLabeledStatement(expr, labeledBody));
+ }
+
+ consumeSemicolon();
+
+ return markerApply(marker, delegate.createExpressionStatement(expr));
+ }
+
+ // 13 Function Definition
+
+ function parseConciseBody() {
+ if (match('{')) {
+ return parseFunctionSourceElements();
+ }
+ return parseAssignmentExpression();
+ }
+
+ function parseFunctionSourceElements() {
+ var sourceElement, sourceElements = [], token, directive, firstRestricted,
+ oldLabelSet, oldInIteration, oldInSwitch, oldInFunctionBody, oldParenthesizedCount,
+ marker = markerCreate();
+
+ expect('{');
+
+ while (index < length) {
+ if (lookahead.type !== Token.StringLiteral) {
+ break;
+ }
+ token = lookahead;
+
+ sourceElement = parseSourceElement();
+ sourceElements.push(sourceElement);
+ if (sourceElement.expression.type !== Syntax.Literal) {
+ // this is not directive
+ break;
+ }
+ directive = source.slice(token.range[0] + 1, token.range[1] - 1);
+ if (directive === 'use strict') {
+ strict = true;
+ if (firstRestricted) {
+ throwErrorTolerant(firstRestricted, Messages.StrictOctalLiteral);
+ }
+ } else {
+ if (!firstRestricted && token.octal) {
+ firstRestricted = token;
+ }
+ }
+ }
+
+ oldLabelSet = state.labelSet;
+ oldInIteration = state.inIteration;
+ oldInSwitch = state.inSwitch;
+ oldInFunctionBody = state.inFunctionBody;
+ oldParenthesizedCount = state.parenthesizedCount;
+
+ state.labelSet = new StringMap();
+ state.inIteration = false;
+ state.inSwitch = false;
+ state.inFunctionBody = true;
+ state.parenthesizedCount = 0;
+
+ while (index < length) {
+ if (match('}')) {
+ break;
+ }
+ sourceElement = parseSourceElement();
+ if (typeof sourceElement === 'undefined') {
+ break;
+ }
+ sourceElements.push(sourceElement);
+ }
+
+ expect('}');
+
+ state.labelSet = oldLabelSet;
+ state.inIteration = oldInIteration;
+ state.inSwitch = oldInSwitch;
+ state.inFunctionBody = oldInFunctionBody;
+ state.parenthesizedCount = oldParenthesizedCount;
+
+ return markerApply(marker, delegate.createBlockStatement(sourceElements));
+ }
+
+ function validateParam(options, param, name) {
+ if (strict) {
+ if (isRestrictedWord(name)) {
+ options.stricted = param;
+ options.message = Messages.StrictParamName;
+ }
+ if (options.paramSet.has(name)) {
+ options.stricted = param;
+ options.message = Messages.StrictParamDupe;
+ }
+ } else if (!options.firstRestricted) {
+ if (isRestrictedWord(name)) {
+ options.firstRestricted = param;
+ options.message = Messages.StrictParamName;
+ } else if (isStrictModeReservedWord(name)) {
+ options.firstRestricted = param;
+ options.message = Messages.StrictReservedWord;
+ } else if (options.paramSet.has(name)) {
+ options.firstRestricted = param;
+ options.message = Messages.StrictParamDupe;
+ }
+ }
+ options.paramSet.set(name, true);
+ }
+
+ function parseParam(options) {
+ var token, rest, param, def;
+
+ token = lookahead;
+ if (token.value === '...') {
+ token = lex();
+ rest = true;
+ }
+
+ if (match('[')) {
+ param = parseArrayInitialiser();
+ reinterpretAsDestructuredParameter(options, param);
+ } else if (match('{')) {
+ if (rest) {
+ throwError({}, Messages.ObjectPatternAsRestParameter);
+ }
+ param = parseObjectInitialiser();
+ reinterpretAsDestructuredParameter(options, param);
+ } else {
+ param = parseVariableIdentifier();
+ validateParam(options, token, token.value);
+ }
+
+ if (match('=')) {
+ if (rest) {
+ throwErrorTolerant(lookahead, Messages.DefaultRestParameter);
+ }
+ lex();
+ def = parseAssignmentExpression();
+ ++options.defaultCount;
+ }
+
+ if (rest) {
+ if (!match(')')) {
+ throwError({}, Messages.ParameterAfterRestParameter);
+ }
+ options.rest = param;
+ return false;
+ }
+
+ options.params.push(param);
+ options.defaults.push(def);
+ return !match(')');
+ }
+
+ function parseParams(firstRestricted) {
+ var options, marker = markerCreate();
+
+ options = {
+ params: [],
+ defaultCount: 0,
+ defaults: [],
+ rest: null,
+ firstRestricted: firstRestricted
+ };
+
+ expect('(');
+
+ if (!match(')')) {
+ options.paramSet = new StringMap();
+ while (index < length) {
+ if (!parseParam(options)) {
+ break;
+ }
+ expect(',');
+ }
+ }
+
+ expect(')');
+
+ if (options.defaultCount === 0) {
+ options.defaults = [];
+ }
+
+ return markerApply(marker, options);
+ }
+
+ function parseFunctionDeclaration() {
+ var id, body, token, tmp, firstRestricted, message, previousStrict, previousYieldAllowed, generator,
+ marker = markerCreate();
+
+ expectKeyword('function');
+
+ generator = false;
+ if (match('*')) {
+ lex();
+ generator = true;
+ }
+
+ token = lookahead;
+
+ id = parseVariableIdentifier();
+
+ if (strict) {
+ if (isRestrictedWord(token.value)) {
+ throwErrorTolerant(token, Messages.StrictFunctionName);
+ }
+ } else {
+ if (isRestrictedWord(token.value)) {
+ firstRestricted = token;
+ message = Messages.StrictFunctionName;
+ } else if (isStrictModeReservedWord(token.value)) {
+ firstRestricted = token;
+ message = Messages.StrictReservedWord;
+ }
+ }
+
+ tmp = parseParams(firstRestricted);
+ firstRestricted = tmp.firstRestricted;
+ if (tmp.message) {
+ message = tmp.message;
+ }
+
+ previousStrict = strict;
+ previousYieldAllowed = state.yieldAllowed;
+ state.yieldAllowed = generator;
+
+ body = parseFunctionSourceElements();
+
+ if (strict && firstRestricted) {
+ throwError(firstRestricted, message);
+ }
+ if (strict && tmp.stricted) {
+ throwErrorTolerant(tmp.stricted, message);
+ }
+ strict = previousStrict;
+ state.yieldAllowed = previousYieldAllowed;
+
+ return markerApply(marker, delegate.createFunctionDeclaration(id, tmp.params, tmp.defaults, body, tmp.rest, generator, false));
+ }
+
+ function parseFunctionExpression() {
+ var token, id = null, firstRestricted, message, tmp, body, previousStrict, previousYieldAllowed, generator,
+ marker = markerCreate();
+
+ expectKeyword('function');
+
+ generator = false;
+
+ if (match('*')) {
+ lex();
+ generator = true;
+ }
+
+ if (!match('(')) {
+ token = lookahead;
+ id = parseVariableIdentifier();
+ if (strict) {
+ if (isRestrictedWord(token.value)) {
+ throwErrorTolerant(token, Messages.StrictFunctionName);
+ }
+ } else {
+ if (isRestrictedWord(token.value)) {
+ firstRestricted = token;
+ message = Messages.StrictFunctionName;
+ } else if (isStrictModeReservedWord(token.value)) {
+ firstRestricted = token;
+ message = Messages.StrictReservedWord;
+ }
+ }
+ }
+
+ tmp = parseParams(firstRestricted);
+ firstRestricted = tmp.firstRestricted;
+ if (tmp.message) {
+ message = tmp.message;
+ }
+
+ previousStrict = strict;
+ previousYieldAllowed = state.yieldAllowed;
+ state.yieldAllowed = generator;
+
+ body = parseFunctionSourceElements();
+
+ if (strict && firstRestricted) {
+ throwError(firstRestricted, message);
+ }
+ if (strict && tmp.stricted) {
+ throwErrorTolerant(tmp.stricted, message);
+ }
+ strict = previousStrict;
+ state.yieldAllowed = previousYieldAllowed;
+
+ return markerApply(marker, delegate.createFunctionExpression(id, tmp.params, tmp.defaults, body, tmp.rest, generator, false));
+ }
+
+ function parseYieldExpression() {
+ var yieldToken, delegateFlag, expr, marker = markerCreate();
+
+ yieldToken = lex();
+ assert(yieldToken.value === 'yield', 'Called parseYieldExpression with non-yield lookahead.');
+
+ if (!state.yieldAllowed) {
+ throwErrorTolerant({}, Messages.IllegalYield);
+ }
+
+ delegateFlag = false;
+ if (match('*')) {
+ lex();
+ delegateFlag = true;
+ }
+
+ expr = parseAssignmentExpression();
+
+ return markerApply(marker, delegate.createYieldExpression(expr, delegateFlag));
+ }
+
+ // 14 Functions and classes
+
+ // 14.1 Functions is defined above (13 in ES5)
+ // 14.2 Arrow Functions Definitions is defined in (7.3 assignments)
+
+ // 14.3 Method Definitions
+ // 14.3.7
+ function specialMethod(methodDefinition) {
+ return methodDefinition.kind === 'get' ||
+ methodDefinition.kind === 'set' ||
+ methodDefinition.value.generator;
+ }
+
+ function parseMethodDefinition() {
+ var token, key, param, propType, computed,
+ marker = markerCreate();
+
+ if (lookahead.value === 'static') {
+ propType = ClassPropertyType.static;
+ lex();
+ } else {
+ propType = ClassPropertyType.prototype;
+ }
+
+ if (match('*')) {
+ lex();
+ computed = (lookahead.value === '[');
+ return markerApply(marker, delegate.createMethodDefinition(
+ propType,
+ '',
+ parseObjectPropertyKey(),
+ parsePropertyMethodFunction({ generator: true }),
+ computed
+ ));
+ }
+
+ token = lookahead;
+ key = parseObjectPropertyKey();
+
+ if (token.value === 'get' && !match('(')) {
+ computed = (lookahead.value === '[');
+ key = parseObjectPropertyKey();
+
+ expect('(');
+ expect(')');
+ return markerApply(marker, delegate.createMethodDefinition(
+ propType,
+ 'get',
+ key,
+ parsePropertyFunction({ generator: false }),
+ computed
+ ));
+ }
+ if (token.value === 'set' && !match('(')) {
+ computed = (lookahead.value === '[');
+ key = parseObjectPropertyKey();
+
+ expect('(');
+ token = lookahead;
+ param = [ parseVariableIdentifier() ];
+ expect(')');
+ return markerApply(marker, delegate.createMethodDefinition(
+ propType,
+ 'set',
+ key,
+ parsePropertyFunction({ params: param, generator: false, name: token }),
+ computed
+ ));
+ }
+
+ computed = (token.value === '[');
+
+ return markerApply(marker, delegate.createMethodDefinition(
+ propType,
+ '',
+ key,
+ parsePropertyMethodFunction({ generator: false }),
+ computed
+ ));
+ }
+
+ // 14.5 Class Definitions
+
+ function parseClassElement() {
+ if (match(';')) {
+ lex();
+ } else {
+ return parseMethodDefinition();
+ }
+ }
+
+ function parseClassBody() {
+ var classElement, classElements = [], existingProps = {},
+ marker = markerCreate(), propName, propType;
+
+ existingProps[ClassPropertyType.static] = new StringMap();
+ existingProps[ClassPropertyType.prototype] = new StringMap();
+
+ expect('{');
+
+ while (index < length) {
+ if (match('}')) {
+ break;
+ }
+ classElement = parseClassElement(existingProps);
+
+ if (typeof classElement !== 'undefined') {
+ classElements.push(classElement);
+
+ propName = !classElement.computed && getFieldName(classElement.key);
+ if (propName !== false) {
+ propType = classElement.static ?
+ ClassPropertyType.static :
+ ClassPropertyType.prototype;
+
+ if (propName === 'constructor' && !classElement.static) {
+ if (specialMethod(classElement)) {
+ throwError(classElement, Messages.IllegalClassConstructorProperty);
+ }
+ if (existingProps[ClassPropertyType.prototype].has('constructor')) {
+ throwError(classElement.key, Messages.IllegalDuplicateClassProperty);
+ }
+ }
+ existingProps[propType].set(propName, true);
+ }
+ }
+ }
+
+ expect('}');
+
+ return markerApply(marker, delegate.createClassBody(classElements));
+ }
+
+ function parseClassExpression() {
+ var id, previousYieldAllowed, superClass = null, marker = markerCreate();
+
+ expectKeyword('class');
+
+ if (!matchKeyword('extends') && !match('{')) {
+ id = parseVariableIdentifier();
+ }
+
+ if (matchKeyword('extends')) {
+ expectKeyword('extends');
+ previousYieldAllowed = state.yieldAllowed;
+ state.yieldAllowed = false;
+ superClass = parseAssignmentExpression();
+ state.yieldAllowed = previousYieldAllowed;
+ }
+
+ return markerApply(marker, delegate.createClassExpression(id, superClass, parseClassBody()));
+ }
+
+ function parseClassDeclaration() {
+ var id, previousYieldAllowed, superClass = null, marker = markerCreate();
+
+ expectKeyword('class');
+
+ id = parseVariableIdentifier();
+
+ if (matchKeyword('extends')) {
+ expectKeyword('extends');
+ previousYieldAllowed = state.yieldAllowed;
+ state.yieldAllowed = false;
+ superClass = parseAssignmentExpression();
+ state.yieldAllowed = previousYieldAllowed;
+ }
+
+ return markerApply(marker, delegate.createClassDeclaration(id, superClass, parseClassBody()));
+ }
+
+ // 15 Program
+
+ function parseSourceElement() {
+ if (lookahead.type === Token.Keyword) {
+ switch (lookahead.value) {
+ case 'const':
+ case 'let':
+ return parseConstLetDeclaration(lookahead.value);
+ case 'function':
+ return parseFunctionDeclaration();
+ case 'export':
+ throwErrorTolerant({}, Messages.IllegalExportDeclaration);
+ return parseExportDeclaration();
+ case 'import':
+ throwErrorTolerant({}, Messages.IllegalImportDeclaration);
+ return parseImportDeclaration();
+ default:
+ return parseStatement();
+ }
+ }
+
+ if (lookahead.type !== Token.EOF) {
+ return parseStatement();
+ }
+ }
+
+ function parseProgramElement() {
+ if (extra.isModule && lookahead.type === Token.Keyword) {
+ switch (lookahead.value) {
+ case 'export':
+ return parseExportDeclaration();
+ case 'import':
+ return parseImportDeclaration();
+ }
+ }
+
+ return parseSourceElement();
+ }
+
+ function parseProgramElements() {
+ var sourceElement, sourceElements = [], token, directive, firstRestricted;
+
+ while (index < length) {
+ token = lookahead;
+ if (token.type !== Token.StringLiteral) {
+ break;
+ }
+
+ sourceElement = parseProgramElement();
+ sourceElements.push(sourceElement);
+ if (sourceElement.expression.type !== Syntax.Literal) {
+ // this is not directive
+ break;
+ }
+ directive = source.slice(token.range[0] + 1, token.range[1] - 1);
+ if (directive === 'use strict') {
+ strict = true;
+ if (firstRestricted) {
+ throwErrorTolerant(firstRestricted, Messages.StrictOctalLiteral);
+ }
+ } else {
+ if (!firstRestricted && token.octal) {
+ firstRestricted = token;
+ }
+ }
+ }
+
+ while (index < length) {
+ sourceElement = parseProgramElement();
+ if (typeof sourceElement === 'undefined') {
+ break;
+ }
+ sourceElements.push(sourceElement);
+ }
+ return sourceElements;
+ }
+
+ function parseProgram() {
+ var body, marker = markerCreate();
+ strict = !!extra.isModule;
+ peek();
+ body = parseProgramElements();
+ return markerApply(marker, delegate.createProgram(body));
+ }
+
+ function collectToken() {
+ var loc, token, range, value, entry;
+
+ skipComment();
+ loc = {
+ start: {
+ line: lineNumber,
+ column: index - lineStart
+ }
+ };
+
+ token = extra.advance();
+ loc.end = {
+ line: lineNumber,
+ column: index - lineStart
+ };
+
+ if (token.type !== Token.EOF) {
+ range = [token.range[0], token.range[1]];
+ value = source.slice(token.range[0], token.range[1]);
+ entry = {
+ type: TokenName[token.type],
+ value: value,
+ range: range,
+ loc: loc
+ };
+ if (token.regex) {
+ entry.regex = {
+ pattern: token.regex.pattern,
+ flags: token.regex.flags
+ };
+ }
+ extra.tokens.push(entry);
+ }
+
+ return token;
+ }
+
+ function collectRegex() {
+ var pos, loc, regex, token;
+
+ skipComment();
+
+ pos = index;
+ loc = {
+ start: {
+ line: lineNumber,
+ column: index - lineStart
+ }
+ };
+
+ regex = extra.scanRegExp();
+ loc.end = {
+ line: lineNumber,
+ column: index - lineStart
+ };
+
+ if (!extra.tokenize) {
+ /* istanbul ignore next */
+ // Pop the previous token, which is likely '/' or '/='
+ if (extra.tokens.length > 0) {
+ token = extra.tokens[extra.tokens.length - 1];
+ if (token.range[0] === pos && token.type === 'Punctuator') {
+ if (token.value === '/' || token.value === '/=') {
+ extra.tokens.pop();
+ }
+ }
+ }
+
+ extra.tokens.push({
+ type: 'RegularExpression',
+ value: regex.literal,
+ regex: regex.regex,
+ range: [pos, index],
+ loc: loc
+ });
+ }
+
+ return regex;
+ }
+
+ function filterTokenLocation() {
+ var i, entry, token, tokens = [];
+
+ for (i = 0; i < extra.tokens.length; ++i) {
+ entry = extra.tokens[i];
+ token = {
+ type: entry.type,
+ value: entry.value
+ };
+ if (entry.regex) {
+ token.regex = {
+ pattern: entry.regex.pattern,
+ flags: entry.regex.flags
+ };
+ }
+ if (extra.range) {
+ token.range = entry.range;
+ }
+ if (extra.loc) {
+ token.loc = entry.loc;
+ }
+ tokens.push(token);
+ }
+
+ extra.tokens = tokens;
+ }
+
+ function patch() {
+ if (typeof extra.tokens !== 'undefined') {
+ extra.advance = advance;
+ extra.scanRegExp = scanRegExp;
+
+ advance = collectToken;
+ scanRegExp = collectRegex;
+ }
+ }
+
+ function unpatch() {
+ if (typeof extra.scanRegExp === 'function') {
+ advance = extra.advance;
+ scanRegExp = extra.scanRegExp;
+ }
+ }
+
+ // This is used to modify the delegate.
+
+ function extend(object, properties) {
+ var entry, result = {};
+
+ for (entry in object) {
+ /* istanbul ignore else */
+ if (object.hasOwnProperty(entry)) {
+ result[entry] = object[entry];
+ }
+ }
+
+ for (entry in properties) {
+ /* istanbul ignore else */
+ if (properties.hasOwnProperty(entry)) {
+ result[entry] = properties[entry];
+ }
+ }
+
+ return result;
+ }
+
+ function tokenize(code, options) {
+ var toString,
+ token,
+ tokens;
+
+ toString = String;
+ if (typeof code !== 'string' && !(code instanceof String)) {
+ code = toString(code);
+ }
+
+ delegate = SyntaxTreeDelegate;
+ source = code;
+ index = 0;
+ lineNumber = (source.length > 0) ? 1 : 0;
+ lineStart = 0;
+ length = source.length;
+ lookahead = null;
+ state = {
+ allowKeyword: true,
+ allowIn: true,
+ labelSet: new StringMap(),
+ inFunctionBody: false,
+ inIteration: false,
+ inSwitch: false,
+ lastCommentStart: -1
+ };
+
+ extra = {};
+
+ // Options matching.
+ options = options || {};
+
+ // Of course we collect tokens here.
+ options.tokens = true;
+ extra.tokens = [];
+ extra.tokenize = true;
+ // The following two fields are necessary to compute the Regex tokens.
+ extra.openParenToken = -1;
+ extra.openCurlyToken = -1;
+
+ extra.range = (typeof options.range === 'boolean') && options.range;
+ extra.loc = (typeof options.loc === 'boolean') && options.loc;
+
+ if (typeof options.comment === 'boolean' && options.comment) {
+ extra.comments = [];
+ }
+ if (typeof options.tolerant === 'boolean' && options.tolerant) {
+ extra.errors = [];
+ }
+
+ patch();
+
+ try {
+ peek();
+ if (lookahead.type === Token.EOF) {
+ return extra.tokens;
+ }
+
+ token = lex();
+ while (lookahead.type !== Token.EOF) {
+ try {
+ token = lex();
+ } catch (lexError) {
+ token = lookahead;
+ if (extra.errors) {
+ extra.errors.push(lexError);
+ // We have to break on the first error
+ // to avoid infinite loops.
+ break;
+ } else {
+ throw lexError;
+ }
+ }
+ }
+
+ filterTokenLocation();
+ tokens = extra.tokens;
+ if (typeof extra.comments !== 'undefined') {
+ tokens.comments = extra.comments;
+ }
+ if (typeof extra.errors !== 'undefined') {
+ tokens.errors = extra.errors;
+ }
+ } catch (e) {
+ throw e;
+ } finally {
+ unpatch();
+ extra = {};
+ }
+ return tokens;
+ }
+
+ function parse(code, options) {
+ var program, toString;
+
+ toString = String;
+ if (typeof code !== 'string' && !(code instanceof String)) {
+ code = toString(code);
+ }
+
+ delegate = SyntaxTreeDelegate;
+ source = code;
+ index = 0;
+ lineNumber = (source.length > 0) ? 1 : 0;
+ lineStart = 0;
+ length = source.length;
+ lookahead = null;
+ state = {
+ allowKeyword: false,
+ allowIn: true,
+ labelSet: new StringMap(),
+ parenthesizedCount: 0,
+ inFunctionBody: false,
+ inIteration: false,
+ inSwitch: false,
+ lastCommentStart: -1,
+ yieldAllowed: false
+ };
+
+ extra = {};
+ if (typeof options !== 'undefined') {
+ extra.range = (typeof options.range === 'boolean') && options.range;
+ extra.loc = (typeof options.loc === 'boolean') && options.loc;
+ extra.attachComment = (typeof options.attachComment === 'boolean') && options.attachComment;
+
+ if (extra.loc && options.source !== null && options.source !== undefined) {
+ delegate = extend(delegate, {
+ 'postProcess': function (node) {
+ node.loc.source = toString(options.source);
+ return node;
+ }
+ });
+ }
+
+ if (options.sourceType === 'module') {
+ extra.isModule = true;
+ }
+ if (typeof options.tokens === 'boolean' && options.tokens) {
+ extra.tokens = [];
+ }
+ if (typeof options.comment === 'boolean' && options.comment) {
+ extra.comments = [];
+ }
+ if (typeof options.tolerant === 'boolean' && options.tolerant) {
+ extra.errors = [];
+ }
+ if (extra.attachComment) {
+ extra.range = true;
+ extra.comments = [];
+ extra.bottomRightStack = [];
+ extra.trailingComments = [];
+ extra.leadingComments = [];
+ }
+ }
+
+ patch();
+ try {
+ program = parseProgram();
+ if (typeof extra.comments !== 'undefined') {
+ program.comments = extra.comments;
+ }
+ if (typeof extra.tokens !== 'undefined') {
+ filterTokenLocation();
+ program.tokens = extra.tokens;
+ }
+ if (typeof extra.errors !== 'undefined') {
+ program.errors = extra.errors;
+ }
+ } catch (e) {
+ throw e;
+ } finally {
+ unpatch();
+ extra = {};
+ }
+
+ return program;
+ }
+
+ // Sync with *.json manifests.
+ exports.version = '1.1.0-dev-harmony';
+
+ exports.tokenize = tokenize;
+
+ exports.parse = parse;
+
+ // Deep copy.
+ /* istanbul ignore next */
+ exports.Syntax = (function () {
+ var name, types = {};
+
+ if (typeof Object.create === 'function') {
+ types = Object.create(null);
+ }
+
+ for (name in Syntax) {
+ if (Syntax.hasOwnProperty(name)) {
+ types[name] = Syntax[name];
+ }
+ }
+
+ if (typeof Object.freeze === 'function') {
+ Object.freeze(types);
+ }
+
+ return types;
+ }());
+
+}));
+/* vim: set sw=4 ts=4 et tw=80 : */
diff --git a/test/traverse.js b/test/traverse.js
new file mode 100644
index 0000000..4782220
--- /dev/null
+++ b/test/traverse.js
@@ -0,0 +1,562 @@
+// Copyright (C) 2013 Yusuke Suzuki <utatane.tea at gmail.com>
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+import Dumper from './dumper';
+import checkDump from './checkDump';
+import { expect } from 'chai';
+import { traverse } from '..';
+
+describe('object expression', function() {
+ it('properties', function() {
+ const tree = {
+ type: 'ObjectExpression',
+ properties: [{
+ type: 'Property',
+ key: {
+ type: 'Identifier',
+ name: 'a'
+ },
+ value: {
+ type: 'Identifier',
+ name: 'a'
+ }
+ }]
+ };
+
+ checkDump(Dumper.dump(tree), `
+ enter - ObjectExpression
+ enter - Property
+ enter - Identifier
+ leave - Identifier
+ enter - Identifier
+ leave - Identifier
+ leave - Property
+ leave - ObjectExpression
+ `);
+ });
+
+ it('properties without type', function() {
+ const tree = {
+ type: 'ObjectExpression',
+ properties: [{
+ key: {
+ type: 'Identifier',
+ name: 'a'
+ },
+ value: {
+ type: 'Identifier',
+ name: 'a'
+ }
+ }]
+ };
+
+ checkDump(Dumper.dump(tree), `
+ enter - ObjectExpression
+ enter - undefined
+ enter - Identifier
+ leave - Identifier
+ enter - Identifier
+ leave - Identifier
+ leave - undefined
+ leave - ObjectExpression
+ `);
+ });
+
+ it('properties with custom type', function() {
+ const tree = {
+ type: 'ObjectExpression',
+ properties: [{
+ type: 'CustomProperty',
+ foo: {
+ type: 'Identifier',
+ name: 'a'
+ }
+ }]
+ };
+
+ checkDump(Dumper.dump(tree, {CustomProperty: ['foo']}), `
+ enter - ObjectExpression
+ enter - CustomProperty
+ enter - Identifier
+ leave - Identifier
+ leave - CustomProperty
+ leave - ObjectExpression
+ `);
+ });
+
+ it('skip and break', function() {
+ const tree = {
+ type: 'ObjectExpression',
+ properties: [{
+ type: 'Property',
+ key: {
+ type: 'Identifier',
+ name: 'a'
+ },
+ value: {
+ $enter: 'Skip',
+ type: 'ObjectExpression',
+ properties: [{
+ type: 'Property',
+ key: {
+ type: 'Identifier',
+ name: 'a'
+ },
+ value: {
+ type: 'Identifier',
+ name: 'a'
+ }
+ }]
+ },
+ $leave: 'Break'
+ }]
+ };
+
+ checkDump(Dumper.dump(tree), `
+ enter - ObjectExpression
+ enter - Property
+ enter - Identifier
+ leave - Identifier
+ enter - ObjectExpression
+ leave - ObjectExpression
+ leave - Property
+ `);
+ });
+});
+
+describe('object pattern', function() {
+ it('properties', function() {
+ const tree = {
+ type: 'ObjectPattern',
+ properties: [{
+ type: 'Property',
+ key: {
+ type: 'Identifier',
+ name: 'a'
+ },
+ value: {
+ type: 'Identifier',
+ name: 'a'
+ }
+ }]
+ };
+
+ checkDump(Dumper.dump(tree), `
+ enter - ObjectPattern
+ enter - Property
+ enter - Identifier
+ leave - Identifier
+ enter - Identifier
+ leave - Identifier
+ leave - Property
+ leave - ObjectPattern
+ `);
+ });
+
+ it('properties without type', function() {
+ const tree = {
+ type: 'ObjectPattern',
+ properties: [{
+ key: {
+ type: 'Identifier',
+ name: 'a'
+ },
+ value: {
+ type: 'Identifier',
+ name: 'a'
+ }
+ }]
+ };
+
+ checkDump(Dumper.dump(tree), `
+ enter - ObjectPattern
+ enter - undefined
+ enter - Identifier
+ leave - Identifier
+ enter - Identifier
+ leave - Identifier
+ leave - undefined
+ leave - ObjectPattern
+ `);
+ });
+});
+
+describe('try statement', function() {
+ it('old interface', function() {
+ const tree = {
+ type: 'TryStatement',
+ handler: {
+ type: 'BlockStatement',
+ body: []
+ },
+ finalizer: {
+ type: 'BlockStatement',
+ body: []
+ }
+ };
+
+ checkDump(Dumper.dump(tree), `
+ enter - TryStatement
+ enter - BlockStatement
+ leave - BlockStatement
+ enter - BlockStatement
+ leave - BlockStatement
+ leave - TryStatement
+ `);
+ });
+
+ it('new interface', function() {
+ const tree = {
+ type: 'TryStatement',
+ handler: {
+ type: 'BlockStatement',
+ body: []
+ },
+ finalizer: {
+ type: 'BlockStatement',
+ body: []
+ }
+ };
+
+ checkDump(Dumper.dump(tree), `
+ enter - TryStatement
+ enter - BlockStatement
+ leave - BlockStatement
+ enter - BlockStatement
+ leave - BlockStatement
+ leave - TryStatement
+ `);
+ });
+});
+
+describe('arrow function expression', function() {
+ it('traverse', function() {
+ const tree = {
+ type: 'ArrowFunctionExpression',
+ params: [
+ {
+ type: 'AssignmentPattern',
+ left: {
+ type: 'Identifier',
+ name: 'a'
+ },
+ right: {
+ type: 'Literal',
+ value: 20
+ }
+ },
+ {
+ type: 'RestElement',
+ argument: {
+ type: 'Identifier',
+ name: 'rest'
+ }
+ }
+ ],
+ body: {
+ type: 'BlockStatement',
+ body: []
+ }
+ };
+
+ checkDump(Dumper.dump(tree), `
+ enter - ArrowFunctionExpression
+ enter - AssignmentPattern
+ enter - Identifier
+ leave - Identifier
+ enter - Literal
+ leave - Literal
+ leave - AssignmentPattern
+ enter - RestElement
+ enter - Identifier
+ leave - Identifier
+ leave - RestElement
+ enter - BlockStatement
+ leave - BlockStatement
+ leave - ArrowFunctionExpression
+ `);
+ });
+});
+
+describe('function expression', function() {
+ it('traverse', function() {
+ const tree = {
+ type: 'FunctionExpression',
+ params: [
+ {
+ type: 'AssignmentPattern',
+ left: {
+ type: 'Identifier',
+ name: 'a'
+ },
+ right: {
+ type: 'Literal',
+ value: 20
+ }
+ },
+ {
+ type: 'RestElement',
+ argument: {
+ type: 'Identifier',
+ name: 'rest'
+ }
+ }
+ ],
+ body: {
+ type: 'BlockStatement',
+ body: []
+ }
+ };
+
+ checkDump(Dumper.dump(tree), `
+ enter - FunctionExpression
+ enter - AssignmentPattern
+ enter - Identifier
+ leave - Identifier
+ enter - Literal
+ leave - Literal
+ leave - AssignmentPattern
+ enter - RestElement
+ enter - Identifier
+ leave - Identifier
+ leave - RestElement
+ enter - BlockStatement
+ leave - BlockStatement
+ leave - FunctionExpression
+ `);
+ });
+});
+
+describe('function declaration', function() {
+ it('traverse', function() {
+ const tree = {
+ type: 'FunctionDeclaration',
+ id: {
+ type: 'Identifier',
+ name: 'decl'
+ },
+ params: [
+ {
+ type: 'AssignmentPattern',
+ left: {
+ type: 'Identifier',
+ name: 'a'
+ },
+ right: {
+ type: 'Literal',
+ value: 20
+ }
+ },
+ {
+ type: 'RestElement',
+ argument: {
+ type: 'Identifier',
+ name: 'rest'
+ }
+ }
+ ],
+ body: {
+ type: 'BlockStatement',
+ body: []
+ }
+ };
+
+ checkDump(Dumper.dump(tree), `
+ enter - FunctionDeclaration
+ enter - Identifier
+ leave - Identifier
+ enter - AssignmentPattern
+ enter - Identifier
+ leave - Identifier
+ enter - Literal
+ leave - Literal
+ leave - AssignmentPattern
+ enter - RestElement
+ enter - Identifier
+ leave - Identifier
+ leave - RestElement
+ enter - BlockStatement
+ leave - BlockStatement
+ leave - FunctionDeclaration
+ `);
+ });
+});
+
+describe('extending keys', function() {
+ it('traverse', function() {
+ const tree = {
+ type: 'TestStatement',
+ id: {
+ type: 'Identifier',
+ name: 'decl'
+ },
+ params: [{
+ type: 'Identifier',
+ name: 'a'
+ }],
+ defaults: [{
+ type: 'Literal',
+ value: 20
+ }],
+ rest: {
+ type: 'Identifier',
+ name: 'rest'
+ },
+ body: {
+ type: 'BlockStatement',
+ body: []
+ }
+ };
+
+ const result = Dumper.dump(tree, {
+ TestStatement: ['id', 'params', 'defaults', 'rest', 'body']
+ });
+ checkDump(result, `
+ enter - TestStatement
+ enter - Identifier
+ leave - Identifier
+ enter - Identifier
+ leave - Identifier
+ enter - Literal
+ leave - Literal
+ enter - Identifier
+ leave - Identifier
+ enter - BlockStatement
+ leave - BlockStatement
+ leave - TestStatement
+ `);
+ });
+});
+
+
+describe('no listed keys fallback', function() {
+ it('traverse', function() {
+ const tree = {
+ type: 'TestStatement',
+ id: {
+ type: 'Identifier',
+ name: 'decl'
+ },
+ params: [{
+ type: 'Identifier',
+ name: 'a'
+ }],
+ defaults: [{
+ type: 'Literal',
+ value: 20
+ }],
+ rest: {
+ type: 'Identifier',
+ name: 'rest'
+ },
+ body: {
+ type: 'BlockStatement',
+ body: []
+ }
+ };
+
+ checkDump(Dumper.dump(tree, null, 'iteration'), `
+ enter - TestStatement
+ enter - Identifier
+ leave - Identifier
+ enter - Identifier
+ leave - Identifier
+ enter - Literal
+ leave - Literal
+ enter - Identifier
+ leave - Identifier
+ enter - BlockStatement
+ leave - BlockStatement
+ leave - TestStatement
+ `);
+ });
+
+ it('traverse with fallback function', function() {
+ const tree = {
+ type: 'TestStatement',
+ id: {
+ type: 'Identifier',
+ name: 'decl'
+ },
+ params: [{
+ type: 'Identifier',
+ name: 'a'
+ }],
+ defaults: [{
+ type: 'Literal',
+ value: 20
+ }],
+ rest: {
+ type: 'Identifier',
+ name: 'rest'
+ },
+ body: {
+ type: 'BlockStatement',
+ body: []
+ }
+ };
+
+ function filterKeys(node) {
+ return Object.keys(node).filter(key => key !== 'id');
+ }
+
+ checkDump(Dumper.dump(tree, null, filterKeys), `
+ enter - TestStatement
+ enter - Identifier
+ leave - Identifier
+ enter - Literal
+ leave - Literal
+ enter - Identifier
+ leave - Identifier
+ enter - BlockStatement
+ leave - BlockStatement
+ leave - TestStatement
+ `);
+ });
+
+ it('throw unknown node type error when unknown nodes', function() {
+ const tree = {
+ type: 'XXXExpression',
+ properties: [{
+ type: 'Property',
+ key: {
+ type: 'Identifier',
+ name: 'a'
+ },
+ value: {
+ type: 'ArrayExpression',
+ elements: [
+ {type: 'Literal', value: 1},
+ {type: 'Literal', value: 2},
+ {type: 'Literal', value: 3},
+ {type: 'Literal', value: 4}
+ ]
+ }
+ }]
+ };
+
+ expect(
+ () => traverse(tree, { enter(node) {} })
+ ).to.throw('Unknown node type XXXExpression.');
+ });
+});
diff --git a/test/type.js b/test/type.js
new file mode 100644
index 0000000..6d66344
--- /dev/null
+++ b/test/type.js
@@ -0,0 +1,57 @@
+// -*- coding: utf-8 -*-
+// Copyright (C) 2014 Yusuke Suzuki <utatane.tea at gmail.com>
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above copyright
+// notice, this list of conditions and the following disclaimer in the
+// documentation and/or other materials provided with the distribution.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+// ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+// DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+// THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+import { expect } from 'chai';
+import { traverse } from '..';
+
+describe('type API', function() {
+ it('for Property', function() {
+ const property = {
+ type: 'Property',
+ key: {
+ type: 'Identifier',
+ name: 'a'
+ },
+ value: {
+ type: 'Identifier',
+ name: 'a'
+ }
+ };
+
+ const tree = {
+ type: 'ObjectExpression',
+ properties: [property]
+ };
+
+ return traverse(tree, {
+ enter(node) {
+ if (node === property) {
+ expect(this.type()).to.be.equal('Property');
+ }
+ }
+ });
+ });
+});
+
+// vim: set sw=4 ts=4 et tw=80 :
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-estraverse.git
More information about the Pkg-javascript-commits
mailing list