summaryrefslogtreecommitdiffstats
path: root/devtools/shared/Parser.jsm
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /devtools/shared/Parser.jsm
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'devtools/shared/Parser.jsm')
-rw-r--r--devtools/shared/Parser.jsm2451
1 files changed, 2451 insertions, 0 deletions
diff --git a/devtools/shared/Parser.jsm b/devtools/shared/Parser.jsm
new file mode 100644
index 000000000..4bd635383
--- /dev/null
+++ b/devtools/shared/Parser.jsm
@@ -0,0 +1,2451 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const Cu = Components.utils;
+
+const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
+const { console } = require("resource://gre/modules/Console.jsm");
+const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+
+XPCOMUtils.defineLazyModuleGetter(this,
+ "Reflect", "resource://gre/modules/reflect.jsm");
+
+this.EXPORTED_SYMBOLS = ["Parser", "ParserHelpers", "SyntaxTreeVisitor"];
+
+/**
+ * A JS parser using the reflection API.
+ */
+this.Parser = function Parser() {
+ this._cache = new Map();
+ this.errors = [];
+ this.logExceptions = true;
+};
+
+Parser.prototype = {
+ /**
+ * Gets a collection of parser methods for a specified source.
+ *
+ * @param string source
+ * The source text content.
+ * @param string url [optional]
+ * The source url. The AST nodes will be cached, so you can use this
+ * identifier to avoid parsing the whole source again.
+ */
+ get(source, url = "") {
+ // Try to use the cached AST nodes, to avoid useless parsing operations.
+ if (this._cache.has(url)) {
+ return this._cache.get(url);
+ }
+
+ // The source may not necessarily be JS, in which case we need to extract
+ // all the scripts. Fastest/easiest way is with a regular expression.
+ // Don't worry, the rules of using a <script> tag are really strict,
+ // this will work.
+ let regexp = /<script[^>]*?(?:>([^]*?)<\/script\s*>|\/>)/gim;
+ let syntaxTrees = [];
+ let scriptMatches = [];
+ let scriptMatch;
+
+ if (source.match(/^\s*</)) {
+ // First non whitespace character is &lt, so most definitely HTML.
+ while ((scriptMatch = regexp.exec(source))) {
+ // Contents are captured at index 1 or nothing: Self-closing scripts
+ // won't capture code content
+ scriptMatches.push(scriptMatch[1] || "");
+ }
+ }
+
+ // If there are no script matches, send the whole source directly to the
+ // reflection API to generate the AST nodes.
+ if (!scriptMatches.length) {
+ // Reflect.parse throws when encounters a syntax error.
+ try {
+ let nodes = Reflect.parse(source);
+ let length = source.length;
+ syntaxTrees.push(new SyntaxTree(nodes, url, length));
+ } catch (e) {
+ this.errors.push(e);
+ if (this.logExceptions) {
+ DevToolsUtils.reportException(url, e);
+ }
+ }
+ } else {
+ // Generate the AST nodes for each script.
+ for (let script of scriptMatches) {
+ // Reflect.parse throws when encounters a syntax error.
+ try {
+ let nodes = Reflect.parse(script);
+ let offset = source.indexOf(script);
+ let length = script.length;
+ syntaxTrees.push(new SyntaxTree(nodes, url, length, offset));
+ } catch (e) {
+ this.errors.push(e);
+ if (this.logExceptions) {
+ DevToolsUtils.reportException(url, e);
+ }
+ }
+ }
+ }
+
+ let pool = new SyntaxTreesPool(syntaxTrees, url);
+
+ // Cache the syntax trees pool by the specified url. This is entirely
+ // optional, but it's strongly encouraged to cache ASTs because
+ // generating them can be costly with big/complex sources.
+ if (url) {
+ this._cache.set(url, pool);
+ }
+
+ return pool;
+ },
+
+ /**
+ * Clears all the parsed sources from cache.
+ */
+ clearCache() {
+ this._cache.clear();
+ },
+
+ /**
+ * Clears the AST for a particular source.
+ *
+ * @param String url
+ * The URL of the source that is being cleared.
+ */
+ clearSource(url) {
+ this._cache.delete(url);
+ },
+
+ _cache: null,
+ errors: null
+};
+
+/**
+ * A pool handling a collection of AST nodes generated by the reflection API.
+ *
+ * @param object syntaxTrees
+ * A collection of AST nodes generated for a source.
+ * @param string url [optional]
+ * The source url.
+ */
+function SyntaxTreesPool(syntaxTrees, url = "<unknown>") {
+ this._trees = syntaxTrees;
+ this._url = url;
+ this._cache = new Map();
+}
+
+SyntaxTreesPool.prototype = {
+ /**
+ * @see SyntaxTree.prototype.getIdentifierAt
+ */
+ getIdentifierAt({ line, column, scriptIndex, ignoreLiterals }) {
+ return this._call("getIdentifierAt",
+ scriptIndex, line, column, ignoreLiterals)[0];
+ },
+
+ /**
+ * @see SyntaxTree.prototype.getNamedFunctionDefinitions
+ */
+ getNamedFunctionDefinitions(substring) {
+ return this._call("getNamedFunctionDefinitions", -1, substring);
+ },
+
+ /**
+ * @return SyntaxTree
+ * The last tree in this._trees
+ */
+ getLastSyntaxTree() {
+ return this._trees[this._trees.length - 1];
+ },
+
+ /**
+ * Gets the total number of scripts in the parent source.
+ * @return number
+ */
+ get scriptCount() {
+ return this._trees.length;
+ },
+
+ /**
+ * Finds the start and length of the script containing the specified offset
+ * relative to its parent source.
+ *
+ * @param number atOffset
+ * The offset relative to the parent source.
+ * @return object
+ * The offset and length relative to the enclosing script.
+ */
+ getScriptInfo(atOffset) {
+ let info = { start: -1, length: -1, index: -1 };
+
+ for (let { offset, length } of this._trees) {
+ info.index++;
+ if (offset <= atOffset && offset + length >= atOffset) {
+ info.start = offset;
+ info.length = length;
+ return info;
+ }
+ }
+
+ info.index = -1;
+ return info;
+ },
+
+ /**
+ * Handles a request for a specific or all known syntax trees.
+ *
+ * @param string functionName
+ * The function name to call on the SyntaxTree instances.
+ * @param number syntaxTreeIndex
+ * The syntax tree for which to handle the request. If the tree at
+ * the specified index isn't found, the accumulated results for all
+ * syntax trees are returned.
+ * @param any params
+ * Any kind params to pass to the request function.
+ * @return array
+ * The results given by all known syntax trees.
+ */
+ _call(functionName, syntaxTreeIndex, ...params) {
+ let results = [];
+ let requestId = [functionName, syntaxTreeIndex, params].toSource();
+
+ if (this._cache.has(requestId)) {
+ return this._cache.get(requestId);
+ }
+
+ let requestedTree = this._trees[syntaxTreeIndex];
+ let targettedTrees = requestedTree ? [requestedTree] : this._trees;
+
+ for (let syntaxTree of targettedTrees) {
+ try {
+ let parseResults = syntaxTree[functionName].apply(syntaxTree, params);
+ if (parseResults) {
+ parseResults.sourceUrl = syntaxTree.url;
+ parseResults.scriptLength = syntaxTree.length;
+ parseResults.scriptOffset = syntaxTree.offset;
+ results.push(parseResults);
+ }
+ } catch (e) {
+ // Can't guarantee that the tree traversal logic is forever perfect :)
+ // Language features may be added, in which case the recursive methods
+ // need to be updated. If an exception is thrown here, file a bug.
+ DevToolsUtils.reportException(
+ `Syntax tree visitor for ${this._url}`, e);
+ }
+ }
+ this._cache.set(requestId, results);
+ return results;
+ },
+
+ _trees: null,
+ _cache: null
+};
+
+/**
+ * A collection of AST nodes generated by the reflection API.
+ *
+ * @param object nodes
+ * The AST nodes.
+ * @param string url
+ * The source url.
+ * @param number length
+ * The total number of chars of the parsed script in the parent source.
+ * @param number offset [optional]
+ * The char offset of the parsed script in the parent source.
+ */
+function SyntaxTree(nodes, url, length, offset = 0) {
+ this.AST = nodes;
+ this.url = url;
+ this.length = length;
+ this.offset = offset;
+}
+
+SyntaxTree.prototype = {
+ /**
+ * Gets the identifier at the specified location.
+ *
+ * @param number line
+ * The line in the source.
+ * @param number column
+ * The column in the source.
+ * @param boolean ignoreLiterals
+ * Specifies if alone literals should be ignored.
+ * @return object
+ * An object containing identifier information as { name, location,
+ * evalString } properties, or null if nothing is found.
+ */
+ getIdentifierAt(line, column, ignoreLiterals) {
+ let info = null;
+
+ SyntaxTreeVisitor.walk(this.AST, {
+ /**
+ * Callback invoked for each identifier node.
+ * @param Node node
+ */
+ onIdentifier(node) {
+ if (ParserHelpers.nodeContainsPoint(node, line, column)) {
+ info = {
+ name: node.name,
+ location: ParserHelpers.getNodeLocation(node),
+ evalString: ParserHelpers.getIdentifierEvalString(node)
+ };
+
+ // Abruptly halt walking the syntax tree.
+ SyntaxTreeVisitor.break = true;
+ }
+ },
+
+ /**
+ * Callback invoked for each literal node.
+ * @param Node node
+ */
+ onLiteral(node) {
+ if (!ignoreLiterals) {
+ this.onIdentifier(node);
+ }
+ },
+
+ /**
+ * Callback invoked for each 'this' node.
+ * @param Node node
+ */
+ onThisExpression(node) {
+ this.onIdentifier(node);
+ }
+ });
+
+ return info;
+ },
+
+ /**
+ * Searches for all function definitions (declarations and expressions)
+ * whose names (or inferred names) contain a string.
+ *
+ * @param string substring
+ * The string to be contained in the function name (or inferred name).
+ * Can be an empty string to match all functions.
+ * @return array
+ * All the matching function declarations and expressions, as
+ * { functionName, functionLocation ... } object hashes.
+ */
+ getNamedFunctionDefinitions(substring) {
+ let lowerCaseToken = substring.toLowerCase();
+ let store = [];
+
+ function includesToken(name) {
+ return name && name.toLowerCase().includes(lowerCaseToken);
+ }
+
+ SyntaxTreeVisitor.walk(this.AST, {
+ /**
+ * Callback invoked for each function declaration node.
+ * @param Node node
+ */
+ onFunctionDeclaration(node) {
+ let functionName = node.id.name;
+ if (includesToken(functionName)) {
+ store.push({
+ functionName: functionName,
+ functionLocation: ParserHelpers.getNodeLocation(node)
+ });
+ }
+ },
+
+ /**
+ * Callback invoked for each function expression node.
+ * @param Node node
+ */
+ onFunctionExpression(node) {
+ // Function expressions don't necessarily have a name.
+ let functionName = node.id ? node.id.name : "";
+ let functionLocation = ParserHelpers.getNodeLocation(node);
+
+ // Infer the function's name from an enclosing syntax tree node.
+ let inferredInfo = ParserHelpers.inferFunctionExpressionInfo(node);
+ let inferredName = inferredInfo.name;
+ let inferredChain = inferredInfo.chain;
+ let inferredLocation = inferredInfo.loc;
+
+ // Current node may be part of a larger assignment expression stack.
+ if (node._parent.type == "AssignmentExpression") {
+ this.onFunctionExpression(node._parent);
+ }
+
+ if (includesToken(functionName) || includesToken(inferredName)) {
+ store.push({
+ functionName: functionName,
+ functionLocation: functionLocation,
+ inferredName: inferredName,
+ inferredChain: inferredChain,
+ inferredLocation: inferredLocation
+ });
+ }
+ },
+
+ /**
+ * Callback invoked for each arrow expression node.
+ * @param Node node
+ */
+ onArrowFunctionExpression(node) {
+ // Infer the function's name from an enclosing syntax tree node.
+ let inferredInfo = ParserHelpers.inferFunctionExpressionInfo(node);
+ let inferredName = inferredInfo.name;
+ let inferredChain = inferredInfo.chain;
+ let inferredLocation = inferredInfo.loc;
+
+ // Current node may be part of a larger assignment expression stack.
+ if (node._parent.type == "AssignmentExpression") {
+ this.onFunctionExpression(node._parent);
+ }
+
+ if (includesToken(inferredName)) {
+ store.push({
+ inferredName: inferredName,
+ inferredChain: inferredChain,
+ inferredLocation: inferredLocation
+ });
+ }
+ }
+ });
+
+ return store;
+ },
+
+ AST: null,
+ url: "",
+ length: 0,
+ offset: 0
+};
+
+/**
+ * Parser utility methods.
+ */
+var ParserHelpers = {
+ /**
+ * Gets the location information for a node. Not all nodes have a
+ * location property directly attached, or the location information
+ * is incorrect, in which cases it's accessible via the parent.
+ *
+ * @param Node node
+ * The node who's location needs to be retrieved.
+ * @return object
+ * An object containing { line, column } information.
+ */
+ getNodeLocation(node) {
+ if (node.type != "Identifier") {
+ return node.loc;
+ }
+ // Work around the fact that some identifier nodes don't have the
+ // correct location attached.
+ let { loc: parentLocation, type: parentType } = node._parent;
+ let { loc: nodeLocation } = node;
+ if (!nodeLocation) {
+ if (parentType == "FunctionDeclaration" ||
+ parentType == "FunctionExpression") {
+ // e.g. "function foo() {}" or "{ bar: function foo() {} }"
+ // The location is unavailable for the identifier node "foo".
+ let loc = Cu.cloneInto(parentLocation, {});
+ loc.end.line = loc.start.line;
+ loc.end.column = loc.start.column + node.name.length;
+ return loc;
+ }
+ if (parentType == "MemberExpression") {
+ // e.g. "foo.bar"
+ // The location is unavailable for the identifier node "bar".
+ let loc = Cu.cloneInto(parentLocation, {});
+ loc.start.line = loc.end.line;
+ loc.start.column = loc.end.column - node.name.length;
+ return loc;
+ }
+ if (parentType == "LabeledStatement") {
+ // e.g. label: ...
+ // The location is unavailable for the identifier node "label".
+ let loc = Cu.cloneInto(parentLocation, {});
+ loc.end.line = loc.start.line;
+ loc.end.column = loc.start.column + node.name.length;
+ return loc;
+ }
+ if (parentType == "ContinueStatement" || parentType == "BreakStatement") {
+ // e.g. continue label; or break label;
+ // The location is unavailable for the identifier node "label".
+ let loc = Cu.cloneInto(parentLocation, {});
+ loc.start.line = loc.end.line;
+ loc.start.column = loc.end.column - node.name.length;
+ return loc;
+ }
+ } else if (parentType == "VariableDeclarator") {
+ // e.g. "let foo = 42"
+ // The location incorrectly spans across the whole variable declaration,
+ // not just the identifier node "foo".
+ let loc = Cu.cloneInto(nodeLocation, {});
+ loc.end.line = loc.start.line;
+ loc.end.column = loc.start.column + node.name.length;
+ return loc;
+ }
+ return node.loc;
+ },
+
+ /**
+ * Checks if a node's bounds contains a specified line.
+ *
+ * @param Node node
+ * The node's bounds used as reference.
+ * @param number line
+ * The line number to check.
+ * @return boolean
+ * True if the line and column is contained in the node's bounds.
+ */
+ nodeContainsLine(node, line) {
+ let { start: s, end: e } = this.getNodeLocation(node);
+ return s.line <= line && e.line >= line;
+ },
+
+ /**
+ * Checks if a node's bounds contains a specified line and column.
+ *
+ * @param Node node
+ * The node's bounds used as reference.
+ * @param number line
+ * The line number to check.
+ * @param number column
+ * The column number to check.
+ * @return boolean
+ * True if the line and column is contained in the node's bounds.
+ */
+ nodeContainsPoint(node, line, column) {
+ let { start: s, end: e } = this.getNodeLocation(node);
+ return s.line == line && e.line == line &&
+ s.column <= column && e.column >= column;
+ },
+
+ /**
+ * Try to infer a function expression's name & other details based on the
+ * enclosing VariableDeclarator, AssignmentExpression or ObjectExpression.
+ *
+ * @param Node node
+ * The function expression node to get the name for.
+ * @return object
+ * The inferred function name, or empty string can't infer the name,
+ * along with the chain (a generic "context", like a prototype chain)
+ * and location if available.
+ */
+ inferFunctionExpressionInfo(node) {
+ let parent = node._parent;
+
+ // A function expression may be defined in a variable declarator,
+ // e.g. var foo = function(){}, in which case it is possible to infer
+ // the variable name.
+ if (parent.type == "VariableDeclarator") {
+ return {
+ name: parent.id.name,
+ chain: null,
+ loc: this.getNodeLocation(parent.id)
+ };
+ }
+
+ // Function expressions can also be defined in assignment expressions,
+ // e.g. foo = function(){} or foo.bar = function(){}, in which case it is
+ // possible to infer the assignee name ("foo" and "bar" respectively).
+ if (parent.type == "AssignmentExpression") {
+ let propertyChain = this._getMemberExpressionPropertyChain(parent.left);
+ let propertyLeaf = propertyChain.pop();
+ return {
+ name: propertyLeaf,
+ chain: propertyChain,
+ loc: this.getNodeLocation(parent.left)
+ };
+ }
+
+ // If a function expression is defined in an object expression,
+ // e.g. { foo: function(){} }, then it is possible to infer the name
+ // from the corresponding property.
+ if (parent.type == "ObjectExpression") {
+ let propertyKey = this._getObjectExpressionPropertyKeyForValue(node);
+ let propertyChain = this._getObjectExpressionPropertyChain(parent);
+ let propertyLeaf = propertyKey.name;
+ return {
+ name: propertyLeaf,
+ chain: propertyChain,
+ loc: this.getNodeLocation(propertyKey)
+ };
+ }
+
+ // Can't infer the function expression's name.
+ return {
+ name: "",
+ chain: null,
+ loc: null
+ };
+ },
+
+ /**
+ * Gets the name of an object expression's property to which a specified
+ * value is assigned.
+ *
+ * Used for inferring function expression information and retrieving
+ * an identifier evaluation string.
+ *
+ * For example, if "node" represents the "bar" identifier in a hypothetical
+ * "{ foo: bar }" object expression, the returned node is the "foo"
+ * identifier.
+ *
+ * @param Node node
+ * The value node in an object expression.
+ * @return object
+ * The key identifier node in the object expression.
+ */
+ _getObjectExpressionPropertyKeyForValue(node) {
+ let parent = node._parent;
+ if (parent.type != "ObjectExpression") {
+ return null;
+ }
+ for (let property of parent.properties) {
+ if (property.value == node) {
+ return property.key;
+ }
+ }
+ return null;
+ },
+
+ /**
+ * Gets an object expression's property chain to its parent
+ * variable declarator or assignment expression, if available.
+ *
+ * Used for inferring function expression information and retrieving
+ * an identifier evaluation string.
+ *
+ * For example, if node represents the "baz: {}" object expression in a
+ * hypothetical "foo = { bar: { baz: {} } }" assignment expression, the
+ * returned chain is ["foo", "bar", "baz"].
+ *
+ * @param Node node
+ * The object expression node to begin the scan from.
+ * @param array aStore [optional]
+ * The chain to store the nodes into.
+ * @return array
+ * The chain to the parent variable declarator, as strings.
+ */
+ _getObjectExpressionPropertyChain(node, aStore = []) {
+ switch (node.type) {
+ case "ObjectExpression":
+ this._getObjectExpressionPropertyChain(node._parent, aStore);
+ let propertyKey = this._getObjectExpressionPropertyKeyForValue(node);
+ if (propertyKey) {
+ aStore.push(propertyKey.name);
+ }
+ break;
+ // Handle "var foo = { ... }" variable declarators.
+ case "VariableDeclarator":
+ aStore.push(node.id.name);
+ break;
+ // Handle "foo.bar = { ... }" assignment expressions, since they're
+ // commonly used when defining an object's prototype methods; e.g:
+ // "Foo.prototype = { ... }".
+ case "AssignmentExpression":
+ this._getMemberExpressionPropertyChain(node.left, aStore);
+ break;
+ // Additionally handle stuff like "foo = bar.baz({ ... })", because it's
+ // commonly used in prototype-based inheritance in many libraries; e.g:
+ // "Foo = Bar.extend({ ... })".
+ case "NewExpression":
+ case "CallExpression":
+ this._getObjectExpressionPropertyChain(node._parent, aStore);
+ break;
+ }
+ return aStore;
+ },
+
+ /**
+ * Gets a member expression's property chain.
+ *
+ * Used for inferring function expression information and retrieving
+ * an identifier evaluation string.
+ *
+ * For example, if node represents a hypothetical "foo.bar.baz"
+ * member expression, the returned chain ["foo", "bar", "baz"].
+ *
+ * More complex expressions like foo.bar().baz are intentionally not handled.
+ *
+ * @param Node node
+ * The member expression node to begin the scan from.
+ * @param array store [optional]
+ * The chain to store the nodes into.
+ * @return array
+ * The full member chain, as strings.
+ */
+ _getMemberExpressionPropertyChain(node, store = []) {
+ switch (node.type) {
+ case "MemberExpression":
+ this._getMemberExpressionPropertyChain(node.object, store);
+ this._getMemberExpressionPropertyChain(node.property, store);
+ break;
+ case "ThisExpression":
+ store.push("this");
+ break;
+ case "Identifier":
+ store.push(node.name);
+ break;
+ }
+ return store;
+ },
+
+ /**
+ * Returns an evaluation string which can be used to obtain the
+ * current value for the respective identifier.
+ *
+ * @param Node node
+ * The leaf node (e.g. Identifier, Literal) to begin the scan from.
+ * @return string
+ * The corresponding evaluation string, or empty string if
+ * the specified leaf node can't be used.
+ */
+ getIdentifierEvalString(node) {
+ switch (node._parent.type) {
+ case "ObjectExpression":
+ // If the identifier is the actual property value, it can be used
+ // directly as an evaluation string. Otherwise, construct the property
+ // access chain, since the value might have changed.
+ if (!this._getObjectExpressionPropertyKeyForValue(node)) {
+ let propertyChain =
+ this._getObjectExpressionPropertyChain(node._parent);
+ let propertyLeaf = node.name;
+ return [...propertyChain, propertyLeaf].join(".");
+ }
+ break;
+ case "MemberExpression":
+ // Make sure this is a property identifier, not the parent object.
+ if (node._parent.property == node) {
+ return this._getMemberExpressionPropertyChain(node._parent).join(".");
+ }
+ break;
+ }
+ switch (node.type) {
+ case "ThisExpression":
+ return "this";
+ case "Identifier":
+ return node.name;
+ case "Literal":
+ return uneval(node.value);
+ default:
+ return "";
+ }
+ }
+};
+
+/**
+ * A visitor for a syntax tree generated by the reflection API.
+ * See https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API.
+ *
+ * All node types implement the following interface:
+ * interface Node {
+ * type: string;
+ * loc: SourceLocation | null;
+ * }
+ */
+var SyntaxTreeVisitor = {
+ /**
+ * Walks a syntax tree.
+ *
+ * @param object tree
+ * The AST nodes generated by the reflection API
+ * @param object callbacks
+ * A map of all the callbacks to invoke when passing through certain
+ * types of noes (e.g: onFunctionDeclaration, onBlockStatement etc.).
+ */
+ walk(tree, callbacks) {
+ this.break = false;
+ this[tree.type](tree, callbacks);
+ },
+
+ /**
+ * Filters all the nodes in this syntax tree based on a predicate.
+ *
+ * @param object tree
+ * The AST nodes generated by the reflection API
+ * @param function predicate
+ * The predicate ran on each node.
+ * @return array
+ * An array of nodes validating the predicate.
+ */
+ filter(tree, predicate) {
+ let store = [];
+ this.walk(tree, {
+ onNode: e => {
+ if (predicate(e)) {
+ store.push(e);
+ }
+ }
+ });
+ return store;
+ },
+
+ /**
+ * A flag checked on each node in the syntax tree. If true, walking is
+ * abruptly halted.
+ */
+ break: false,
+
+ /**
+ * A complete program source tree.
+ *
+ * interface Program <: Node {
+ * type: "Program";
+ * body: [ Statement ];
+ * }
+ */
+ Program(node, callbacks) {
+ if (callbacks.onProgram) {
+ callbacks.onProgram(node);
+ }
+ for (let statement of node.body) {
+ this[statement.type](statement, node, callbacks);
+ }
+ },
+
+ /**
+ * Any statement.
+ *
+ * interface Statement <: Node { }
+ */
+ Statement(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onStatement) {
+ callbacks.onStatement(node);
+ }
+ },
+
+ /**
+ * An empty statement, i.e., a solitary semicolon.
+ *
+ * interface EmptyStatement <: Statement {
+ * type: "EmptyStatement";
+ * }
+ */
+ EmptyStatement(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onEmptyStatement) {
+ callbacks.onEmptyStatement(node);
+ }
+ },
+
+ /**
+ * A block statement, i.e., a sequence of statements surrounded by braces.
+ *
+ * interface BlockStatement <: Statement {
+ * type: "BlockStatement";
+ * body: [ Statement ];
+ * }
+ */
+ BlockStatement(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onBlockStatement) {
+ callbacks.onBlockStatement(node);
+ }
+ for (let statement of node.body) {
+ this[statement.type](statement, node, callbacks);
+ }
+ },
+
+ /**
+ * An expression statement, i.e., a statement consisting of a single
+ * expression.
+ *
+ * interface ExpressionStatement <: Statement {
+ * type: "ExpressionStatement";
+ * expression: Expression;
+ * }
+ */
+ ExpressionStatement(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onExpressionStatement) {
+ callbacks.onExpressionStatement(node);
+ }
+ this[node.expression.type](node.expression, node, callbacks);
+ },
+
+ /**
+ * An if statement.
+ *
+ * interface IfStatement <: Statement {
+ * type: "IfStatement";
+ * test: Expression;
+ * consequent: Statement;
+ * alternate: Statement | null;
+ * }
+ */
+ IfStatement(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onIfStatement) {
+ callbacks.onIfStatement(node);
+ }
+ this[node.test.type](node.test, node, callbacks);
+ this[node.consequent.type](node.consequent, node, callbacks);
+ if (node.alternate) {
+ this[node.alternate.type](node.alternate, node, callbacks);
+ }
+ },
+
+ /**
+ * A labeled statement, i.e., a statement prefixed by a break/continue label.
+ *
+ * interface LabeledStatement <: Statement {
+ * type: "LabeledStatement";
+ * label: Identifier;
+ * body: Statement;
+ * }
+ */
+ LabeledStatement(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onLabeledStatement) {
+ callbacks.onLabeledStatement(node);
+ }
+ this[node.label.type](node.label, node, callbacks);
+ this[node.body.type](node.body, node, callbacks);
+ },
+
+ /**
+ * A break statement.
+ *
+ * interface BreakStatement <: Statement {
+ * type: "BreakStatement";
+ * label: Identifier | null;
+ * }
+ */
+ BreakStatement(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onBreakStatement) {
+ callbacks.onBreakStatement(node);
+ }
+ if (node.label) {
+ this[node.label.type](node.label, node, callbacks);
+ }
+ },
+
+ /**
+ * A continue statement.
+ *
+ * interface ContinueStatement <: Statement {
+ * type: "ContinueStatement";
+ * label: Identifier | null;
+ * }
+ */
+ ContinueStatement(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onContinueStatement) {
+ callbacks.onContinueStatement(node);
+ }
+ if (node.label) {
+ this[node.label.type](node.label, node, callbacks);
+ }
+ },
+
+ /**
+ * A with statement.
+ *
+ * interface WithStatement <: Statement {
+ * type: "WithStatement";
+ * object: Expression;
+ * body: Statement;
+ * }
+ */
+ WithStatement(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onWithStatement) {
+ callbacks.onWithStatement(node);
+ }
+ this[node.object.type](node.object, node, callbacks);
+ this[node.body.type](node.body, node, callbacks);
+ },
+
+ /**
+ * A switch statement. The lexical flag is metadata indicating whether the
+ * switch statement contains any unnested let declarations (and therefore
+ * introduces a new lexical scope).
+ *
+ * interface SwitchStatement <: Statement {
+ * type: "SwitchStatement";
+ * discriminant: Expression;
+ * cases: [ SwitchCase ];
+ * lexical: boolean;
+ * }
+ */
+ SwitchStatement(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onSwitchStatement) {
+ callbacks.onSwitchStatement(node);
+ }
+ this[node.discriminant.type](node.discriminant, node, callbacks);
+ for (let _case of node.cases) {
+ this[_case.type](_case, node, callbacks);
+ }
+ },
+
+ /**
+ * A return statement.
+ *
+ * interface ReturnStatement <: Statement {
+ * type: "ReturnStatement";
+ * argument: Expression | null;
+ * }
+ */
+ ReturnStatement(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onReturnStatement) {
+ callbacks.onReturnStatement(node);
+ }
+ if (node.argument) {
+ this[node.argument.type](node.argument, node, callbacks);
+ }
+ },
+
+ /**
+ * A throw statement.
+ *
+ * interface ThrowStatement <: Statement {
+ * type: "ThrowStatement";
+ * argument: Expression;
+ * }
+ */
+ ThrowStatement(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onThrowStatement) {
+ callbacks.onThrowStatement(node);
+ }
+ this[node.argument.type](node.argument, node, callbacks);
+ },
+
+ /**
+ * A try statement.
+ *
+ * interface TryStatement <: Statement {
+ * type: "TryStatement";
+ * block: BlockStatement;
+ * handler: CatchClause | null;
+ * guardedHandlers: [ CatchClause ];
+ * finalizer: BlockStatement | null;
+ * }
+ */
+ TryStatement(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onTryStatement) {
+ callbacks.onTryStatement(node);
+ }
+ this[node.block.type](node.block, node, callbacks);
+ if (node.handler) {
+ this[node.handler.type](node.handler, node, callbacks);
+ }
+ for (let guardedHandler of node.guardedHandlers) {
+ this[guardedHandler.type](guardedHandler, node, callbacks);
+ }
+ if (node.finalizer) {
+ this[node.finalizer.type](node.finalizer, node, callbacks);
+ }
+ },
+
+ /**
+ * A while statement.
+ *
+ * interface WhileStatement <: Statement {
+ * type: "WhileStatement";
+ * test: Expression;
+ * body: Statement;
+ * }
+ */
+ WhileStatement(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onWhileStatement) {
+ callbacks.onWhileStatement(node);
+ }
+ this[node.test.type](node.test, node, callbacks);
+ this[node.body.type](node.body, node, callbacks);
+ },
+
+ /**
+ * A do/while statement.
+ *
+ * interface DoWhileStatement <: Statement {
+ * type: "DoWhileStatement";
+ * body: Statement;
+ * test: Expression;
+ * }
+ */
+ DoWhileStatement(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onDoWhileStatement) {
+ callbacks.onDoWhileStatement(node);
+ }
+ this[node.body.type](node.body, node, callbacks);
+ this[node.test.type](node.test, node, callbacks);
+ },
+
+ /**
+ * A for statement.
+ *
+ * interface ForStatement <: Statement {
+ * type: "ForStatement";
+ * init: VariableDeclaration | Expression | null;
+ * test: Expression | null;
+ * update: Expression | null;
+ * body: Statement;
+ * }
+ */
+ ForStatement(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onForStatement) {
+ callbacks.onForStatement(node);
+ }
+ if (node.init) {
+ this[node.init.type](node.init, node, callbacks);
+ }
+ if (node.test) {
+ this[node.test.type](node.test, node, callbacks);
+ }
+ if (node.update) {
+ this[node.update.type](node.update, node, callbacks);
+ }
+ this[node.body.type](node.body, node, callbacks);
+ },
+
+ /**
+ * A for/in statement, or, if each is true, a for each/in statement.
+ *
+ * interface ForInStatement <: Statement {
+ * type: "ForInStatement";
+ * left: VariableDeclaration | Expression;
+ * right: Expression;
+ * body: Statement;
+ * each: boolean;
+ * }
+ */
+ ForInStatement(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onForInStatement) {
+ callbacks.onForInStatement(node);
+ }
+ this[node.left.type](node.left, node, callbacks);
+ this[node.right.type](node.right, node, callbacks);
+ this[node.body.type](node.body, node, callbacks);
+ },
+
+ /**
+ * A for/of statement.
+ *
+ * interface ForOfStatement <: Statement {
+ * type: "ForOfStatement";
+ * left: VariableDeclaration | Expression;
+ * right: Expression;
+ * body: Statement;
+ * }
+ */
+ ForOfStatement(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onForOfStatement) {
+ callbacks.onForOfStatement(node);
+ }
+ this[node.left.type](node.left, node, callbacks);
+ this[node.right.type](node.right, node, callbacks);
+ this[node.body.type](node.body, node, callbacks);
+ },
+
+ /**
+ * A let statement.
+ *
+ * interface LetStatement <: Statement {
+ * type: "LetStatement";
+ * head: [ { id: Pattern, init: Expression | null } ];
+ * body: Statement;
+ * }
+ */
+ LetStatement(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onLetStatement) {
+ callbacks.onLetStatement(node);
+ }
+ for (let { id, init } of node.head) {
+ this[id.type](id, node, callbacks);
+ if (init) {
+ this[init.type](init, node, callbacks);
+ }
+ }
+ this[node.body.type](node.body, node, callbacks);
+ },
+
+ /**
+ * A debugger statement.
+ *
+ * interface DebuggerStatement <: Statement {
+ * type: "DebuggerStatement";
+ * }
+ */
+ DebuggerStatement(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onDebuggerStatement) {
+ callbacks.onDebuggerStatement(node);
+ }
+ },
+
+ /**
+ * Any declaration node. Note that declarations are considered statements;
+ * this is because declarations can appear in any statement context in the
+ * language recognized by the SpiderMonkey parser.
+ *
+ * interface Declaration <: Statement { }
+ */
+ Declaration(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onDeclaration) {
+ callbacks.onDeclaration(node);
+ }
+ },
+
+ /**
+ * A function declaration.
+ *
+ * interface FunctionDeclaration <: Function, Declaration {
+ * type: "FunctionDeclaration";
+ * id: Identifier;
+ * params: [ Pattern ];
+ * defaults: [ Expression ];
+ * rest: Identifier | null;
+ * body: BlockStatement | Expression;
+ * generator: boolean;
+ * expression: boolean;
+ * }
+ */
+ FunctionDeclaration(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onFunctionDeclaration) {
+ callbacks.onFunctionDeclaration(node);
+ }
+ this[node.id.type](node.id, node, callbacks);
+ for (let param of node.params) {
+ this[param.type](param, node, callbacks);
+ }
+ for (let _default of node.defaults) {
+ if (_default) {
+ this[_default.type](_default, node, callbacks);
+ }
+ }
+ if (node.rest) {
+ this[node.rest.type](node.rest, node, callbacks);
+ }
+ this[node.body.type](node.body, node, callbacks);
+ },
+
+ /**
+ * A variable declaration, via one of var, let, or const.
+ *
+ * interface VariableDeclaration <: Declaration {
+ * type: "VariableDeclaration";
+ * declarations: [ VariableDeclarator ];
+ * kind: "var" | "let" | "const";
+ * }
+ */
+ VariableDeclaration(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onVariableDeclaration) {
+ callbacks.onVariableDeclaration(node);
+ }
+ for (let declaration of node.declarations) {
+ this[declaration.type](declaration, node, callbacks);
+ }
+ },
+
+ /**
+ * A variable declarator.
+ *
+ * interface VariableDeclarator <: Node {
+ * type: "VariableDeclarator";
+ * id: Pattern;
+ * init: Expression | null;
+ * }
+ */
+ VariableDeclarator(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onVariableDeclarator) {
+ callbacks.onVariableDeclarator(node);
+ }
+ this[node.id.type](node.id, node, callbacks);
+ if (node.init) {
+ this[node.init.type](node.init, node, callbacks);
+ }
+ },
+
+ /**
+ * Any expression node. Since the left-hand side of an assignment may be any
+ * expression in general, an expression can also be a pattern.
+ *
+ * interface Expression <: Node, Pattern { }
+ */
+ Expression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onExpression) {
+ callbacks.onExpression(node);
+ }
+ },
+
+ /**
+ * A this expression.
+ *
+ * interface ThisExpression <: Expression {
+ * type: "ThisExpression";
+ * }
+ */
+ ThisExpression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onThisExpression) {
+ callbacks.onThisExpression(node);
+ }
+ },
+
+ /**
+ * An array expression.
+ *
+ * interface ArrayExpression <: Expression {
+ * type: "ArrayExpression";
+ * elements: [ Expression | null ];
+ * }
+ */
+ ArrayExpression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onArrayExpression) {
+ callbacks.onArrayExpression(node);
+ }
+ for (let element of node.elements) {
+ if (element) {
+ this[element.type](element, node, callbacks);
+ }
+ }
+ },
+
+ /**
+ * A spread expression.
+ *
+ * interface SpreadExpression <: Expression {
+ * type: "SpreadExpression";
+ * expression: Expression;
+ * }
+ */
+ SpreadExpression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onSpreadExpression) {
+ callbacks.onSpreadExpression(node);
+ }
+ this[node.expression.type](node.expression, node, callbacks);
+ },
+
+ /**
+ * An object expression. A literal property in an object expression can have
+ * either a string or number as its value. Ordinary property initializers
+ * have a kind value "init"; getters and setters have the kind values "get"
+ * and "set", respectively.
+ *
+ * interface ObjectExpression <: Expression {
+ * type: "ObjectExpression";
+ * properties: [ { key: Literal | Identifier | ComputedName,
+ * value: Expression,
+ * kind: "init" | "get" | "set" } ];
+ * }
+ */
+ ObjectExpression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onObjectExpression) {
+ callbacks.onObjectExpression(node);
+ }
+ for (let { key, value } of node.properties) {
+ this[key.type](key, node, callbacks);
+ this[value.type](value, node, callbacks);
+ }
+ },
+
+ /**
+ * A computed property name in object expression, like in { [a]: b }
+ *
+ * interface ComputedName <: Node {
+ * type: "ComputedName";
+ * name: Expression;
+ * }
+ */
+ ComputedName(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onComputedName) {
+ callbacks.onComputedName(node);
+ }
+ this[node.name.type](node.name, node, callbacks);
+ },
+
+ /**
+ * A function expression.
+ *
+ * interface FunctionExpression <: Function, Expression {
+ * type: "FunctionExpression";
+ * id: Identifier | null;
+ * params: [ Pattern ];
+ * defaults: [ Expression ];
+ * rest: Identifier | null;
+ * body: BlockStatement | Expression;
+ * generator: boolean;
+ * expression: boolean;
+ * }
+ */
+ FunctionExpression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onFunctionExpression) {
+ callbacks.onFunctionExpression(node);
+ }
+ if (node.id) {
+ this[node.id.type](node.id, node, callbacks);
+ }
+ for (let param of node.params) {
+ this[param.type](param, node, callbacks);
+ }
+ for (let _default of node.defaults) {
+ if (_default) {
+ this[_default.type](_default, node, callbacks);
+ }
+ }
+ if (node.rest) {
+ this[node.rest.type](node.rest, node, callbacks);
+ }
+ this[node.body.type](node.body, node, callbacks);
+ },
+
+ /**
+ * An arrow expression.
+ *
+ * interface ArrowFunctionExpression <: Function, Expression {
+ * type: "ArrowFunctionExpression";
+ * params: [ Pattern ];
+ * defaults: [ Expression ];
+ * rest: Identifier | null;
+ * body: BlockStatement | Expression;
+ * generator: boolean;
+ * expression: boolean;
+ * }
+ */
+ ArrowFunctionExpression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onArrowFunctionExpression) {
+ callbacks.onArrowFunctionExpression(node);
+ }
+ for (let param of node.params) {
+ this[param.type](param, node, callbacks);
+ }
+ for (let _default of node.defaults) {
+ if (_default) {
+ this[_default.type](_default, node, callbacks);
+ }
+ }
+ if (node.rest) {
+ this[node.rest.type](node.rest, node, callbacks);
+ }
+ this[node.body.type](node.body, node, callbacks);
+ },
+
+ /**
+ * A sequence expression, i.e., a comma-separated sequence of expressions.
+ *
+ * interface SequenceExpression <: Expression {
+ * type: "SequenceExpression";
+ * expressions: [ Expression ];
+ * }
+ */
+ SequenceExpression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onSequenceExpression) {
+ callbacks.onSequenceExpression(node);
+ }
+ for (let expression of node.expressions) {
+ this[expression.type](expression, node, callbacks);
+ }
+ },
+
+ /**
+ * A unary operator expression.
+ *
+ * interface UnaryExpression <: Expression {
+ * type: "UnaryExpression";
+ * operator: UnaryOperator;
+ * prefix: boolean;
+ * argument: Expression;
+ * }
+ */
+ UnaryExpression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onUnaryExpression) {
+ callbacks.onUnaryExpression(node);
+ }
+ this[node.argument.type](node.argument, node, callbacks);
+ },
+
+ /**
+ * A binary operator expression.
+ *
+ * interface BinaryExpression <: Expression {
+ * type: "BinaryExpression";
+ * operator: BinaryOperator;
+ * left: Expression;
+ * right: Expression;
+ * }
+ */
+ BinaryExpression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onBinaryExpression) {
+ callbacks.onBinaryExpression(node);
+ }
+ this[node.left.type](node.left, node, callbacks);
+ this[node.right.type](node.right, node, callbacks);
+ },
+
+ /**
+ * An assignment operator expression.
+ *
+ * interface AssignmentExpression <: Expression {
+ * type: "AssignmentExpression";
+ * operator: AssignmentOperator;
+ * left: Expression;
+ * right: Expression;
+ * }
+ */
+ AssignmentExpression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onAssignmentExpression) {
+ callbacks.onAssignmentExpression(node);
+ }
+ this[node.left.type](node.left, node, callbacks);
+ this[node.right.type](node.right, node, callbacks);
+ },
+
+ /**
+ * An update (increment or decrement) operator expression.
+ *
+ * interface UpdateExpression <: Expression {
+ * type: "UpdateExpression";
+ * operator: UpdateOperator;
+ * argument: Expression;
+ * prefix: boolean;
+ * }
+ */
+ UpdateExpression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onUpdateExpression) {
+ callbacks.onUpdateExpression(node);
+ }
+ this[node.argument.type](node.argument, node, callbacks);
+ },
+
+ /**
+ * A logical operator expression.
+ *
+ * interface LogicalExpression <: Expression {
+ * type: "LogicalExpression";
+ * operator: LogicalOperator;
+ * left: Expression;
+ * right: Expression;
+ * }
+ */
+ LogicalExpression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onLogicalExpression) {
+ callbacks.onLogicalExpression(node);
+ }
+ this[node.left.type](node.left, node, callbacks);
+ this[node.right.type](node.right, node, callbacks);
+ },
+
+ /**
+ * A conditional expression, i.e., a ternary ?/: expression.
+ *
+ * interface ConditionalExpression <: Expression {
+ * type: "ConditionalExpression";
+ * test: Expression;
+ * alternate: Expression;
+ * consequent: Expression;
+ * }
+ */
+ ConditionalExpression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onConditionalExpression) {
+ callbacks.onConditionalExpression(node);
+ }
+ this[node.test.type](node.test, node, callbacks);
+ this[node.alternate.type](node.alternate, node, callbacks);
+ this[node.consequent.type](node.consequent, node, callbacks);
+ },
+
+ /**
+ * A new expression.
+ *
+ * interface NewExpression <: Expression {
+ * type: "NewExpression";
+ * callee: Expression;
+ * arguments: [ Expression | null ];
+ * }
+ */
+ NewExpression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onNewExpression) {
+ callbacks.onNewExpression(node);
+ }
+ this[node.callee.type](node.callee, node, callbacks);
+ for (let argument of node.arguments) {
+ if (argument) {
+ this[argument.type](argument, node, callbacks);
+ }
+ }
+ },
+
+ /**
+ * A function or method call expression.
+ *
+ * interface CallExpression <: Expression {
+ * type: "CallExpression";
+ * callee: Expression;
+ * arguments: [ Expression | null ];
+ * }
+ */
+ CallExpression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onCallExpression) {
+ callbacks.onCallExpression(node);
+ }
+ this[node.callee.type](node.callee, node, callbacks);
+ for (let argument of node.arguments) {
+ if (argument) {
+ if (!this[argument.type]) {
+ console.error("Unknown parser object:", argument.type);
+ }
+ this[argument.type](argument, node, callbacks);
+ }
+ }
+ },
+
+ /**
+ * A member expression. If computed is true, the node corresponds to a
+ * computed e1[e2] expression and property is an Expression. If computed is
+ * false, the node corresponds to a static e1.x expression and property is an
+ * Identifier.
+ *
+ * interface MemberExpression <: Expression {
+ * type: "MemberExpression";
+ * object: Expression;
+ * property: Identifier | Expression;
+ * computed: boolean;
+ * }
+ */
+ MemberExpression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onMemberExpression) {
+ callbacks.onMemberExpression(node);
+ }
+ this[node.object.type](node.object, node, callbacks);
+ this[node.property.type](node.property, node, callbacks);
+ },
+
+ /**
+ * A yield expression.
+ *
+ * interface YieldExpression <: Expression {
+ * argument: Expression | null;
+ * }
+ */
+ YieldExpression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onYieldExpression) {
+ callbacks.onYieldExpression(node);
+ }
+ if (node.argument) {
+ this[node.argument.type](node.argument, node, callbacks);
+ }
+ },
+
+ /**
+ * An array comprehension. The blocks array corresponds to the sequence of
+ * for and for each blocks. The optional filter expression corresponds to the
+ * final if clause, if present.
+ *
+ * interface ComprehensionExpression <: Expression {
+ * body: Expression;
+ * blocks: [ ComprehensionBlock ];
+ * filter: Expression | null;
+ * }
+ */
+ ComprehensionExpression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onComprehensionExpression) {
+ callbacks.onComprehensionExpression(node);
+ }
+ this[node.body.type](node.body, node, callbacks);
+ for (let block of node.blocks) {
+ this[block.type](block, node, callbacks);
+ }
+ if (node.filter) {
+ this[node.filter.type](node.filter, node, callbacks);
+ }
+ },
+
+ /**
+ * A generator expression. As with array comprehensions, the blocks array
+ * corresponds to the sequence of for and for each blocks, and the optional
+ * filter expression corresponds to the final if clause, if present.
+ *
+ * interface GeneratorExpression <: Expression {
+ * body: Expression;
+ * blocks: [ ComprehensionBlock ];
+ * filter: Expression | null;
+ * }
+ */
+ GeneratorExpression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onGeneratorExpression) {
+ callbacks.onGeneratorExpression(node);
+ }
+ this[node.body.type](node.body, node, callbacks);
+ for (let block of node.blocks) {
+ this[block.type](block, node, callbacks);
+ }
+ if (node.filter) {
+ this[node.filter.type](node.filter, node, callbacks);
+ }
+ },
+
+ /**
+ * A graph expression, aka "sharp literal," such as #1={ self: #1# }.
+ *
+ * interface GraphExpression <: Expression {
+ * index: uint32;
+ * expression: Literal;
+ * }
+ */
+ GraphExpression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onGraphExpression) {
+ callbacks.onGraphExpression(node);
+ }
+ this[node.expression.type](node.expression, node, callbacks);
+ },
+
+ /**
+ * A graph index expression, aka "sharp variable," such as #1#.
+ *
+ * interface GraphIndexExpression <: Expression {
+ * index: uint32;
+ * }
+ */
+ GraphIndexExpression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onGraphIndexExpression) {
+ callbacks.onGraphIndexExpression(node);
+ }
+ },
+
+ /**
+ * A let expression.
+ *
+ * interface LetExpression <: Expression {
+ * type: "LetExpression";
+ * head: [ { id: Pattern, init: Expression | null } ];
+ * body: Expression;
+ * }
+ */
+ LetExpression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onLetExpression) {
+ callbacks.onLetExpression(node);
+ }
+ for (let { id, init } of node.head) {
+ this[id.type](id, node, callbacks);
+ if (init) {
+ this[init.type](init, node, callbacks);
+ }
+ }
+ this[node.body.type](node.body, node, callbacks);
+ },
+
+ /**
+ * Any pattern.
+ *
+ * interface Pattern <: Node { }
+ */
+ Pattern(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onPattern) {
+ callbacks.onPattern(node);
+ }
+ },
+
+ /**
+ * An object-destructuring pattern. A literal property in an object pattern
+ * can have either a string or number as its value.
+ *
+ * interface ObjectPattern <: Pattern {
+ * type: "ObjectPattern";
+ * properties: [ { key: Literal | Identifier, value: Pattern } ];
+ * }
+ */
+ ObjectPattern(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onObjectPattern) {
+ callbacks.onObjectPattern(node);
+ }
+ for (let { key, value } of node.properties) {
+ this[key.type](key, node, callbacks);
+ this[value.type](value, node, callbacks);
+ }
+ },
+
+ /**
+ * An array-destructuring pattern.
+ *
+ * interface ArrayPattern <: Pattern {
+ * type: "ArrayPattern";
+ * elements: [ Pattern | null ];
+ * }
+ */
+ ArrayPattern(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onArrayPattern) {
+ callbacks.onArrayPattern(node);
+ }
+ for (let element of node.elements) {
+ if (element) {
+ this[element.type](element, node, callbacks);
+ }
+ }
+ },
+
+ /**
+ * A case (if test is an Expression) or default (if test is null) clause in
+ * the body of a switch statement.
+ *
+ * interface SwitchCase <: Node {
+ * type: "SwitchCase";
+ * test: Expression | null;
+ * consequent: [ Statement ];
+ * }
+ */
+ SwitchCase(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onSwitchCase) {
+ callbacks.onSwitchCase(node);
+ }
+ if (node.test) {
+ this[node.test.type](node.test, node, callbacks);
+ }
+ for (let consequent of node.consequent) {
+ this[consequent.type](consequent, node, callbacks);
+ }
+ },
+
+ /**
+ * A catch clause following a try block. The optional guard property
+ * corresponds to the optional expression guard on the bound variable.
+ *
+ * interface CatchClause <: Node {
+ * type: "CatchClause";
+ * param: Pattern;
+ * guard: Expression | null;
+ * body: BlockStatement;
+ * }
+ */
+ CatchClause(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onCatchClause) {
+ callbacks.onCatchClause(node);
+ }
+ this[node.param.type](node.param, node, callbacks);
+ if (node.guard) {
+ this[node.guard.type](node.guard, node, callbacks);
+ }
+ this[node.body.type](node.body, node, callbacks);
+ },
+
+ /**
+ * A for or for each block in an array comprehension or generator expression.
+ *
+ * interface ComprehensionBlock <: Node {
+ * left: Pattern;
+ * right: Expression;
+ * each: boolean;
+ * }
+ */
+ ComprehensionBlock(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onComprehensionBlock) {
+ callbacks.onComprehensionBlock(node);
+ }
+ this[node.left.type](node.left, node, callbacks);
+ this[node.right.type](node.right, node, callbacks);
+ },
+
+ /**
+ * An identifier. Note that an identifier may be an expression or a
+ * destructuring pattern.
+ *
+ * interface Identifier <: Node, Expression, Pattern {
+ * type: "Identifier";
+ * name: string;
+ * }
+ */
+ Identifier(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onIdentifier) {
+ callbacks.onIdentifier(node);
+ }
+ },
+
+ /**
+ * A literal token. Note that a literal can be an expression.
+ *
+ * interface Literal <: Node, Expression {
+ * type: "Literal";
+ * value: string | boolean | null | number | RegExp;
+ * }
+ */
+ Literal(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onLiteral) {
+ callbacks.onLiteral(node);
+ }
+ },
+
+ /**
+ * A template string literal.
+ *
+ * interface TemplateLiteral <: Node {
+ * type: "TemplateLiteral";
+ * elements: [ Expression ];
+ * }
+ */
+ TemplateLiteral(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onTemplateLiteral) {
+ callbacks.onTemplateLiteral(node);
+ }
+ for (let element of node.elements) {
+ if (element) {
+ this[element.type](element, node, callbacks);
+ }
+ }
+ }
+};
+
+XPCOMUtils.defineLazyGetter(Parser, "reflectionAPI", () => Reflect);