summaryrefslogtreecommitdiff
path: root/browser/devtools/shared/Parser.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'browser/devtools/shared/Parser.jsm')
-rw-r--r--browser/devtools/shared/Parser.jsm2293
1 files changed, 2293 insertions, 0 deletions
diff --git a/browser/devtools/shared/Parser.jsm b/browser/devtools/shared/Parser.jsm
new file mode 100644
index 000000000..42b32c07c
--- /dev/null
+++ b/browser/devtools/shared/Parser.jsm
@@ -0,0 +1,2293 @@
+/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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 Ci = Components.interfaces;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this,
+ "Reflect", "resource://gre/modules/reflect.jsm");
+
+this.EXPORTED_SYMBOLS = ["Parser"];
+
+/**
+ * A JS parser using the reflection API.
+ */
+this.Parser = function Parser() {
+ this._cache = new Map();
+};
+
+Parser.prototype = {
+ /**
+ * Gets a collection of parser methods for a specified source.
+ *
+ * @param string aUrl [optional]
+ * The source url. The AST nodes will be cached, so you can use this
+ * identifier to avoid parsing the whole source again.
+ * @param string aSource
+ * The source text content.
+ */
+ get: function P_get(aUrl, aSource) {
+ // Try to use the cached AST nodes, to avoid useless parsing operations.
+ if (this._cache.has(aUrl)) {
+ return this._cache.get(aUrl);
+ }
+
+ // 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 (aSource.match(/^\s*</)) {
+ // First non whitespace character is &lt, so most definitely HTML.
+ while (scriptMatch = regexp.exec(aSource)) {
+ scriptMatches.push(scriptMatch[1]); // Contents are captured at index 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(aSource);
+ let length = aSource.length;
+ syntaxTrees.push(new SyntaxTree(nodes, aUrl, length));
+ } catch (e) {
+ log(aUrl, e);
+ }
+ }
+ // Generate the AST nodes for each script.
+ else {
+ for (let script of scriptMatches) {
+ // Reflect.parse throws when encounters a syntax error.
+ try {
+ let nodes = Reflect.parse(script);
+ let offset = aSource.indexOf(script);
+ let length = script.length;
+ syntaxTrees.push(new SyntaxTree(nodes, aUrl, length, offset));
+ } catch (e) {
+ log(aUrl, e);
+ }
+ }
+ }
+
+ let pool = new SyntaxTreesPool(syntaxTrees);
+ this._cache.set(aUrl, pool);
+ return pool;
+ },
+
+ /**
+ * Clears all the parsed sources from cache.
+ */
+ clearCache: function P_clearCache() {
+ this._cache.clear();
+ },
+
+ _cache: null
+};
+
+/**
+ * A pool handling a collection of AST nodes generated by the reflection API.
+ *
+ * @param object aSyntaxTrees
+ * A collection of AST nodes generated for a source.
+ */
+function SyntaxTreesPool(aSyntaxTrees) {
+ this._trees = aSyntaxTrees;
+ this._cache = new Map();
+}
+
+SyntaxTreesPool.prototype = {
+ /**
+ * @see SyntaxTree.prototype.getNamedFunctionDefinitions
+ */
+ getNamedFunctionDefinitions: function STP_getNamedFunctionDefinitions(aSubstring) {
+ return this._call("getNamedFunctionDefinitions", aSubstring);
+ },
+
+ /**
+ * @see SyntaxTree.prototype.getFunctionAtLocation
+ */
+ getFunctionAtLocation: function STP_getFunctionAtLocation(aLine, aColumn) {
+ return this._call("getFunctionAtLocation", [aLine, aColumn]);
+ },
+
+ /**
+ * Finds the offset and length of the script containing the specified offset
+ * relative to its parent source.
+ *
+ * @param number aOffset
+ * The offset relative to the parent source.
+ * @return array
+ * The offset and length relative to the enclosing script.
+ */
+ getScriptInfo: function STP_getScriptInfo(aOffset) {
+ for (let { offset, length } of this._trees) {
+ if (offset <= aOffset && offset + length >= aOffset) {
+ return [offset, length];
+ }
+ }
+ return [-1, -1];
+ },
+
+ /**
+ * Handles a request for all known syntax trees.
+ *
+ * @param string aFunction
+ * The function name to call on the SyntaxTree instances.
+ * @param any aParams
+ * Any kind params to pass to the request function.
+ * @return array
+ * The results given by all known syntax trees.
+ */
+ _call: function STP__call(aFunction, aParams) {
+ let results = [];
+ let requestId = aFunction + aParams; // Cache all the things!
+
+ if (this._cache.has(requestId)) {
+ return this._cache.get(requestId);
+ }
+ for (let syntaxTree of this._trees) {
+ try {
+ results.push({
+ sourceUrl: syntaxTree.url,
+ scriptLength: syntaxTree.length,
+ scriptOffset: syntaxTree.offset,
+ parseResults: syntaxTree[aFunction](aParams)
+ });
+ } 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.
+ log("syntax tree", e);
+ }
+ }
+ this._cache.set(requestId, results);
+ return results;
+ },
+
+ _trees: null,
+ _cache: null
+};
+
+/**
+ * A collection of AST nodes generated by the reflection API.
+ *
+ * @param object aNodes
+ * The AST nodes.
+ * @param string aUrl
+ * The source url.
+ * @param number aLength
+ * The total number of chars of the parsed script in the parent source.
+ * @param number aOffset [optional]
+ * The char offset of the parsed script in the parent source.
+ */
+function SyntaxTree(aNodes, aUrl, aLength, aOffset = 0) {
+ this.AST = aNodes;
+ this.url = aUrl;
+ this.length = aLength;
+ this.offset = aOffset;
+};
+
+SyntaxTree.prototype = {
+ /**
+ * Searches for all function definitions (declarations and expressions)
+ * whose names (or inferred names) contain a string.
+ *
+ * @param string aSubstring
+ * 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: function ST_getNamedFunctionDefinitions(aSubstring) {
+ let lowerCaseToken = aSubstring.toLowerCase();
+ let store = [];
+
+ SyntaxTreeVisitor.walk(this.AST, {
+ /**
+ * Callback invoked for each function declaration node.
+ * @param Node aNode
+ */
+ onFunctionDeclaration: function STW_onFunctionDeclaration(aNode) {
+ let functionName = aNode.id.name;
+ if (functionName.toLowerCase().contains(lowerCaseToken)) {
+ store.push({
+ functionName: functionName,
+ functionLocation: aNode.loc
+ });
+ }
+ },
+
+ /**
+ * Callback invoked for each function expression node.
+ * @param Node aNode
+ */
+ onFunctionExpression: function STW_onFunctionExpression(aNode) {
+ let parent = aNode._parent;
+ let functionName, inferredName, inferredChain, inferredLocation;
+
+ // Function expressions don't necessarily have a name.
+ if (aNode.id) {
+ functionName = aNode.id.name;
+ }
+ // Infer the function's name from an enclosing syntax tree node.
+ if (parent) {
+ let inferredInfo = ParserHelpers.inferFunctionExpressionInfo(aNode);
+ inferredName = inferredInfo.name;
+ inferredChain = inferredInfo.chain;
+ inferredLocation = inferredInfo.loc;
+ }
+ // Current node may be part of a larger assignment expression stack.
+ if (parent.type == "AssignmentExpression") {
+ this.onFunctionExpression(parent);
+ }
+
+ if ((functionName && functionName.toLowerCase().contains(lowerCaseToken)) ||
+ (inferredName && inferredName.toLowerCase().contains(lowerCaseToken))) {
+ store.push({
+ functionName: functionName,
+ functionLocation: aNode.loc,
+ inferredName: inferredName,
+ inferredChain: inferredChain,
+ inferredLocation: inferredLocation
+ });
+ }
+ },
+
+ /**
+ * Callback invoked for each arrow expression node.
+ * @param Node aNode
+ */
+ onArrowExpression: function STW_onArrowExpression(aNode) {
+ let parent = aNode._parent;
+ let inferredName, inferredChain, inferredLocation;
+
+ // Infer the function's name from an enclosing syntax tree node.
+ let inferredInfo = ParserHelpers.inferFunctionExpressionInfo(aNode);
+ inferredName = inferredInfo.name;
+ inferredChain = inferredInfo.chain;
+ inferredLocation = inferredInfo.loc;
+
+ // Current node may be part of a larger assignment expression stack.
+ if (parent.type == "AssignmentExpression") {
+ this.onFunctionExpression(parent);
+ }
+
+ if (inferredName && inferredName.toLowerCase().contains(lowerCaseToken)) {
+ store.push({
+ inferredName: inferredName,
+ inferredChain: inferredChain,
+ inferredLocation: inferredLocation
+ });
+ }
+ }
+ });
+
+ return store;
+ },
+
+ /**
+ * Gets the "new" or "call" expression at the specified location.
+ *
+ * @param number aLine
+ * The line in the source.
+ * @param number aColumn
+ * The column in the source.
+ * @return object
+ * An { functionName, functionLocation } object hash,
+ * or null if nothing is found at the specified location.
+ */
+ getFunctionAtLocation: function STW_getFunctionAtLocation([aLine, aColumn]) {
+ let self = this;
+ let func = null;
+
+ SyntaxTreeVisitor.walk(this.AST, {
+ /**
+ * Callback invoked for each node.
+ * @param Node aNode
+ */
+ onNode: function STW_onNode(aNode) {
+ // Make sure the node is part of a branch that's guaranteed to be
+ // hovered. Otherwise, return true to abruptly halt walking this
+ // syntax tree branch. This is a really efficient optimization.
+ return ParserHelpers.isWithinLines(aNode, aLine);
+ },
+
+ /**
+ * Callback invoked for each identifier node.
+ * @param Node aNode
+ */
+ onIdentifier: function STW_onIdentifier(aNode) {
+ // Make sure the identifier itself is hovered.
+ let hovered = ParserHelpers.isWithinBounds(aNode, aLine, aColumn);
+ if (!hovered) {
+ return;
+ }
+
+ // Make sure the identifier is part of a "new" expression or
+ // "call" expression node.
+ let expression = ParserHelpers.getEnclosingFunctionExpression(aNode);
+ if (!expression) {
+ return;
+ }
+
+ // Found an identifier node that is part of a "new" expression or
+ // "call" expression node. However, it may be an argument, not a callee.
+ if (ParserHelpers.isFunctionCalleeArgument(aNode)) {
+ // It's an argument.
+ if (self.functionIdentifiersCache.has(aNode.name)) {
+ // It's a function as an argument.
+ func = {
+ functionName: aNode.name,
+ functionLocation: aNode.loc || aNode._parent.loc
+ };
+ }
+ return;
+ }
+
+ // Found a valid "new" expression or "call" expression node.
+ func = {
+ functionName: aNode.name,
+ functionLocation: ParserHelpers.getFunctionCalleeInfo(expression).loc
+ };
+
+ // Abruptly halt walking the syntax tree.
+ this.break = true;
+ }
+ });
+
+ return func;
+ },
+
+ /**
+ * Gets all the function identifiers in this syntax tree (both the
+ * function names and their inferred names).
+ *
+ * @return array
+ * An array of strings.
+ */
+ get functionIdentifiersCache() {
+ if (this._functionIdentifiersCache) {
+ return this._functionIdentifiersCache;
+ }
+ let functionDefinitions = this.getNamedFunctionDefinitions("");
+ let functionIdentifiers = new Set();
+
+ for (let { functionName, inferredName } of functionDefinitions) {
+ functionIdentifiers.add(functionName);
+ functionIdentifiers.add(inferredName);
+ }
+ return this._functionIdentifiersCache = functionIdentifiers;
+ },
+
+ AST: null,
+ url: "",
+ length: 0,
+ offset: 0
+};
+
+/**
+ * Parser utility methods.
+ */
+let ParserHelpers = {
+ /**
+ * Checks if a node's bounds contains a specified line.
+ *
+ * @param Node aNode
+ * The node's bounds used as reference.
+ * @param number aLine
+ * The line number to check.
+ * @return boolean
+ * True if the line and column is contained in the node's bounds.
+ */
+ isWithinLines: function PH_isWithinLines(aNode, aLine) {
+ // Not all nodes have location information attached.
+ if (!aNode.loc) {
+ return this.isWithinLines(aNode._parent, aLine);
+ }
+ return aNode.loc.start.line <= aLine && aNode.loc.end.line >= aLine;
+ },
+
+ /**
+ * Checks if a node's bounds contains a specified line and column.
+ *
+ * @param Node aNode
+ * The node's bounds used as reference.
+ * @param number aLine
+ * The line number to check.
+ * @param number aColumn
+ * The column number to check.
+ * @return boolean
+ * True if the line and column is contained in the node's bounds.
+ */
+ isWithinBounds: function PH_isWithinBounds(aNode, aLine, aColumn) {
+ // Not all nodes have location information attached.
+ if (!aNode.loc) {
+ return this.isWithinBounds(aNode._parent, aLine, aColumn);
+ }
+ return aNode.loc.start.line == aLine && aNode.loc.end.line == aLine &&
+ aNode.loc.start.column <= aColumn && aNode.loc.end.column >= aColumn;
+ },
+
+ /**
+ * Try to infer a function expression's name & other details based on the
+ * enclosing VariableDeclarator, AssignmentExpression or ObjectExpression node.
+ *
+ * @param Node aNode
+ * The function expression node to get the name for.
+ * @return object
+ * The inferred function name, or empty string can't infer name,
+ * along with the chain (a generic "context", like a prototype chain)
+ * and location if available.
+ */
+ inferFunctionExpressionInfo: function PH_inferFunctionExpressionInfo(aNode) {
+ let parent = aNode._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: parent.loc
+ };
+ }
+
+ // 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 assigneeChain = this.getAssignmentExpressionAssigneeChain(parent);
+ let assigneeLeaf = assigneeChain.pop();
+ return {
+ name: assigneeLeaf,
+ chain: assigneeChain,
+ loc: parent.left.loc
+ };
+ }
+
+ // 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 propertyDetails = this.getObjectExpressionPropertyKeyForValue(aNode);
+ let propertyChain = this.getObjectExpressionPropertyChain(parent);
+ let propertyLeaf = propertyDetails.name;
+ return {
+ name: propertyLeaf,
+ chain: propertyChain,
+ loc: propertyDetails.loc
+ };
+ }
+
+ // Can't infer the function expression's name.
+ return {
+ name: "",
+ chain: null,
+ loc: null
+ };
+ },
+
+ /**
+ * Gets details about an object expression's property to which a specified
+ * value is assigned. For example, the node returned for the value 42 in
+ * "{ foo: { bar: 42 } }" is "bar".
+ *
+ * @param Node aNode
+ * The value node assigned to a property in an object expression.
+ * @return object
+ * The details about the assignee property node.
+ */
+ getObjectExpressionPropertyKeyForValue:
+ function PH_getObjectExpressionPropertyKeyForValue(aNode) {
+ let parent = aNode._parent;
+ if (parent.type != "ObjectExpression") {
+ return null;
+ }
+ for (let property of parent.properties) {
+ if (property.value == aNode) {
+ return property.key;
+ }
+ }
+ },
+
+ /**
+ * Gets an object expression property chain to its parent variable declarator.
+ * For example, the chain to "baz" in "foo = { bar: { baz: { } } }" is
+ * ["foo", "bar", "baz"].
+ *
+ * @param Node aNode
+ * 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:
+ function PH_getObjectExpressionPropertyChain(aNode, aStore = []) {
+ switch (aNode.type) {
+ case "ObjectExpression":
+ this.getObjectExpressionPropertyChain(aNode._parent, aStore);
+
+ let propertyDetails = this.getObjectExpressionPropertyKeyForValue(aNode);
+ if (propertyDetails) {
+ aStore.push(this.getObjectExpressionPropertyKeyForValue(aNode).name);
+ }
+ break;
+ // Handle "foo.bar = { ... }" since it's commonly used when defining an
+ // object's prototype methods; for example: "Foo.prototype = { ... }".
+ case "AssignmentExpression":
+ this.getAssignmentExpressionAssigneeChain(aNode, aStore);
+ break;
+ // Additionally handle stuff like "foo = bar.baz({ ... })", because it's
+ // commonly used in prototype-based inheritance in many libraries;
+ // for example: "Foo.Bar = Baz.extend({ ... })".
+ case "NewExpression":
+ case "CallExpression":
+ this.getObjectExpressionPropertyChain(aNode._parent, aStore);
+ break;
+ // End of the chain.
+ case "VariableDeclarator":
+ aStore.push(aNode.id.name);
+ break;
+ }
+ return aStore;
+ },
+
+ /**
+ * Gets the assignee property chain in an assignment expression.
+ * For example, the chain in "foo.bar.baz = 42" is ["foo", "bar", "baz"].
+ *
+ * @param Node aNode
+ * The assignment expression node to begin the scan from.
+ * @param array aStore
+ * The chain to store the nodes into.
+ * @param array aStore [optional]
+ * The chain to store the nodes into.
+ * @return array
+ * The full assignee chain, as strings.
+ */
+ getAssignmentExpressionAssigneeChain:
+ function PH_getAssignmentExpressionAssigneeChain(aNode, aStore = []) {
+ switch (aNode.type) {
+ case "AssignmentExpression":
+ this.getAssignmentExpressionAssigneeChain(aNode.left, aStore);
+ break;
+ case "MemberExpression":
+ this.getAssignmentExpressionAssigneeChain(aNode.object, aStore);
+ this.getAssignmentExpressionAssigneeChain(aNode.property, aStore);
+ break;
+ case "ThisExpression":
+ // Such expressions may appear in an assignee chain, for example
+ // "this.foo.bar = baz", however it seems better to ignore such nodes
+ // and limit the chain to ["foo", "bar"].
+ break;
+ case "Identifier":
+ aStore.push(aNode.name);
+ break;
+ }
+ return aStore;
+ },
+
+ /**
+ * Gets the "new" expression or "call" expression containing the specified
+ * node. If the node is not enclosed in either of these expression types,
+ * null is returned.
+ *
+ * @param Node aNode
+ * The child node of an enclosing "new" expression or "call" expression.
+ * @return Node
+ * The enclosing "new" expression or "call" expression node, or
+ * null if nothing is found.
+ */
+ getEnclosingFunctionExpression:
+ function PH_getEnclosingFunctionExpression(aNode) {
+ switch (aNode.type) {
+ case "NewExpression":
+ case "CallExpression":
+ return aNode;
+ case "MemberExpression":
+ case "Identifier":
+ return this.getEnclosingFunctionExpression(aNode._parent);
+ default:
+ return null;
+ }
+ },
+
+ /**
+ * Gets the name and { line, column } location of a "new" expression or
+ * "call" expression's callee node.
+ *
+ * @param Node aNode
+ * The "new" expression or "call" expression to get the callee info for.
+ * @return object
+ * An object containing the name and location as properties, or
+ * null if nothing is found.
+ */
+ getFunctionCalleeInfo: function PH_getFunctionCalleeInfo(aNode) {
+ switch (aNode.type) {
+ case "NewExpression":
+ case "CallExpression":
+ return this.getFunctionCalleeInfo(aNode.callee);
+ case "MemberExpression":
+ return this.getFunctionCalleeInfo(aNode.property);
+ case "Identifier":
+ return {
+ name: aNode.name,
+ loc: aNode.loc || (aNode._parent || {}).loc
+ };
+ default:
+ return null;
+ }
+ },
+
+ /**
+ * Determines if an identifier node is part of a "new" expression or
+ * "call" expression's callee arguments.
+ *
+ * @param Node aNode
+ * The node to determine if part of a function's arguments.
+ * @return boolean
+ * True if the identifier is an argument, false otherwise.
+ */
+ isFunctionCalleeArgument: function PH_isFunctionCalleeArgument(aNode) {
+ if (!aNode._parent) {
+ return false;
+ }
+ switch (aNode._parent.type) {
+ case "NewExpression":
+ case "CallExpression":
+ return aNode._parent.arguments.indexOf(aNode) != -1;
+ default:
+ return this.isFunctionCalleeArgument(aNode._parent);
+ }
+ }
+};
+
+/**
+ * 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;
+ * }
+ */
+let SyntaxTreeVisitor = {
+ /**
+ * Walks a syntax tree.
+ *
+ * @param object aTree
+ * The AST nodes generated by the reflection API
+ * @param object aCallbacks
+ * A map of all the callbacks to invoke when passing through certain
+ * types of noes (e.g: onFunctionDeclaration, onBlockStatement etc.).
+ */
+ walk: function STV_walk(aTree, aCallbacks) {
+ this[aTree.type](aTree, aCallbacks);
+ },
+
+ /**
+ * 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: function STV_Program(aNode, aCallbacks) {
+ if (aCallbacks.onProgram) {
+ aCallbacks.onProgram(aNode);
+ }
+ for (let statement of aNode.body) {
+ this[statement.type](statement, aNode, aCallbacks);
+ }
+ },
+
+ /**
+ * Any statement.
+ *
+ * interface Statement <: Node { }
+ */
+ Statement: function STV_Statement(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onStatement) {
+ aCallbacks.onStatement(aNode);
+ }
+ },
+
+ /**
+ * An empty statement, i.e., a solitary semicolon.
+ *
+ * interface EmptyStatement <: Statement {
+ * type: "EmptyStatement";
+ * }
+ */
+ EmptyStatement: function STV_EmptyStatement(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onEmptyStatement) {
+ aCallbacks.onEmptyStatement(aNode);
+ }
+ },
+
+ /**
+ * A block statement, i.e., a sequence of statements surrounded by braces.
+ *
+ * interface BlockStatement <: Statement {
+ * type: "BlockStatement";
+ * body: [ Statement ];
+ * }
+ */
+ BlockStatement: function STV_BlockStatement(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onBlockStatement) {
+ aCallbacks.onBlockStatement(aNode);
+ }
+ for (let statement of aNode.body) {
+ this[statement.type](statement, aNode, aCallbacks);
+ }
+ },
+
+ /**
+ * An expression statement, i.e., a statement consisting of a single expression.
+ *
+ * interface ExpressionStatement <: Statement {
+ * type: "ExpressionStatement";
+ * expression: Expression;
+ * }
+ */
+ ExpressionStatement: function STV_ExpressionStatement(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onExpressionStatement) {
+ aCallbacks.onExpressionStatement(aNode);
+ }
+ this[aNode.expression.type](aNode.expression, aNode, aCallbacks);
+ },
+
+ /**
+ * An if statement.
+ *
+ * interface IfStatement <: Statement {
+ * type: "IfStatement";
+ * test: Expression;
+ * consequent: Statement;
+ * alternate: Statement | null;
+ * }
+ */
+ IfStatement: function STV_IfStatement(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onIfStatement) {
+ aCallbacks.onIfStatement(aNode);
+ }
+ this[aNode.test.type](aNode.test, aNode, aCallbacks);
+ this[aNode.consequent.type](aNode.consequent, aNode, aCallbacks);
+ if (aNode.alternate) {
+ this[aNode.alternate.type](aNode.alternate, aNode, aCallbacks);
+ }
+ },
+
+ /**
+ * A labeled statement, i.e., a statement prefixed by a break/continue label.
+ *
+ * interface LabeledStatement <: Statement {
+ * type: "LabeledStatement";
+ * label: Identifier;
+ * body: Statement;
+ * }
+ */
+ LabeledStatement: function STV_LabeledStatement(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onLabeledStatement) {
+ aCallbacks.onLabeledStatement(aNode);
+ }
+ this[aNode.label.type](aNode.label, aNode, aCallbacks);
+ this[aNode.body.type](aNode.body, aNode, aCallbacks);
+ },
+
+ /**
+ * A break statement.
+ *
+ * interface BreakStatement <: Statement {
+ * type: "BreakStatement";
+ * label: Identifier | null;
+ * }
+ */
+ BreakStatement: function STV_BreakStatement(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onBreakStatement) {
+ aCallbacks.onBreakStatement(aNode);
+ }
+ if (aNode.label) {
+ this[aNode.label.type](aNode.label, aNode, aCallbacks);
+ }
+ },
+
+ /**
+ * A continue statement.
+ *
+ * interface ContinueStatement <: Statement {
+ * type: "ContinueStatement";
+ * label: Identifier | null;
+ * }
+ */
+ ContinueStatement: function STV_ContinueStatement(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onContinueStatement) {
+ aCallbacks.onContinueStatement(aNode);
+ }
+ if (aNode.label) {
+ this[aNode.label.type](aNode.label, aNode, aCallbacks);
+ }
+ },
+
+ /**
+ * A with statement.
+ *
+ * interface WithStatement <: Statement {
+ * type: "WithStatement";
+ * object: Expression;
+ * body: Statement;
+ * }
+ */
+ WithStatement: function STV_WithStatement(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onWithStatement) {
+ aCallbacks.onWithStatement(aNode);
+ }
+ this[aNode.object.type](aNode.object, aNode, aCallbacks);
+ this[aNode.body.type](aNode.body, aNode, aCallbacks);
+ },
+
+ /**
+ * 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: function STV_SwitchStatement(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onSwitchStatement) {
+ aCallbacks.onSwitchStatement(aNode);
+ }
+ this[aNode.discriminant.type](aNode.discriminant, aNode, aCallbacks);
+ for (let _case of aNode.cases) {
+ this[_case.type](_case, aNode, aCallbacks);
+ }
+ },
+
+ /**
+ * A return statement.
+ *
+ * interface ReturnStatement <: Statement {
+ * type: "ReturnStatement";
+ * argument: Expression | null;
+ * }
+ */
+ ReturnStatement: function STV_ReturnStatement(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onReturnStatement) {
+ aCallbacks.onReturnStatement(aNode);
+ }
+ if (aNode.argument) {
+ this[aNode.argument.type](aNode.argument, aNode, aCallbacks);
+ }
+ },
+
+ /**
+ * A throw statement.
+ *
+ * interface ThrowStatement <: Statement {
+ * type: "ThrowStatement";
+ * argument: Expression;
+ * }
+ */
+ ThrowStatement: function STV_ThrowStatement(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onThrowStatement) {
+ aCallbacks.onThrowStatement(aNode);
+ }
+ this[aNode.argument.type](aNode.argument, aNode, aCallbacks);
+ },
+
+ /**
+ * A try statement.
+ *
+ * interface TryStatement <: Statement {
+ * type: "TryStatement";
+ * block: BlockStatement;
+ * handler: CatchClause | null;
+ * guardedHandlers: [ CatchClause ];
+ * finalizer: BlockStatement | null;
+ * }
+ */
+ TryStatement: function STV_TryStatement(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onTryStatement) {
+ aCallbacks.onTryStatement(aNode);
+ }
+ this[aNode.block.type](aNode.block, aNode, aCallbacks);
+ if (aNode.handler) {
+ this[aNode.handler.type](aNode.handler, aNode, aCallbacks);
+ }
+ for (let guardedHandler of aNode.guardedHandlers) {
+ this[guardedHandler.type](guardedHandler, aNode, aCallbacks);
+ }
+ if (aNode.finalizer) {
+ this[aNode.finalizer.type](aNode.finalizer, aNode, aCallbacks);
+ }
+ },
+
+ /**
+ * A while statement.
+ *
+ * interface WhileStatement <: Statement {
+ * type: "WhileStatement";
+ * test: Expression;
+ * body: Statement;
+ * }
+ */
+ WhileStatement: function STV_WhileStatement(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onWhileStatement) {
+ aCallbacks.onWhileStatement(aNode);
+ }
+ this[aNode.test.type](aNode.test, aNode, aCallbacks);
+ this[aNode.body.type](aNode.body, aNode, aCallbacks);
+ },
+
+ /**
+ * A do/while statement.
+ *
+ * interface DoWhileStatement <: Statement {
+ * type: "DoWhileStatement";
+ * body: Statement;
+ * test: Expression;
+ * }
+ */
+ DoWhileStatement: function STV_DoWhileStatement(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onDoWhileStatement) {
+ aCallbacks.onDoWhileStatement(aNode);
+ }
+ this[aNode.body.type](aNode.body, aNode, aCallbacks);
+ this[aNode.test.type](aNode.test, aNode, aCallbacks);
+ },
+
+ /**
+ * A for statement.
+ *
+ * interface ForStatement <: Statement {
+ * type: "ForStatement";
+ * init: VariableDeclaration | Expression | null;
+ * test: Expression | null;
+ * update: Expression | null;
+ * body: Statement;
+ * }
+ */
+ ForStatement: function STV_ForStatement(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onForStatement) {
+ aCallbacks.onForStatement(aNode);
+ }
+ if (aNode.init) {
+ this[aNode.init.type](aNode.init, aNode, aCallbacks);
+ }
+ if (aNode.test) {
+ this[aNode.test.type](aNode.test, aNode, aCallbacks);
+ }
+ if (aNode.update) {
+ this[aNode.update.type](aNode.update, aNode, aCallbacks);
+ }
+ this[aNode.body.type](aNode.body, aNode, aCallbacks);
+ },
+
+ /**
+ * 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: function STV_ForInStatement(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onForInStatement) {
+ aCallbacks.onForInStatement(aNode);
+ }
+ this[aNode.left.type](aNode.left, aNode, aCallbacks);
+ this[aNode.right.type](aNode.right, aNode, aCallbacks);
+ this[aNode.body.type](aNode.body, aNode, aCallbacks);
+ },
+
+ /**
+ * A for/of statement.
+ *
+ * interface ForOfStatement <: Statement {
+ * type: "ForOfStatement";
+ * left: VariableDeclaration | Expression;
+ * right: Expression;
+ * body: Statement;
+ * }
+ */
+ ForOfStatement: function STV_ForOfStatement(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onForOfStatement) {
+ aCallbacks.onForOfStatement(aNode);
+ }
+ this[aNode.left.type](aNode.left, aNode, aCallbacks);
+ this[aNode.right.type](aNode.right, aNode, aCallbacks);
+ this[aNode.body.type](aNode.body, aNode, aCallbacks);
+ },
+
+ /**
+ * A let statement.
+ *
+ * interface LetStatement <: Statement {
+ * type: "LetStatement";
+ * head: [ { id: Pattern, init: Expression | null } ];
+ * body: Statement;
+ * }
+ */
+ LetStatement: function STV_LetStatement(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onLetStatement) {
+ aCallbacks.onLetStatement(aNode);
+ }
+ for (let { id, init } of aNode.head) {
+ this[id.type](id, aNode, aCallbacks);
+ if (init) {
+ this[init.type](init, aNode, aCallbacks);
+ }
+ }
+ this[aNode.body.type](aNode.body, aNode, aCallbacks);
+ },
+
+ /**
+ * A debugger statement.
+ *
+ * interface DebuggerStatement <: Statement {
+ * type: "DebuggerStatement";
+ * }
+ */
+ DebuggerStatement: function STV_DebuggerStatement(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onDebuggerStatement) {
+ aCallbacks.onDebuggerStatement(aNode);
+ }
+ },
+
+ /**
+ * 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: function STV_Declaration(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onDeclaration) {
+ aCallbacks.onDeclaration(aNode);
+ }
+ },
+
+ /**
+ * 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: function STV_FunctionDeclaration(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onFunctionDeclaration) {
+ aCallbacks.onFunctionDeclaration(aNode);
+ }
+ this[aNode.id.type](aNode.id, aNode, aCallbacks);
+ for (let param of aNode.params) {
+ this[param.type](param, aNode, aCallbacks);
+ }
+ for (let _default of aNode.defaults) {
+ this[_default.type](_default, aNode, aCallbacks);
+ }
+ if (aNode.rest) {
+ this[aNode.rest.type](aNode.rest, aNode, aCallbacks);
+ }
+ this[aNode.body.type](aNode.body, aNode, aCallbacks);
+ },
+
+ /**
+ * A variable declaration, via one of var, let, or const.
+ *
+ * interface VariableDeclaration <: Declaration {
+ * type: "VariableDeclaration";
+ * declarations: [ VariableDeclarator ];
+ * kind: "var" | "let" | "const";
+ * }
+ */
+ VariableDeclaration: function STV_VariableDeclaration(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onVariableDeclaration) {
+ aCallbacks.onVariableDeclaration(aNode);
+ }
+ for (let declaration of aNode.declarations) {
+ this[declaration.type](declaration, aNode, aCallbacks);
+ }
+ },
+
+ /**
+ * A variable declarator.
+ *
+ * interface VariableDeclarator <: Node {
+ * type: "VariableDeclarator";
+ * id: Pattern;
+ * init: Expression | null;
+ * }
+ */
+ VariableDeclarator: function STV_VariableDeclarator(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onVariableDeclarator) {
+ aCallbacks.onVariableDeclarator(aNode);
+ }
+ this[aNode.id.type](aNode.id, aNode, aCallbacks);
+ if (aNode.init) {
+ this[aNode.init.type](aNode.init, aNode, aCallbacks);
+ }
+ },
+
+ /**
+ * 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: function STV_Expression(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onExpression) {
+ aCallbacks.onExpression(aNode);
+ }
+ },
+
+ /**
+ * A this expression.
+ *
+ * interface ThisExpression <: Expression {
+ * type: "ThisExpression";
+ * }
+ */
+ ThisExpression: function STV_ThisExpression(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onThisExpression) {
+ aCallbacks.onThisExpression(aNode);
+ }
+ },
+
+ /**
+ * An array expression.
+ *
+ * interface ArrayExpression <: Expression {
+ * type: "ArrayExpression";
+ * elements: [ Expression | null ];
+ * }
+ */
+ ArrayExpression: function STV_ArrayExpression(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onArrayExpression) {
+ aCallbacks.onArrayExpression(aNode);
+ }
+ for (let element of aNode.elements) {
+ if (element) {
+ this[element.type](element, aNode, aCallbacks);
+ }
+ }
+ },
+
+ /**
+ * 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,
+ * value: Expression,
+ * kind: "init" | "get" | "set" } ];
+ * }
+ */
+ ObjectExpression: function STV_ObjectExpression(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onObjectExpression) {
+ aCallbacks.onObjectExpression(aNode);
+ }
+ for (let { key, value } of aNode.properties) {
+ this[key.type](key, aNode, aCallbacks);
+ this[value.type](value, aNode, aCallbacks);
+ }
+ },
+
+ /**
+ * 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: function STV_FunctionExpression(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onFunctionExpression) {
+ aCallbacks.onFunctionExpression(aNode);
+ }
+ if (aNode.id) {
+ this[aNode.id.type](aNode.id, aNode, aCallbacks);
+ }
+ for (let param of aNode.params) {
+ this[param.type](param, aNode, aCallbacks);
+ }
+ for (let _default of aNode.defaults) {
+ this[_default.type](_default, aNode, aCallbacks);
+ }
+ if (aNode.rest) {
+ this[aNode.rest.type](aNode.rest, aNode, aCallbacks);
+ }
+ this[aNode.body.type](aNode.body, aNode, aCallbacks);
+ },
+
+ /**
+ * An arrow expression.
+ *
+ * interface ArrowExpression <: Function, Expression {
+ * type: "ArrowExpression";
+ * params: [ Pattern ];
+ * defaults: [ Expression ];
+ * rest: Identifier | null;
+ * body: BlockStatement | Expression;
+ * generator: boolean;
+ * expression: boolean;
+ * }
+ */
+ ArrowExpression: function STV_ArrowExpression(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onArrowExpression) {
+ aCallbacks.onArrowExpression(aNode);
+ }
+ for (let param of aNode.params) {
+ this[param.type](param, aNode, aCallbacks);
+ }
+ for (let _default of aNode.defaults) {
+ this[_default.type](_default, aNode, aCallbacks);
+ }
+ if (aNode.rest) {
+ this[aNode.rest.type](aNode.rest, aNode, aCallbacks);
+ }
+ this[aNode.body.type](aNode.body, aNode, aCallbacks);
+ },
+
+ /**
+ * A sequence expression, i.e., a comma-separated sequence of expressions.
+ *
+ * interface SequenceExpression <: Expression {
+ * type: "SequenceExpression";
+ * expressions: [ Expression ];
+ * }
+ */
+ SequenceExpression: function STV_SequenceExpression(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onSequenceExpression) {
+ aCallbacks.onSequenceExpression(aNode);
+ }
+ for (let expression of aNode.expressions) {
+ this[expression.type](expression, aNode, aCallbacks);
+ }
+ },
+
+ /**
+ * A unary operator expression.
+ *
+ * interface UnaryExpression <: Expression {
+ * type: "UnaryExpression";
+ * operator: UnaryOperator;
+ * prefix: boolean;
+ * argument: Expression;
+ * }
+ */
+ UnaryExpression: function STV_UnaryExpression(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onUnaryExpression) {
+ aCallbacks.onUnaryExpression(aNode);
+ }
+ this[aNode.argument.type](aNode.argument, aNode, aCallbacks);
+ },
+
+ /**
+ * A binary operator expression.
+ *
+ * interface BinaryExpression <: Expression {
+ * type: "BinaryExpression";
+ * operator: BinaryOperator;
+ * left: Expression;
+ * right: Expression;
+ * }
+ */
+ BinaryExpression: function STV_BinaryExpression(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onBinaryExpression) {
+ aCallbacks.onBinaryExpression(aNode);
+ }
+ this[aNode.left.type](aNode.left, aNode, aCallbacks);
+ this[aNode.right.type](aNode.right, aNode, aCallbacks);
+ },
+
+ /**
+ * An assignment operator expression.
+ *
+ * interface AssignmentExpression <: Expression {
+ * type: "AssignmentExpression";
+ * operator: AssignmentOperator;
+ * left: Expression;
+ * right: Expression;
+ * }
+ */
+ AssignmentExpression: function STV_AssignmentExpression(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onAssignmentExpression) {
+ aCallbacks.onAssignmentExpression(aNode);
+ }
+ this[aNode.left.type](aNode.left, aNode, aCallbacks);
+ this[aNode.right.type](aNode.right, aNode, aCallbacks);
+ },
+
+ /**
+ * An update (increment or decrement) operator expression.
+ *
+ * interface UpdateExpression <: Expression {
+ * type: "UpdateExpression";
+ * operator: UpdateOperator;
+ * argument: Expression;
+ * prefix: boolean;
+ * }
+ */
+ UpdateExpression: function STV_UpdateExpression(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onUpdateExpression) {
+ aCallbacks.onUpdateExpression(aNode);
+ }
+ this[aNode.argument.type](aNode.argument, aNode, aCallbacks);
+ },
+
+ /**
+ * A logical operator expression.
+ *
+ * interface LogicalExpression <: Expression {
+ * type: "LogicalExpression";
+ * operator: LogicalOperator;
+ * left: Expression;
+ * right: Expression;
+ * }
+ */
+ LogicalExpression: function STV_LogicalExpression(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onLogicalExpression) {
+ aCallbacks.onLogicalExpression(aNode);
+ }
+ this[aNode.left.type](aNode.left, aNode, aCallbacks);
+ this[aNode.right.type](aNode.right, aNode, aCallbacks);
+ },
+
+ /**
+ * A conditional expression, i.e., a ternary ?/: expression.
+ *
+ * interface ConditionalExpression <: Expression {
+ * type: "ConditionalExpression";
+ * test: Expression;
+ * alternate: Expression;
+ * consequent: Expression;
+ * }
+ */
+ ConditionalExpression: function STV_ConditionalExpression(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onConditionalExpression) {
+ aCallbacks.onConditionalExpression(aNode);
+ }
+ this[aNode.test.type](aNode.test, aNode, aCallbacks);
+ this[aNode.alternate.type](aNode.alternate, aNode, aCallbacks);
+ this[aNode.consequent.type](aNode.consequent, aNode, aCallbacks);
+ },
+
+ /**
+ * A new expression.
+ *
+ * interface NewExpression <: Expression {
+ * type: "NewExpression";
+ * callee: Expression;
+ * arguments: [ Expression | null ];
+ * }
+ */
+ NewExpression: function STV_NewExpression(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onNewExpression) {
+ aCallbacks.onNewExpression(aNode);
+ }
+ this[aNode.callee.type](aNode.callee, aNode, aCallbacks);
+ for (let argument of aNode.arguments) {
+ if (argument) {
+ this[argument.type](argument, aNode, aCallbacks);
+ }
+ }
+ },
+
+ /**
+ * A function or method call expression.
+ *
+ * interface CallExpression <: Expression {
+ * type: "CallExpression";
+ * callee: Expression;
+ * arguments: [ Expression | null ];
+ * }
+ */
+ CallExpression: function STV_CallExpression(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onCallExpression) {
+ aCallbacks.onCallExpression(aNode);
+ }
+ this[aNode.callee.type](aNode.callee, aNode, aCallbacks);
+ for (let argument of aNode.arguments) {
+ if (argument) {
+ this[argument.type](argument, aNode, aCallbacks);
+ }
+ }
+ },
+
+ /**
+ * 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: function STV_MemberExpression(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onMemberExpression) {
+ aCallbacks.onMemberExpression(aNode);
+ }
+ this[aNode.object.type](aNode.object, aNode, aCallbacks);
+ this[aNode.property.type](aNode.property, aNode, aCallbacks);
+ },
+
+ /**
+ * A yield expression.
+ *
+ * interface YieldExpression <: Expression {
+ * argument: Expression | null;
+ * }
+ */
+ YieldExpression: function STV_YieldExpression(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onYieldExpression) {
+ aCallbacks.onYieldExpression(aNode);
+ }
+ if (aNode.argument) {
+ this[aNode.argument.type](aNode.argument, aNode, aCallbacks);
+ }
+ },
+
+ /**
+ * 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: function STV_ComprehensionExpression(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onComprehensionExpression) {
+ aCallbacks.onComprehensionExpression(aNode);
+ }
+ this[aNode.body.type](aNode.body, aNode, aCallbacks);
+ for (let block of aNode.blocks) {
+ this[block.type](block, aNode, aCallbacks);
+ }
+ if (aNode.filter) {
+ this[aNode.filter.type](aNode.filter, aNode, aCallbacks);
+ }
+ },
+
+ /**
+ * 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: function STV_GeneratorExpression(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onGeneratorExpression) {
+ aCallbacks.onGeneratorExpression(aNode);
+ }
+ this[aNode.body.type](aNode.body, aNode, aCallbacks);
+ for (let block of aNode.blocks) {
+ this[block.type](block, aNode, aCallbacks);
+ }
+ if (aNode.filter) {
+ this[aNode.filter.type](aNode.filter, aNode, aCallbacks);
+ }
+ },
+
+ /**
+ * A graph expression, aka "sharp literal," such as #1={ self: #1# }.
+ *
+ * interface GraphExpression <: Expression {
+ * index: uint32;
+ * expression: Literal;
+ * }
+ */
+ GraphExpression: function STV_GraphExpression(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onGraphExpression) {
+ aCallbacks.onGraphExpression(aNode);
+ }
+ this[aNode.expression.type](aNode.expression, aNode, aCallbacks);
+ },
+
+ /**
+ * A graph index expression, aka "sharp variable," such as #1#.
+ *
+ * interface GraphIndexExpression <: Expression {
+ * index: uint32;
+ * }
+ */
+ GraphIndexExpression: function STV_GraphIndexExpression(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onGraphIndexExpression) {
+ aCallbacks.onGraphIndexExpression(aNode);
+ }
+ },
+
+ /**
+ * A let expression.
+ *
+ * interface LetExpression <: Expression {
+ * type: "LetExpression";
+ * head: [ { id: Pattern, init: Expression | null } ];
+ * body: Expression;
+ * }
+ */
+ LetExpression: function STV_LetExpression(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onLetExpression) {
+ aCallbacks.onLetExpression(aNode);
+ }
+ for (let { id, init } of aNode.head) {
+ this[id.type](id, aNode, aCallbacks);
+ if (init) {
+ this[init.type](init, aNode, aCallbacks);
+ }
+ }
+ this[aNode.body.type](aNode.body, aNode, aCallbacks);
+ },
+
+ /**
+ * Any pattern.
+ *
+ * interface Pattern <: Node { }
+ */
+ Pattern: function STV_Pattern(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onPattern) {
+ aCallbacks.onPattern(aNode);
+ }
+ },
+
+ /**
+ * 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: function STV_ObjectPattern(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onObjectPattern) {
+ aCallbacks.onObjectPattern(aNode);
+ }
+ for (let { key, value } of aNode.properties) {
+ this[key.type](key, aNode, aCallbacks);
+ this[value.type](value, aNode, aCallbacks);
+ }
+ },
+
+ /**
+ * An array-destructuring pattern.
+ *
+ * interface ArrayPattern <: Pattern {
+ * type: "ArrayPattern";
+ * elements: [ Pattern | null ];
+ * }
+ */
+ ArrayPattern: function STV_ArrayPattern(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onArrayPattern) {
+ aCallbacks.onArrayPattern(aNode);
+ }
+ for (let element of aNode.elements) {
+ if (element) {
+ this[element.type](element, aNode, aCallbacks);
+ }
+ }
+ },
+
+ /**
+ * 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: function STV_SwitchCase(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onSwitchCase) {
+ aCallbacks.onSwitchCase(aNode);
+ }
+ if (aNode.test) {
+ this[aNode.test.type](aNode.test, aNode, aCallbacks);
+ }
+ for (let consequent of aNode.consequent) {
+ this[consequent.type](consequent, aNode, aCallbacks);
+ }
+ },
+
+ /**
+ * 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: function STV_CatchClause(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onCatchClause) {
+ aCallbacks.onCatchClause(aNode);
+ }
+ this[aNode.param.type](aNode.param, aNode, aCallbacks);
+ if (aNode.guard) {
+ this[aNode.guard.type](aNode.guard, aNode, aCallbacks);
+ }
+ this[aNode.body.type](aNode.body, aNode, aCallbacks);
+ },
+
+ /**
+ * A for or for each block in an array comprehension or generator expression.
+ *
+ * interface ComprehensionBlock <: Node {
+ * left: Pattern;
+ * right: Expression;
+ * each: boolean;
+ * }
+ */
+ ComprehensionBlock: function STV_ComprehensionBlock(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onComprehensionBlock) {
+ aCallbacks.onComprehensionBlock(aNode);
+ }
+ this[aNode.left.type](aNode.left, aNode, aCallbacks);
+ this[aNode.right.type](aNode.right, aNode, aCallbacks);
+ },
+
+ /**
+ * An identifier. Note that an identifier may be an expression or a
+ * destructuring pattern.
+ *
+ * interface Identifier <: Node, Expression, Pattern {
+ * type: "Identifier";
+ * name: string;
+ * }
+ */
+ Identifier: function STV_Identifier(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onIdentifier) {
+ aCallbacks.onIdentifier(aNode);
+ }
+ },
+
+ /**
+ * A literal token. Note that a literal can be an expression.
+ *
+ * interface Literal <: Node, Expression {
+ * type: "Literal";
+ * value: string | boolean | null | number | RegExp;
+ * }
+ */
+ Literal: function STV_Literal(aNode, aParent, aCallbacks) {
+ aNode._parent = aParent;
+
+ if (this.break) {
+ return;
+ }
+ if (aCallbacks.onNode) {
+ if (aCallbacks.onNode(aNode, aParent) === false) {
+ return;
+ }
+ }
+ if (aCallbacks.onLiteral) {
+ aCallbacks.onLiteral(aNode);
+ }
+ }
+};
+
+/**
+ * Logs a warning.
+ *
+ * @param string aStr
+ * The message to be displayed.
+ * @param Exception aEx
+ * The thrown exception.
+ */
+function log(aStr, aEx) {
+ let msg = "Warning: " + aStr + ", " + aEx + "\n" + aEx.stack;
+ Cu.reportError(msg);
+ dump(msg + "\n");
+};
+
+XPCOMUtils.defineLazyGetter(Parser, "reflectionAPI", function() Reflect);