/* -*- 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);