In a following patch, all DevTools moz.build files will use DevToolsModules to install JS modules at a path that corresponds directly to their source tree location. Here we rewrite all require and import calls to match the new location that these files are installed to.
2344 lines
63 KiB
JavaScript
2344 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://gre/modules/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 <, 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);
|
|
},
|
|
|
|
/**
|
|
* 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);
|