diff options
Diffstat (limited to 'browser/devtools/shared/Parser.jsm')
-rw-r--r-- | browser/devtools/shared/Parser.jsm | 2293 |
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 <, 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); |