gecko/devtools/shared/Parser.jsm

2352 lines
63 KiB
JavaScript

/* -*- 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 Ci = Components.interfaces;
const Cu = Components.utils;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
const { require } = Cu.import("resource://devtools/shared/Loader.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 aSource
* The source text content.
* @param string aUrl [optional]
* The source url. The AST nodes will be cached, so you can use this
* identifier to avoid parsing the whole source again.
*/
get: function(aSource, aUrl = "") {
// Try to use the cached AST nodes, to avoid useless parsing operations.
if (this._cache.has(aUrl)) {
return this._cache.get(aUrl);
}
// The source may not necessarily be JS, in which case we need to extract
// all the scripts. Fastest/easiest way is with a regular expression.
// Don't worry, the rules of using a <script> tag are really strict,
// this will work.
let regexp = /<script[^>]*>([^]*?)<\/script\s*>/gim;
let syntaxTrees = [];
let scriptMatches = [];
let scriptMatch;
if (aSource.match(/^\s*</)) {
// First non whitespace character is &lt, so most definitely HTML.
while (scriptMatch = regexp.exec(aSource)) {
scriptMatches.push(scriptMatch[1]); // Contents are captured at index 1.
}
}
// If there are no script matches, send the whole source directly to the
// reflection API to generate the AST nodes.
if (!scriptMatches.length) {
// Reflect.parse throws when encounters a syntax error.
try {
let nodes = Reflect.parse(aSource);
let length = aSource.length;
syntaxTrees.push(new SyntaxTree(nodes, aUrl, length));
} catch (e) {
this.errors.push(e);
if (this.logExceptions) {
DevToolsUtils.reportException(aUrl, e);
}
}
}
// Generate the AST nodes for each script.
else {
for (let script of scriptMatches) {
// Reflect.parse throws when encounters a syntax error.
try {
let nodes = Reflect.parse(script);
let offset = aSource.indexOf(script);
let length = script.length;
syntaxTrees.push(new SyntaxTree(nodes, aUrl, length, offset));
} catch (e) {
this.errors.push(e);
if (this.logExceptions) {
DevToolsUtils.reportException(aUrl, e);
}
}
}
}
let pool = new SyntaxTreesPool(syntaxTrees, aUrl);
// 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 (aUrl) {
this._cache.set(aUrl, pool);
}
return pool;
},
/**
* Clears all the parsed sources from cache.
*/
clearCache: function() {
this._cache.clear();
},
/**
* Clears the AST for a particular source.
*
* @param String aUrl
* The URL of the source that is being cleared.
*/
clearSource: function(aUrl) {
this._cache.delete(aUrl);
},
_cache: null,
errors: null
};
/**
* A pool handling a collection of AST nodes generated by the reflection API.
*
* @param object aSyntaxTrees
* A collection of AST nodes generated for a source.
* @param string aUrl [optional]
* The source url.
*/
function SyntaxTreesPool(aSyntaxTrees, aUrl = "<unknown>") {
this._trees = aSyntaxTrees;
this._url = aUrl;
this._cache = new Map();
}
SyntaxTreesPool.prototype = {
/**
* @see SyntaxTree.prototype.getIdentifierAt
*/
getIdentifierAt: function({ line, column, scriptIndex, ignoreLiterals }) {
return this._call("getIdentifierAt", scriptIndex, line, column, ignoreLiterals)[0];
},
/**
* @see SyntaxTree.prototype.getNamedFunctionDefinitions
*/
getNamedFunctionDefinitions: function(aSubstring) {
return this._call("getNamedFunctionDefinitions", -1, aSubstring);
},
/**
* @return SyntaxTree
* The last tree in this._trees
*/
getLastSyntaxTree: function() {
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 aOffset
* The offset relative to the parent source.
* @return object
* The offset and length relative to the enclosing script.
*/
getScriptInfo: function(aOffset) {
let info = { start: -1, length: -1, index: -1 };
for (let { offset, length } of this._trees) {
info.index++;
if (offset <= aOffset && offset + length >= aOffset) {
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 aFunction
* The function name to call on the SyntaxTree instances.
* @param number aSyntaxTreeIndex
* 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 aParams
* Any kind params to pass to the request function.
* @return array
* The results given by all known syntax trees.
*/
_call: function(aFunction, aSyntaxTreeIndex, ...aParams) {
let results = [];
let requestId = [aFunction, aSyntaxTreeIndex, aParams].toSource();
if (this._cache.has(requestId)) {
return this._cache.get(requestId);
}
let requestedTree = this._trees[aSyntaxTreeIndex];
let targettedTrees = requestedTree ? [requestedTree] : this._trees;
for (let syntaxTree of targettedTrees) {
try {
let parseResults = syntaxTree[aFunction].apply(syntaxTree, aParams);
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 aNodes
* The AST nodes.
* @param string aUrl
* The source url.
* @param number aLength
* The total number of chars of the parsed script in the parent source.
* @param number aOffset [optional]
* The char offset of the parsed script in the parent source.
*/
function SyntaxTree(aNodes, aUrl, aLength, aOffset = 0) {
this.AST = aNodes;
this.url = aUrl;
this.length = aLength;
this.offset = aOffset;
};
SyntaxTree.prototype = {
/**
* Gets the identifier at the specified location.
*
* @param number aLine
* The line in the source.
* @param number aColumn
* The column in the source.
* @param boolean aIgnoreLiterals
* 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: function(aLine, aColumn, aIgnoreLiterals) {
let info = null;
SyntaxTreeVisitor.walk(this.AST, {
/**
* Callback invoked for each identifier node.
* @param Node aNode
*/
onIdentifier: function(aNode) {
if (ParserHelpers.nodeContainsPoint(aNode, aLine, aColumn)) {
info = {
name: aNode.name,
location: ParserHelpers.getNodeLocation(aNode),
evalString: ParserHelpers.getIdentifierEvalString(aNode)
};
// Abruptly halt walking the syntax tree.
SyntaxTreeVisitor.break = true;
}
},
/**
* Callback invoked for each literal node.
* @param Node aNode
*/
onLiteral: function(aNode) {
if (!aIgnoreLiterals) {
this.onIdentifier(aNode);
}
},
/**
* Callback invoked for each 'this' node.
* @param Node aNode
*/
onThisExpression: function(aNode) {
this.onIdentifier(aNode);
}
});
return info;
},
/**
* Searches for all function definitions (declarations and expressions)
* whose names (or inferred names) contain a string.
*
* @param string aSubstring
* The string to be contained in the function name (or inferred name).
* Can be an empty string to match all functions.
* @return array
* All the matching function declarations and expressions, as
* { functionName, functionLocation ... } object hashes.
*/
getNamedFunctionDefinitions: function(aSubstring) {
let lowerCaseToken = aSubstring.toLowerCase();
let store = [];
SyntaxTreeVisitor.walk(this.AST, {
/**
* Callback invoked for each function declaration node.
* @param Node aNode
*/
onFunctionDeclaration: function(aNode) {
let functionName = aNode.id.name;
if (functionName.toLowerCase().includes(lowerCaseToken)) {
store.push({
functionName: functionName,
functionLocation: ParserHelpers.getNodeLocation(aNode)
});
}
},
/**
* Callback invoked for each function expression node.
* @param Node aNode
*/
onFunctionExpression: function(aNode) {
// Function expressions don't necessarily have a name.
let functionName = aNode.id ? aNode.id.name : "";
let functionLocation = ParserHelpers.getNodeLocation(aNode);
// Infer the function's name from an enclosing syntax tree node.
let inferredInfo = ParserHelpers.inferFunctionExpressionInfo(aNode);
let inferredName = inferredInfo.name;
let inferredChain = inferredInfo.chain;
let inferredLocation = inferredInfo.loc;
// Current node may be part of a larger assignment expression stack.
if (aNode._parent.type == "AssignmentExpression") {
this.onFunctionExpression(aNode._parent);
}
if ((functionName && functionName.toLowerCase().includes(lowerCaseToken)) ||
(inferredName && inferredName.toLowerCase().includes(lowerCaseToken))) {
store.push({
functionName: functionName,
functionLocation: functionLocation,
inferredName: inferredName,
inferredChain: inferredChain,
inferredLocation: inferredLocation
});
}
},
/**
* Callback invoked for each arrow expression node.
* @param Node aNode
*/
onArrowFunctionExpression: function(aNode) {
// Infer the function's name from an enclosing syntax tree node.
let inferredInfo = ParserHelpers.inferFunctionExpressionInfo(aNode);
let inferredName = inferredInfo.name;
let inferredChain = inferredInfo.chain;
let inferredLocation = inferredInfo.loc;
// Current node may be part of a larger assignment expression stack.
if (aNode._parent.type == "AssignmentExpression") {
this.onFunctionExpression(aNode._parent);
}
if (inferredName && inferredName.toLowerCase().includes(lowerCaseToken)) {
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 aNode
* The node who's location needs to be retrieved.
* @return object
* An object containing { line, column } information.
*/
getNodeLocation: function(aNode) {
if (aNode.type != "Identifier") {
return aNode.loc;
}
// Work around the fact that some identifier nodes don't have the
// correct location attached.
let { loc: parentLocation, type: parentType } = aNode._parent;
let { loc: nodeLocation } = aNode;
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 + aNode.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 - aNode.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 + aNode.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 - aNode.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 + aNode.name.length;
return loc;
}
}
return aNode.loc;
},
/**
* Checks if a node's bounds contains a specified line.
*
* @param Node aNode
* The node's bounds used as reference.
* @param number aLine
* The line number to check.
* @return boolean
* True if the line and column is contained in the node's bounds.
*/
nodeContainsLine: function(aNode, aLine) {
let { start: s, end: e } = this.getNodeLocation(aNode);
return s.line <= aLine && e.line >= aLine;
},
/**
* Checks if a node's bounds contains a specified line and column.
*
* @param Node aNode
* The node's bounds used as reference.
* @param number aLine
* The line number to check.
* @param number aColumn
* The column number to check.
* @return boolean
* True if the line and column is contained in the node's bounds.
*/
nodeContainsPoint: function(aNode, aLine, aColumn) {
let { start: s, end: e } = this.getNodeLocation(aNode);
return s.line == aLine && e.line == aLine &&
s.column <= aColumn && e.column >= aColumn;
},
/**
* Try to infer a function expression's name & other details based on the
* enclosing VariableDeclarator, AssignmentExpression or ObjectExpression.
*
* @param Node aNode
* 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: function(aNode) {
let parent = aNode._parent;
// A function expression may be defined in a variable declarator,
// e.g. var foo = function(){}, in which case it is possible to infer
// the variable name.
if (parent.type == "VariableDeclarator") {
return {
name: parent.id.name,
chain: null,
loc: 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(aNode);
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 aNode represents the "bar" identifier in a hypothetical
* "{ foo: bar }" object expression, the returned node is the "foo" identifier.
*
* @param Node aNode
* The value node in an object expression.
* @return object
* The key identifier node in the object expression.
*/
_getObjectExpressionPropertyKeyForValue: function(aNode) {
let parent = aNode._parent;
if (parent.type != "ObjectExpression") {
return null;
}
for (let property of parent.properties) {
if (property.value == aNode) {
return property.key;
}
}
},
/**
* Gets an object expression'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 aNode represents the "baz: {}" object expression in a
* hypothetical "foo = { bar: { baz: {} } }" assignment expression, the
* returned chain is ["foo", "bar", "baz"].
*
* @param Node aNode
* The object expression node to begin the scan from.
* @param array aStore [optional]
* The chain to store the nodes into.
* @return array
* The chain to the parent variable declarator, as strings.
*/
_getObjectExpressionPropertyChain: function(aNode, aStore = []) {
switch (aNode.type) {
case "ObjectExpression":
this._getObjectExpressionPropertyChain(aNode._parent, aStore);
let propertyKey = this._getObjectExpressionPropertyKeyForValue(aNode);
if (propertyKey) {
aStore.push(propertyKey.name);
}
break;
// Handle "var foo = { ... }" variable declarators.
case "VariableDeclarator":
aStore.push(aNode.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(aNode.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(aNode._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 aNode 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 aNode
* The member expression node to begin the scan from.
* @param array aStore [optional]
* The chain to store the nodes into.
* @return array
* The full member chain, as strings.
*/
_getMemberExpressionPropertyChain: function(aNode, aStore = []) {
switch (aNode.type) {
case "MemberExpression":
this._getMemberExpressionPropertyChain(aNode.object, aStore);
this._getMemberExpressionPropertyChain(aNode.property, aStore);
break;
case "ThisExpression":
aStore.push("this");
break;
case "Identifier":
aStore.push(aNode.name);
break;
}
return aStore;
},
/**
* Returns an evaluation string which can be used to obtain the
* current value for the respective identifier.
*
* @param Node aNode
* 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: function(aNode) {
switch (aNode._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(aNode)) {
let propertyChain = this._getObjectExpressionPropertyChain(aNode._parent);
let propertyLeaf = aNode.name;
return [...propertyChain, propertyLeaf].join(".");
}
break;
case "MemberExpression":
// Make sure this is a property identifier, not the parent object.
if (aNode._parent.property == aNode) {
return this._getMemberExpressionPropertyChain(aNode._parent).join(".");
}
break;
}
switch (aNode.type) {
case "ThisExpression":
return "this";
case "Identifier":
return aNode.name;
case "Literal":
return uneval(aNode.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 aTree
* The AST nodes generated by the reflection API
* @param object aCallbacks
* A map of all the callbacks to invoke when passing through certain
* types of noes (e.g: onFunctionDeclaration, onBlockStatement etc.).
*/
walk: function(aTree, aCallbacks) {
this.break = false;
this[aTree.type](aTree, aCallbacks);
},
/**
* Filters all the nodes in this syntax tree based on a predicate.
*
* @param object aTree
* The AST nodes generated by the reflection API
* @param function aPredicate
* The predicate ran on each node.
* @return array
* An array of nodes validating the predicate.
*/
filter: function(aTree, aPredicate) {
let store = [];
this.walk(aTree, { onNode: e => { if (aPredicate(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: function(aNode, aCallbacks) {
if (aCallbacks.onProgram) {
aCallbacks.onProgram(aNode);
}
for (let statement of aNode.body) {
this[statement.type](statement, aNode, aCallbacks);
}
},
/**
* Any statement.
*
* interface Statement <: Node { }
*/
Statement: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onStatement) {
aCallbacks.onStatement(aNode);
}
},
/**
* An empty statement, i.e., a solitary semicolon.
*
* interface EmptyStatement <: Statement {
* type: "EmptyStatement";
* }
*/
EmptyStatement: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onEmptyStatement) {
aCallbacks.onEmptyStatement(aNode);
}
},
/**
* A block statement, i.e., a sequence of statements surrounded by braces.
*
* interface BlockStatement <: Statement {
* type: "BlockStatement";
* body: [ Statement ];
* }
*/
BlockStatement: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onBlockStatement) {
aCallbacks.onBlockStatement(aNode);
}
for (let statement of aNode.body) {
this[statement.type](statement, aNode, aCallbacks);
}
},
/**
* An expression statement, i.e., a statement consisting of a single expression.
*
* interface ExpressionStatement <: Statement {
* type: "ExpressionStatement";
* expression: Expression;
* }
*/
ExpressionStatement: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onExpressionStatement) {
aCallbacks.onExpressionStatement(aNode);
}
this[aNode.expression.type](aNode.expression, aNode, aCallbacks);
},
/**
* An if statement.
*
* interface IfStatement <: Statement {
* type: "IfStatement";
* test: Expression;
* consequent: Statement;
* alternate: Statement | null;
* }
*/
IfStatement: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onIfStatement) {
aCallbacks.onIfStatement(aNode);
}
this[aNode.test.type](aNode.test, aNode, aCallbacks);
this[aNode.consequent.type](aNode.consequent, aNode, aCallbacks);
if (aNode.alternate) {
this[aNode.alternate.type](aNode.alternate, aNode, aCallbacks);
}
},
/**
* A labeled statement, i.e., a statement prefixed by a break/continue label.
*
* interface LabeledStatement <: Statement {
* type: "LabeledStatement";
* label: Identifier;
* body: Statement;
* }
*/
LabeledStatement: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onLabeledStatement) {
aCallbacks.onLabeledStatement(aNode);
}
this[aNode.label.type](aNode.label, aNode, aCallbacks);
this[aNode.body.type](aNode.body, aNode, aCallbacks);
},
/**
* A break statement.
*
* interface BreakStatement <: Statement {
* type: "BreakStatement";
* label: Identifier | null;
* }
*/
BreakStatement: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onBreakStatement) {
aCallbacks.onBreakStatement(aNode);
}
if (aNode.label) {
this[aNode.label.type](aNode.label, aNode, aCallbacks);
}
},
/**
* A continue statement.
*
* interface ContinueStatement <: Statement {
* type: "ContinueStatement";
* label: Identifier | null;
* }
*/
ContinueStatement: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onContinueStatement) {
aCallbacks.onContinueStatement(aNode);
}
if (aNode.label) {
this[aNode.label.type](aNode.label, aNode, aCallbacks);
}
},
/**
* A with statement.
*
* interface WithStatement <: Statement {
* type: "WithStatement";
* object: Expression;
* body: Statement;
* }
*/
WithStatement: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onWithStatement) {
aCallbacks.onWithStatement(aNode);
}
this[aNode.object.type](aNode.object, aNode, aCallbacks);
this[aNode.body.type](aNode.body, aNode, aCallbacks);
},
/**
* A switch statement. The lexical flag is metadata indicating whether the
* switch statement contains any unnested let declarations (and therefore
* introduces a new lexical scope).
*
* interface SwitchStatement <: Statement {
* type: "SwitchStatement";
* discriminant: Expression;
* cases: [ SwitchCase ];
* lexical: boolean;
* }
*/
SwitchStatement: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onSwitchStatement) {
aCallbacks.onSwitchStatement(aNode);
}
this[aNode.discriminant.type](aNode.discriminant, aNode, aCallbacks);
for (let _case of aNode.cases) {
this[_case.type](_case, aNode, aCallbacks);
}
},
/**
* A return statement.
*
* interface ReturnStatement <: Statement {
* type: "ReturnStatement";
* argument: Expression | null;
* }
*/
ReturnStatement: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onReturnStatement) {
aCallbacks.onReturnStatement(aNode);
}
if (aNode.argument) {
this[aNode.argument.type](aNode.argument, aNode, aCallbacks);
}
},
/**
* A throw statement.
*
* interface ThrowStatement <: Statement {
* type: "ThrowStatement";
* argument: Expression;
* }
*/
ThrowStatement: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onThrowStatement) {
aCallbacks.onThrowStatement(aNode);
}
this[aNode.argument.type](aNode.argument, aNode, aCallbacks);
},
/**
* A try statement.
*
* interface TryStatement <: Statement {
* type: "TryStatement";
* block: BlockStatement;
* handler: CatchClause | null;
* guardedHandlers: [ CatchClause ];
* finalizer: BlockStatement | null;
* }
*/
TryStatement: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onTryStatement) {
aCallbacks.onTryStatement(aNode);
}
this[aNode.block.type](aNode.block, aNode, aCallbacks);
if (aNode.handler) {
this[aNode.handler.type](aNode.handler, aNode, aCallbacks);
}
for (let guardedHandler of aNode.guardedHandlers) {
this[guardedHandler.type](guardedHandler, aNode, aCallbacks);
}
if (aNode.finalizer) {
this[aNode.finalizer.type](aNode.finalizer, aNode, aCallbacks);
}
},
/**
* A while statement.
*
* interface WhileStatement <: Statement {
* type: "WhileStatement";
* test: Expression;
* body: Statement;
* }
*/
WhileStatement: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onWhileStatement) {
aCallbacks.onWhileStatement(aNode);
}
this[aNode.test.type](aNode.test, aNode, aCallbacks);
this[aNode.body.type](aNode.body, aNode, aCallbacks);
},
/**
* A do/while statement.
*
* interface DoWhileStatement <: Statement {
* type: "DoWhileStatement";
* body: Statement;
* test: Expression;
* }
*/
DoWhileStatement: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onDoWhileStatement) {
aCallbacks.onDoWhileStatement(aNode);
}
this[aNode.body.type](aNode.body, aNode, aCallbacks);
this[aNode.test.type](aNode.test, aNode, aCallbacks);
},
/**
* A for statement.
*
* interface ForStatement <: Statement {
* type: "ForStatement";
* init: VariableDeclaration | Expression | null;
* test: Expression | null;
* update: Expression | null;
* body: Statement;
* }
*/
ForStatement: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onForStatement) {
aCallbacks.onForStatement(aNode);
}
if (aNode.init) {
this[aNode.init.type](aNode.init, aNode, aCallbacks);
}
if (aNode.test) {
this[aNode.test.type](aNode.test, aNode, aCallbacks);
}
if (aNode.update) {
this[aNode.update.type](aNode.update, aNode, aCallbacks);
}
this[aNode.body.type](aNode.body, aNode, aCallbacks);
},
/**
* A for/in statement, or, if each is true, a for each/in statement.
*
* interface ForInStatement <: Statement {
* type: "ForInStatement";
* left: VariableDeclaration | Expression;
* right: Expression;
* body: Statement;
* each: boolean;
* }
*/
ForInStatement: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onForInStatement) {
aCallbacks.onForInStatement(aNode);
}
this[aNode.left.type](aNode.left, aNode, aCallbacks);
this[aNode.right.type](aNode.right, aNode, aCallbacks);
this[aNode.body.type](aNode.body, aNode, aCallbacks);
},
/**
* A for/of statement.
*
* interface ForOfStatement <: Statement {
* type: "ForOfStatement";
* left: VariableDeclaration | Expression;
* right: Expression;
* body: Statement;
* }
*/
ForOfStatement: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onForOfStatement) {
aCallbacks.onForOfStatement(aNode);
}
this[aNode.left.type](aNode.left, aNode, aCallbacks);
this[aNode.right.type](aNode.right, aNode, aCallbacks);
this[aNode.body.type](aNode.body, aNode, aCallbacks);
},
/**
* A let statement.
*
* interface LetStatement <: Statement {
* type: "LetStatement";
* head: [ { id: Pattern, init: Expression | null } ];
* body: Statement;
* }
*/
LetStatement: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onLetStatement) {
aCallbacks.onLetStatement(aNode);
}
for (let { id, init } of aNode.head) {
this[id.type](id, aNode, aCallbacks);
if (init) {
this[init.type](init, aNode, aCallbacks);
}
}
this[aNode.body.type](aNode.body, aNode, aCallbacks);
},
/**
* A debugger statement.
*
* interface DebuggerStatement <: Statement {
* type: "DebuggerStatement";
* }
*/
DebuggerStatement: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onDebuggerStatement) {
aCallbacks.onDebuggerStatement(aNode);
}
},
/**
* Any declaration node. Note that declarations are considered statements;
* this is because declarations can appear in any statement context in the
* language recognized by the SpiderMonkey parser.
*
* interface Declaration <: Statement { }
*/
Declaration: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onDeclaration) {
aCallbacks.onDeclaration(aNode);
}
},
/**
* A function declaration.
*
* interface FunctionDeclaration <: Function, Declaration {
* type: "FunctionDeclaration";
* id: Identifier;
* params: [ Pattern ];
* defaults: [ Expression ];
* rest: Identifier | null;
* body: BlockStatement | Expression;
* generator: boolean;
* expression: boolean;
* }
*/
FunctionDeclaration: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onFunctionDeclaration) {
aCallbacks.onFunctionDeclaration(aNode);
}
this[aNode.id.type](aNode.id, aNode, aCallbacks);
for (let param of aNode.params) {
this[param.type](param, aNode, aCallbacks);
}
for (let _default of aNode.defaults) {
this[_default.type](_default, aNode, aCallbacks);
}
if (aNode.rest) {
this[aNode.rest.type](aNode.rest, aNode, aCallbacks);
}
this[aNode.body.type](aNode.body, aNode, aCallbacks);
},
/**
* A variable declaration, via one of var, let, or const.
*
* interface VariableDeclaration <: Declaration {
* type: "VariableDeclaration";
* declarations: [ VariableDeclarator ];
* kind: "var" | "let" | "const";
* }
*/
VariableDeclaration: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onVariableDeclaration) {
aCallbacks.onVariableDeclaration(aNode);
}
for (let declaration of aNode.declarations) {
this[declaration.type](declaration, aNode, aCallbacks);
}
},
/**
* A variable declarator.
*
* interface VariableDeclarator <: Node {
* type: "VariableDeclarator";
* id: Pattern;
* init: Expression | null;
* }
*/
VariableDeclarator: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onVariableDeclarator) {
aCallbacks.onVariableDeclarator(aNode);
}
this[aNode.id.type](aNode.id, aNode, aCallbacks);
if (aNode.init) {
this[aNode.init.type](aNode.init, aNode, aCallbacks);
}
},
/**
* Any expression node. Since the left-hand side of an assignment may be any
* expression in general, an expression can also be a pattern.
*
* interface Expression <: Node, Pattern { }
*/
Expression: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onExpression) {
aCallbacks.onExpression(aNode);
}
},
/**
* A this expression.
*
* interface ThisExpression <: Expression {
* type: "ThisExpression";
* }
*/
ThisExpression: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onThisExpression) {
aCallbacks.onThisExpression(aNode);
}
},
/**
* An array expression.
*
* interface ArrayExpression <: Expression {
* type: "ArrayExpression";
* elements: [ Expression | null ];
* }
*/
ArrayExpression: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onArrayExpression) {
aCallbacks.onArrayExpression(aNode);
}
for (let element of aNode.elements) {
// TODO: remove the typeof check when support for SpreadExpression is
// added (bug 890913).
if (element && typeof this[element.type] == "function") {
this[element.type](element, aNode, aCallbacks);
}
}
},
/**
* An object expression. A literal property in an object expression can have
* either a string or number as its value. Ordinary property initializers
* have a kind value "init"; getters and setters have the kind values "get"
* and "set", respectively.
*
* interface ObjectExpression <: Expression {
* type: "ObjectExpression";
* properties: [ { key: Literal | Identifier,
* value: Expression,
* kind: "init" | "get" | "set" } ];
* }
*/
ObjectExpression: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onObjectExpression) {
aCallbacks.onObjectExpression(aNode);
}
for (let { key, value } of aNode.properties) {
this[key.type](key, aNode, aCallbacks);
this[value.type](value, aNode, aCallbacks);
}
},
/**
* A function expression.
*
* interface FunctionExpression <: Function, Expression {
* type: "FunctionExpression";
* id: Identifier | null;
* params: [ Pattern ];
* defaults: [ Expression ];
* rest: Identifier | null;
* body: BlockStatement | Expression;
* generator: boolean;
* expression: boolean;
* }
*/
FunctionExpression: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onFunctionExpression) {
aCallbacks.onFunctionExpression(aNode);
}
if (aNode.id) {
this[aNode.id.type](aNode.id, aNode, aCallbacks);
}
for (let param of aNode.params) {
this[param.type](param, aNode, aCallbacks);
}
for (let _default of aNode.defaults) {
this[_default.type](_default, aNode, aCallbacks);
}
if (aNode.rest) {
this[aNode.rest.type](aNode.rest, aNode, aCallbacks);
}
this[aNode.body.type](aNode.body, aNode, aCallbacks);
},
/**
* An arrow expression.
*
* interface ArrowFunctionExpression <: Function, Expression {
* type: "ArrowFunctionExpression";
* params: [ Pattern ];
* defaults: [ Expression ];
* rest: Identifier | null;
* body: BlockStatement | Expression;
* generator: boolean;
* expression: boolean;
* }
*/
ArrowFunctionExpression: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onArrowFunctionExpression) {
aCallbacks.onArrowFunctionExpression(aNode);
}
for (let param of aNode.params) {
this[param.type](param, aNode, aCallbacks);
}
for (let _default of aNode.defaults) {
this[_default.type](_default, aNode, aCallbacks);
}
if (aNode.rest) {
this[aNode.rest.type](aNode.rest, aNode, aCallbacks);
}
this[aNode.body.type](aNode.body, aNode, aCallbacks);
},
/**
* A sequence expression, i.e., a comma-separated sequence of expressions.
*
* interface SequenceExpression <: Expression {
* type: "SequenceExpression";
* expressions: [ Expression ];
* }
*/
SequenceExpression: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onSequenceExpression) {
aCallbacks.onSequenceExpression(aNode);
}
for (let expression of aNode.expressions) {
this[expression.type](expression, aNode, aCallbacks);
}
},
/**
* A unary operator expression.
*
* interface UnaryExpression <: Expression {
* type: "UnaryExpression";
* operator: UnaryOperator;
* prefix: boolean;
* argument: Expression;
* }
*/
UnaryExpression: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onUnaryExpression) {
aCallbacks.onUnaryExpression(aNode);
}
this[aNode.argument.type](aNode.argument, aNode, aCallbacks);
},
/**
* A binary operator expression.
*
* interface BinaryExpression <: Expression {
* type: "BinaryExpression";
* operator: BinaryOperator;
* left: Expression;
* right: Expression;
* }
*/
BinaryExpression: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onBinaryExpression) {
aCallbacks.onBinaryExpression(aNode);
}
this[aNode.left.type](aNode.left, aNode, aCallbacks);
this[aNode.right.type](aNode.right, aNode, aCallbacks);
},
/**
* An assignment operator expression.
*
* interface AssignmentExpression <: Expression {
* type: "AssignmentExpression";
* operator: AssignmentOperator;
* left: Expression;
* right: Expression;
* }
*/
AssignmentExpression: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onAssignmentExpression) {
aCallbacks.onAssignmentExpression(aNode);
}
this[aNode.left.type](aNode.left, aNode, aCallbacks);
this[aNode.right.type](aNode.right, aNode, aCallbacks);
},
/**
* An update (increment or decrement) operator expression.
*
* interface UpdateExpression <: Expression {
* type: "UpdateExpression";
* operator: UpdateOperator;
* argument: Expression;
* prefix: boolean;
* }
*/
UpdateExpression: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onUpdateExpression) {
aCallbacks.onUpdateExpression(aNode);
}
this[aNode.argument.type](aNode.argument, aNode, aCallbacks);
},
/**
* A logical operator expression.
*
* interface LogicalExpression <: Expression {
* type: "LogicalExpression";
* operator: LogicalOperator;
* left: Expression;
* right: Expression;
* }
*/
LogicalExpression: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onLogicalExpression) {
aCallbacks.onLogicalExpression(aNode);
}
this[aNode.left.type](aNode.left, aNode, aCallbacks);
this[aNode.right.type](aNode.right, aNode, aCallbacks);
},
/**
* A conditional expression, i.e., a ternary ?/: expression.
*
* interface ConditionalExpression <: Expression {
* type: "ConditionalExpression";
* test: Expression;
* alternate: Expression;
* consequent: Expression;
* }
*/
ConditionalExpression: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onConditionalExpression) {
aCallbacks.onConditionalExpression(aNode);
}
this[aNode.test.type](aNode.test, aNode, aCallbacks);
this[aNode.alternate.type](aNode.alternate, aNode, aCallbacks);
this[aNode.consequent.type](aNode.consequent, aNode, aCallbacks);
},
/**
* A new expression.
*
* interface NewExpression <: Expression {
* type: "NewExpression";
* callee: Expression;
* arguments: [ Expression | null ];
* }
*/
NewExpression: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onNewExpression) {
aCallbacks.onNewExpression(aNode);
}
this[aNode.callee.type](aNode.callee, aNode, aCallbacks);
for (let argument of aNode.arguments) {
if (argument) {
this[argument.type](argument, aNode, aCallbacks);
}
}
},
/**
* A function or method call expression.
*
* interface CallExpression <: Expression {
* type: "CallExpression";
* callee: Expression;
* arguments: [ Expression | null ];
* }
*/
CallExpression: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onCallExpression) {
aCallbacks.onCallExpression(aNode);
}
this[aNode.callee.type](aNode.callee, aNode, aCallbacks);
for (let argument of aNode.arguments) {
if (argument) {
this[argument.type](argument, aNode, aCallbacks);
}
}
},
/**
* A member expression. If computed is true, the node corresponds to a
* computed e1[e2] expression and property is an Expression. If computed is
* false, the node corresponds to a static e1.x expression and property is an
* Identifier.
*
* interface MemberExpression <: Expression {
* type: "MemberExpression";
* object: Expression;
* property: Identifier | Expression;
* computed: boolean;
* }
*/
MemberExpression: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onMemberExpression) {
aCallbacks.onMemberExpression(aNode);
}
this[aNode.object.type](aNode.object, aNode, aCallbacks);
this[aNode.property.type](aNode.property, aNode, aCallbacks);
},
/**
* A yield expression.
*
* interface YieldExpression <: Expression {
* argument: Expression | null;
* }
*/
YieldExpression: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onYieldExpression) {
aCallbacks.onYieldExpression(aNode);
}
if (aNode.argument) {
this[aNode.argument.type](aNode.argument, aNode, aCallbacks);
}
},
/**
* An array comprehension. The blocks array corresponds to the sequence of
* for and for each blocks. The optional filter expression corresponds to the
* final if clause, if present.
*
* interface ComprehensionExpression <: Expression {
* body: Expression;
* blocks: [ ComprehensionBlock ];
* filter: Expression | null;
* }
*/
ComprehensionExpression: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onComprehensionExpression) {
aCallbacks.onComprehensionExpression(aNode);
}
this[aNode.body.type](aNode.body, aNode, aCallbacks);
for (let block of aNode.blocks) {
this[block.type](block, aNode, aCallbacks);
}
if (aNode.filter) {
this[aNode.filter.type](aNode.filter, aNode, aCallbacks);
}
},
/**
* A generator expression. As with array comprehensions, the blocks array
* corresponds to the sequence of for and for each blocks, and the optional
* filter expression corresponds to the final if clause, if present.
*
* interface GeneratorExpression <: Expression {
* body: Expression;
* blocks: [ ComprehensionBlock ];
* filter: Expression | null;
* }
*/
GeneratorExpression: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onGeneratorExpression) {
aCallbacks.onGeneratorExpression(aNode);
}
this[aNode.body.type](aNode.body, aNode, aCallbacks);
for (let block of aNode.blocks) {
this[block.type](block, aNode, aCallbacks);
}
if (aNode.filter) {
this[aNode.filter.type](aNode.filter, aNode, aCallbacks);
}
},
/**
* A graph expression, aka "sharp literal," such as #1={ self: #1# }.
*
* interface GraphExpression <: Expression {
* index: uint32;
* expression: Literal;
* }
*/
GraphExpression: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onGraphExpression) {
aCallbacks.onGraphExpression(aNode);
}
this[aNode.expression.type](aNode.expression, aNode, aCallbacks);
},
/**
* A graph index expression, aka "sharp variable," such as #1#.
*
* interface GraphIndexExpression <: Expression {
* index: uint32;
* }
*/
GraphIndexExpression: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onGraphIndexExpression) {
aCallbacks.onGraphIndexExpression(aNode);
}
},
/**
* A let expression.
*
* interface LetExpression <: Expression {
* type: "LetExpression";
* head: [ { id: Pattern, init: Expression | null } ];
* body: Expression;
* }
*/
LetExpression: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onLetExpression) {
aCallbacks.onLetExpression(aNode);
}
for (let { id, init } of aNode.head) {
this[id.type](id, aNode, aCallbacks);
if (init) {
this[init.type](init, aNode, aCallbacks);
}
}
this[aNode.body.type](aNode.body, aNode, aCallbacks);
},
/**
* Any pattern.
*
* interface Pattern <: Node { }
*/
Pattern: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onPattern) {
aCallbacks.onPattern(aNode);
}
},
/**
* An object-destructuring pattern. A literal property in an object pattern
* can have either a string or number as its value.
*
* interface ObjectPattern <: Pattern {
* type: "ObjectPattern";
* properties: [ { key: Literal | Identifier, value: Pattern } ];
* }
*/
ObjectPattern: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onObjectPattern) {
aCallbacks.onObjectPattern(aNode);
}
for (let { key, value } of aNode.properties) {
this[key.type](key, aNode, aCallbacks);
this[value.type](value, aNode, aCallbacks);
}
},
/**
* An array-destructuring pattern.
*
* interface ArrayPattern <: Pattern {
* type: "ArrayPattern";
* elements: [ Pattern | null ];
* }
*/
ArrayPattern: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onArrayPattern) {
aCallbacks.onArrayPattern(aNode);
}
for (let element of aNode.elements) {
if (element) {
this[element.type](element, aNode, aCallbacks);
}
}
},
/**
* A case (if test is an Expression) or default (if test is null) clause in
* the body of a switch statement.
*
* interface SwitchCase <: Node {
* type: "SwitchCase";
* test: Expression | null;
* consequent: [ Statement ];
* }
*/
SwitchCase: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onSwitchCase) {
aCallbacks.onSwitchCase(aNode);
}
if (aNode.test) {
this[aNode.test.type](aNode.test, aNode, aCallbacks);
}
for (let consequent of aNode.consequent) {
this[consequent.type](consequent, aNode, aCallbacks);
}
},
/**
* A catch clause following a try block. The optional guard property
* corresponds to the optional expression guard on the bound variable.
*
* interface CatchClause <: Node {
* type: "CatchClause";
* param: Pattern;
* guard: Expression | null;
* body: BlockStatement;
* }
*/
CatchClause: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onCatchClause) {
aCallbacks.onCatchClause(aNode);
}
this[aNode.param.type](aNode.param, aNode, aCallbacks);
if (aNode.guard) {
this[aNode.guard.type](aNode.guard, aNode, aCallbacks);
}
this[aNode.body.type](aNode.body, aNode, aCallbacks);
},
/**
* A for or for each block in an array comprehension or generator expression.
*
* interface ComprehensionBlock <: Node {
* left: Pattern;
* right: Expression;
* each: boolean;
* }
*/
ComprehensionBlock: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onComprehensionBlock) {
aCallbacks.onComprehensionBlock(aNode);
}
this[aNode.left.type](aNode.left, aNode, aCallbacks);
this[aNode.right.type](aNode.right, aNode, aCallbacks);
},
/**
* An identifier. Note that an identifier may be an expression or a
* destructuring pattern.
*
* interface Identifier <: Node, Expression, Pattern {
* type: "Identifier";
* name: string;
* }
*/
Identifier: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onIdentifier) {
aCallbacks.onIdentifier(aNode);
}
},
/**
* A literal token. Note that a literal can be an expression.
*
* interface Literal <: Node, Expression {
* type: "Literal";
* value: string | boolean | null | number | RegExp;
* }
*/
Literal: function(aNode, aParent, aCallbacks) {
aNode._parent = aParent;
if (this.break) {
return;
}
if (aCallbacks.onNode) {
if (aCallbacks.onNode(aNode, aParent) === false) {
return;
}
}
if (aCallbacks.onLiteral) {
aCallbacks.onLiteral(aNode);
}
}
};
XPCOMUtils.defineLazyGetter(Parser, "reflectionAPI", () => Reflect);