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