mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-07 12:15:51 +00:00
2306 lines
62 KiB
JavaScript
2306 lines
62 KiB
JavaScript
/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
"use strict";
|
|
|
|
const Ci = Components.interfaces;
|
|
const Cu = Components.utils;
|
|
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
|
|
XPCOMUtils.defineLazyModuleGetter(this,
|
|
"Reflect", "resource://gre/modules/reflect.jsm");
|
|
|
|
this.EXPORTED_SYMBOLS = ["Parser"];
|
|
|
|
/**
|
|
* A JS parser using the reflection API.
|
|
*/
|
|
this.Parser = function Parser() {
|
|
this._cache = new Map();
|
|
};
|
|
|
|
Parser.prototype = {
|
|
/**
|
|
* Gets a collection of parser methods for a specified source.
|
|
*
|
|
* @param string aUrl [optional]
|
|
* The source url. The AST nodes will be cached, so you can use this
|
|
* identifier to avoid parsing the whole source again.
|
|
* @param string aSource
|
|
* The source text content.
|
|
*/
|
|
get: function P_get(aUrl, aSource) {
|
|
// Try to use the cached AST nodes, to avoid useless parsing operations.
|
|
if (this._cache.has(aUrl)) {
|
|
return this._cache.get(aUrl);
|
|
}
|
|
|
|
// The source may not necessarily be JS, in which case we need to extract
|
|
// all the scripts. Fastest/easiest way is with a regular expression.
|
|
// Don't worry, the rules of using a <script> tag are really strict,
|
|
// this will work.
|
|
let regexp = /<script[^>]*>([^]*?)<\/script\s*>/gim;
|
|
let syntaxTrees = [];
|
|
let scriptMatches = [];
|
|
let scriptMatch;
|
|
|
|
if (aSource.match(/^\s*</)) {
|
|
// First non whitespace character is <, so most definitely HTML.
|
|
while (scriptMatch = regexp.exec(aSource)) {
|
|
scriptMatches.push(scriptMatch[1]); // Contents are captured at index 1.
|
|
}
|
|
}
|
|
|
|
// If there are no script matches, send the whole source directly to the
|
|
// reflection API to generate the AST nodes.
|
|
if (!scriptMatches.length) {
|
|
// Reflect.parse throws when encounters a syntax error.
|
|
try {
|
|
let nodes = Reflect.parse(aSource);
|
|
let length = aSource.length;
|
|
syntaxTrees.push(new SyntaxTree(nodes, aUrl, length));
|
|
} catch (e) {
|
|
log(aUrl, e);
|
|
}
|
|
}
|
|
// Generate the AST nodes for each script.
|
|
else {
|
|
for (let script of scriptMatches) {
|
|
// Reflect.parse throws when encounters a syntax error.
|
|
try {
|
|
let nodes = Reflect.parse(script);
|
|
let offset = aSource.indexOf(script);
|
|
let length = script.length;
|
|
syntaxTrees.push(new SyntaxTree(nodes, aUrl, length, offset));
|
|
} catch (e) {
|
|
log(aUrl, e);
|
|
}
|
|
}
|
|
}
|
|
|
|
let pool = new SyntaxTreesPool(syntaxTrees);
|
|
this._cache.set(aUrl, pool);
|
|
return pool;
|
|
},
|
|
|
|
/**
|
|
* Clears all the parsed sources from cache.
|
|
*/
|
|
clearCache: function P_clearCache() {
|
|
this._cache.clear();
|
|
},
|
|
|
|
/**
|
|
* Clears the AST for a particular source.
|
|
*
|
|
* @param String aUrl
|
|
* The URL of the source that is being cleared.
|
|
*/
|
|
clearSource: function P_clearSource(aUrl) {
|
|
this._cache.delete(aUrl);
|
|
},
|
|
|
|
_cache: null
|
|
};
|
|
|
|
/**
|
|
* A pool handling a collection of AST nodes generated by the reflection API.
|
|
*
|
|
* @param object aSyntaxTrees
|
|
* A collection of AST nodes generated for a source.
|
|
*/
|
|
function SyntaxTreesPool(aSyntaxTrees) {
|
|
this._trees = aSyntaxTrees;
|
|
this._cache = new Map();
|
|
}
|
|
|
|
SyntaxTreesPool.prototype = {
|
|
/**
|
|
* @see SyntaxTree.prototype.getNamedFunctionDefinitions
|
|
*/
|
|
getNamedFunctionDefinitions: function STP_getNamedFunctionDefinitions(aSubstring) {
|
|
return this._call("getNamedFunctionDefinitions", aSubstring);
|
|
},
|
|
|
|
/**
|
|
* @see SyntaxTree.prototype.getFunctionAtLocation
|
|
*/
|
|
getFunctionAtLocation: function STP_getFunctionAtLocation(aLine, aColumn) {
|
|
return this._call("getFunctionAtLocation", [aLine, aColumn]);
|
|
},
|
|
|
|
/**
|
|
* Finds the offset and length of the script containing the specified offset
|
|
* relative to its parent source.
|
|
*
|
|
* @param number aOffset
|
|
* The offset relative to the parent source.
|
|
* @return array
|
|
* The offset and length relative to the enclosing script.
|
|
*/
|
|
getScriptInfo: function STP_getScriptInfo(aOffset) {
|
|
for (let { offset, length } of this._trees) {
|
|
if (offset <= aOffset && offset + length >= aOffset) {
|
|
return [offset, length];
|
|
}
|
|
}
|
|
return [-1, -1];
|
|
},
|
|
|
|
/**
|
|
* Handles a request for all known syntax trees.
|
|
*
|
|
* @param string aFunction
|
|
* The function name to call on the SyntaxTree instances.
|
|
* @param any aParams
|
|
* Any kind params to pass to the request function.
|
|
* @return array
|
|
* The results given by all known syntax trees.
|
|
*/
|
|
_call: function STP__call(aFunction, aParams) {
|
|
let results = [];
|
|
let requestId = aFunction + aParams; // Cache all the things!
|
|
|
|
if (this._cache.has(requestId)) {
|
|
return this._cache.get(requestId);
|
|
}
|
|
for (let syntaxTree of this._trees) {
|
|
try {
|
|
results.push({
|
|
sourceUrl: syntaxTree.url,
|
|
scriptLength: syntaxTree.length,
|
|
scriptOffset: syntaxTree.offset,
|
|
parseResults: syntaxTree[aFunction](aParams)
|
|
});
|
|
} catch (e) {
|
|
// Can't guarantee that the tree traversal logic is forever perfect :)
|
|
// Language features may be added, in which case the recursive methods
|
|
// need to be updated. If an exception is thrown here, file a bug.
|
|
log("syntax tree", e);
|
|
}
|
|
}
|
|
this._cache.set(requestId, results);
|
|
return results;
|
|
},
|
|
|
|
_trees: null,
|
|
_cache: null
|
|
};
|
|
|
|
/**
|
|
* A collection of AST nodes generated by the reflection API.
|
|
*
|
|
* @param object aNodes
|
|
* The AST nodes.
|
|
* @param string aUrl
|
|
* The source url.
|
|
* @param number aLength
|
|
* The total number of chars of the parsed script in the parent source.
|
|
* @param number aOffset [optional]
|
|
* The char offset of the parsed script in the parent source.
|
|
*/
|
|
function SyntaxTree(aNodes, aUrl, aLength, aOffset = 0) {
|
|
this.AST = aNodes;
|
|
this.url = aUrl;
|
|
this.length = aLength;
|
|
this.offset = aOffset;
|
|
};
|
|
|
|
SyntaxTree.prototype = {
|
|
/**
|
|
* Searches for all function definitions (declarations and expressions)
|
|
* whose names (or inferred names) contain a string.
|
|
*
|
|
* @param string aSubstring
|
|
* The string to be contained in the function name (or inferred name).
|
|
* Can be an empty string to match all functions.
|
|
* @return array
|
|
* All the matching function declarations and expressions, as
|
|
* { functionName, functionLocation ... } object hashes.
|
|
*/
|
|
getNamedFunctionDefinitions: function ST_getNamedFunctionDefinitions(aSubstring) {
|
|
let lowerCaseToken = aSubstring.toLowerCase();
|
|
let store = [];
|
|
|
|
SyntaxTreeVisitor.walk(this.AST, {
|
|
/**
|
|
* Callback invoked for each function declaration node.
|
|
* @param Node aNode
|
|
*/
|
|
onFunctionDeclaration: function STW_onFunctionDeclaration(aNode) {
|
|
let functionName = aNode.id.name;
|
|
if (functionName.toLowerCase().contains(lowerCaseToken)) {
|
|
store.push({
|
|
functionName: functionName,
|
|
functionLocation: aNode.loc
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Callback invoked for each function expression node.
|
|
* @param Node aNode
|
|
*/
|
|
onFunctionExpression: function STW_onFunctionExpression(aNode) {
|
|
let parent = aNode._parent;
|
|
let functionName, inferredName, inferredChain, inferredLocation;
|
|
|
|
// Function expressions don't necessarily have a name.
|
|
if (aNode.id) {
|
|
functionName = aNode.id.name;
|
|
}
|
|
// Infer the function's name from an enclosing syntax tree node.
|
|
if (parent) {
|
|
let inferredInfo = ParserHelpers.inferFunctionExpressionInfo(aNode);
|
|
inferredName = inferredInfo.name;
|
|
inferredChain = inferredInfo.chain;
|
|
inferredLocation = inferredInfo.loc;
|
|
}
|
|
// Current node may be part of a larger assignment expression stack.
|
|
if (parent.type == "AssignmentExpression") {
|
|
this.onFunctionExpression(parent);
|
|
}
|
|
|
|
if ((functionName && functionName.toLowerCase().contains(lowerCaseToken)) ||
|
|
(inferredName && inferredName.toLowerCase().contains(lowerCaseToken))) {
|
|
store.push({
|
|
functionName: functionName,
|
|
functionLocation: aNode.loc,
|
|
inferredName: inferredName,
|
|
inferredChain: inferredChain,
|
|
inferredLocation: inferredLocation
|
|
});
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Callback invoked for each arrow expression node.
|
|
* @param Node aNode
|
|
*/
|
|
onArrowExpression: function STW_onArrowExpression(aNode) {
|
|
let parent = aNode._parent;
|
|
let inferredName, inferredChain, inferredLocation;
|
|
|
|
// Infer the function's name from an enclosing syntax tree node.
|
|
let inferredInfo = ParserHelpers.inferFunctionExpressionInfo(aNode);
|
|
inferredName = inferredInfo.name;
|
|
inferredChain = inferredInfo.chain;
|
|
inferredLocation = inferredInfo.loc;
|
|
|
|
// Current node may be part of a larger assignment expression stack.
|
|
if (parent.type == "AssignmentExpression") {
|
|
this.onFunctionExpression(parent);
|
|
}
|
|
|
|
if (inferredName && inferredName.toLowerCase().contains(lowerCaseToken)) {
|
|
store.push({
|
|
inferredName: inferredName,
|
|
inferredChain: inferredChain,
|
|
inferredLocation: inferredLocation
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
return store;
|
|
},
|
|
|
|
/**
|
|
* Gets the "new" or "call" expression at the specified location.
|
|
*
|
|
* @param number aLine
|
|
* The line in the source.
|
|
* @param number aColumn
|
|
* The column in the source.
|
|
* @return object
|
|
* An { functionName, functionLocation } object hash,
|
|
* or null if nothing is found at the specified location.
|
|
*/
|
|
getFunctionAtLocation: function STW_getFunctionAtLocation([aLine, aColumn]) {
|
|
let self = this;
|
|
let func = null;
|
|
|
|
SyntaxTreeVisitor.walk(this.AST, {
|
|
/**
|
|
* Callback invoked for each node.
|
|
* @param Node aNode
|
|
*/
|
|
onNode: function STW_onNode(aNode) {
|
|
// Make sure the node is part of a branch that's guaranteed to be
|
|
// hovered. Otherwise, return true to abruptly halt walking this
|
|
// syntax tree branch. This is a really efficient optimization.
|
|
return ParserHelpers.isWithinLines(aNode, aLine);
|
|
},
|
|
|
|
/**
|
|
* Callback invoked for each identifier node.
|
|
* @param Node aNode
|
|
*/
|
|
onIdentifier: function STW_onIdentifier(aNode) {
|
|
// Make sure the identifier itself is hovered.
|
|
let hovered = ParserHelpers.isWithinBounds(aNode, aLine, aColumn);
|
|
if (!hovered) {
|
|
return;
|
|
}
|
|
|
|
// Make sure the identifier is part of a "new" expression or
|
|
// "call" expression node.
|
|
let expression = ParserHelpers.getEnclosingFunctionExpression(aNode);
|
|
if (!expression) {
|
|
return;
|
|
}
|
|
|
|
// Found an identifier node that is part of a "new" expression or
|
|
// "call" expression node. However, it may be an argument, not a callee.
|
|
if (ParserHelpers.isFunctionCalleeArgument(aNode)) {
|
|
// It's an argument.
|
|
if (self.functionIdentifiersCache.has(aNode.name)) {
|
|
// It's a function as an argument.
|
|
func = {
|
|
functionName: aNode.name,
|
|
functionLocation: aNode.loc || aNode._parent.loc
|
|
};
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Found a valid "new" expression or "call" expression node.
|
|
func = {
|
|
functionName: aNode.name,
|
|
functionLocation: ParserHelpers.getFunctionCalleeInfo(expression).loc
|
|
};
|
|
|
|
// Abruptly halt walking the syntax tree.
|
|
this.break = true;
|
|
}
|
|
});
|
|
|
|
return func;
|
|
},
|
|
|
|
/**
|
|
* Gets all the function identifiers in this syntax tree (both the
|
|
* function names and their inferred names).
|
|
*
|
|
* @return array
|
|
* An array of strings.
|
|
*/
|
|
get functionIdentifiersCache() {
|
|
if (this._functionIdentifiersCache) {
|
|
return this._functionIdentifiersCache;
|
|
}
|
|
let functionDefinitions = this.getNamedFunctionDefinitions("");
|
|
let functionIdentifiers = new Set();
|
|
|
|
for (let { functionName, inferredName } of functionDefinitions) {
|
|
functionIdentifiers.add(functionName);
|
|
functionIdentifiers.add(inferredName);
|
|
}
|
|
return this._functionIdentifiersCache = functionIdentifiers;
|
|
},
|
|
|
|
AST: null,
|
|
url: "",
|
|
length: 0,
|
|
offset: 0
|
|
};
|
|
|
|
/**
|
|
* Parser utility methods.
|
|
*/
|
|
let ParserHelpers = {
|
|
/**
|
|
* Checks if a node's bounds contains a specified line.
|
|
*
|
|
* @param Node aNode
|
|
* The node's bounds used as reference.
|
|
* @param number aLine
|
|
* The line number to check.
|
|
* @return boolean
|
|
* True if the line and column is contained in the node's bounds.
|
|
*/
|
|
isWithinLines: function PH_isWithinLines(aNode, aLine) {
|
|
// Not all nodes have location information attached.
|
|
if (!aNode.loc) {
|
|
return this.isWithinLines(aNode._parent, aLine);
|
|
}
|
|
return aNode.loc.start.line <= aLine && aNode.loc.end.line >= aLine;
|
|
},
|
|
|
|
/**
|
|
* Checks if a node's bounds contains a specified line and column.
|
|
*
|
|
* @param Node aNode
|
|
* The node's bounds used as reference.
|
|
* @param number aLine
|
|
* The line number to check.
|
|
* @param number aColumn
|
|
* The column number to check.
|
|
* @return boolean
|
|
* True if the line and column is contained in the node's bounds.
|
|
*/
|
|
isWithinBounds: function PH_isWithinBounds(aNode, aLine, aColumn) {
|
|
// Not all nodes have location information attached.
|
|
if (!aNode.loc) {
|
|
return this.isWithinBounds(aNode._parent, aLine, aColumn);
|
|
}
|
|
return aNode.loc.start.line == aLine && aNode.loc.end.line == aLine &&
|
|
aNode.loc.start.column <= aColumn && aNode.loc.end.column >= aColumn;
|
|
},
|
|
|
|
/**
|
|
* Try to infer a function expression's name & other details based on the
|
|
* enclosing VariableDeclarator, AssignmentExpression or ObjectExpression node.
|
|
*
|
|
* @param Node aNode
|
|
* The function expression node to get the name for.
|
|
* @return object
|
|
* The inferred function name, or empty string can't infer name,
|
|
* along with the chain (a generic "context", like a prototype chain)
|
|
* and location if available.
|
|
*/
|
|
inferFunctionExpressionInfo: function PH_inferFunctionExpressionInfo(aNode) {
|
|
let parent = aNode._parent;
|
|
|
|
// A function expression may be defined in a variable declarator,
|
|
// e.g. var foo = function(){}, in which case it is possible to infer
|
|
// the variable name.
|
|
if (parent.type == "VariableDeclarator") {
|
|
return {
|
|
name: parent.id.name,
|
|
chain: null,
|
|
loc: parent.loc
|
|
};
|
|
}
|
|
|
|
// Function expressions can also be defined in assignment expressions,
|
|
// e.g. foo = function(){} or foo.bar = function(){}, in which case it is
|
|
// possible to infer the assignee name ("foo" and "bar" respectively).
|
|
if (parent.type == "AssignmentExpression") {
|
|
let assigneeChain = this.getAssignmentExpressionAssigneeChain(parent);
|
|
let assigneeLeaf = assigneeChain.pop();
|
|
return {
|
|
name: assigneeLeaf,
|
|
chain: assigneeChain,
|
|
loc: parent.left.loc
|
|
};
|
|
}
|
|
|
|
// If a function expression is defined in an object expression,
|
|
// e.g. { foo: function(){} }, then it is possible to infer the name
|
|
// from the corresponding property.
|
|
if (parent.type == "ObjectExpression") {
|
|
let propertyDetails = this.getObjectExpressionPropertyKeyForValue(aNode);
|
|
let propertyChain = this.getObjectExpressionPropertyChain(parent);
|
|
let propertyLeaf = propertyDetails.name;
|
|
return {
|
|
name: propertyLeaf,
|
|
chain: propertyChain,
|
|
loc: propertyDetails.loc
|
|
};
|
|
}
|
|
|
|
// Can't infer the function expression's name.
|
|
return {
|
|
name: "",
|
|
chain: null,
|
|
loc: null
|
|
};
|
|
},
|
|
|
|
/**
|
|
* Gets details about an object expression's property to which a specified
|
|
* value is assigned. For example, the node returned for the value 42 in
|
|
* "{ foo: { bar: 42 } }" is "bar".
|
|
*
|
|
* @param Node aNode
|
|
* The value node assigned to a property in an object expression.
|
|
* @return object
|
|
* The details about the assignee property node.
|
|
*/
|
|
getObjectExpressionPropertyKeyForValue:
|
|
function PH_getObjectExpressionPropertyKeyForValue(aNode) {
|
|
let parent = aNode._parent;
|
|
if (parent.type != "ObjectExpression") {
|
|
return null;
|
|
}
|
|
for (let property of parent.properties) {
|
|
if (property.value == aNode) {
|
|
return property.key;
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Gets an object expression property chain to its parent variable declarator.
|
|
* For example, the chain to "baz" in "foo = { bar: { baz: { } } }" is
|
|
* ["foo", "bar", "baz"].
|
|
*
|
|
* @param Node aNode
|
|
* The object expression node to begin the scan from.
|
|
* @param array aStore [optional]
|
|
* The chain to store the nodes into.
|
|
* @return array
|
|
* The chain to the parent variable declarator, as strings.
|
|
*/
|
|
getObjectExpressionPropertyChain:
|
|
function PH_getObjectExpressionPropertyChain(aNode, aStore = []) {
|
|
switch (aNode.type) {
|
|
case "ObjectExpression":
|
|
this.getObjectExpressionPropertyChain(aNode._parent, aStore);
|
|
|
|
let propertyDetails = this.getObjectExpressionPropertyKeyForValue(aNode);
|
|
if (propertyDetails) {
|
|
aStore.push(this.getObjectExpressionPropertyKeyForValue(aNode).name);
|
|
}
|
|
break;
|
|
// Handle "foo.bar = { ... }" since it's commonly used when defining an
|
|
// object's prototype methods; for example: "Foo.prototype = { ... }".
|
|
case "AssignmentExpression":
|
|
this.getAssignmentExpressionAssigneeChain(aNode, aStore);
|
|
break;
|
|
// Additionally handle stuff like "foo = bar.baz({ ... })", because it's
|
|
// commonly used in prototype-based inheritance in many libraries;
|
|
// for example: "Foo.Bar = Baz.extend({ ... })".
|
|
case "NewExpression":
|
|
case "CallExpression":
|
|
this.getObjectExpressionPropertyChain(aNode._parent, aStore);
|
|
break;
|
|
// End of the chain.
|
|
case "VariableDeclarator":
|
|
aStore.push(aNode.id.name);
|
|
break;
|
|
}
|
|
return aStore;
|
|
},
|
|
|
|
/**
|
|
* Gets the assignee property chain in an assignment expression.
|
|
* For example, the chain in "foo.bar.baz = 42" is ["foo", "bar", "baz"].
|
|
*
|
|
* @param Node aNode
|
|
* The assignment expression node to begin the scan from.
|
|
* @param array aStore
|
|
* The chain to store the nodes into.
|
|
* @param array aStore [optional]
|
|
* The chain to store the nodes into.
|
|
* @return array
|
|
* The full assignee chain, as strings.
|
|
*/
|
|
getAssignmentExpressionAssigneeChain:
|
|
function PH_getAssignmentExpressionAssigneeChain(aNode, aStore = []) {
|
|
switch (aNode.type) {
|
|
case "AssignmentExpression":
|
|
this.getAssignmentExpressionAssigneeChain(aNode.left, aStore);
|
|
break;
|
|
case "MemberExpression":
|
|
this.getAssignmentExpressionAssigneeChain(aNode.object, aStore);
|
|
this.getAssignmentExpressionAssigneeChain(aNode.property, aStore);
|
|
break;
|
|
case "ThisExpression":
|
|
// Such expressions may appear in an assignee chain, for example
|
|
// "this.foo.bar = baz", however it seems better to ignore such nodes
|
|
// and limit the chain to ["foo", "bar"].
|
|
break;
|
|
case "Identifier":
|
|
aStore.push(aNode.name);
|
|
break;
|
|
}
|
|
return aStore;
|
|
},
|
|
|
|
/**
|
|
* Gets the "new" expression or "call" expression containing the specified
|
|
* node. If the node is not enclosed in either of these expression types,
|
|
* null is returned.
|
|
*
|
|
* @param Node aNode
|
|
* The child node of an enclosing "new" expression or "call" expression.
|
|
* @return Node
|
|
* The enclosing "new" expression or "call" expression node, or
|
|
* null if nothing is found.
|
|
*/
|
|
getEnclosingFunctionExpression:
|
|
function PH_getEnclosingFunctionExpression(aNode) {
|
|
switch (aNode.type) {
|
|
case "NewExpression":
|
|
case "CallExpression":
|
|
return aNode;
|
|
case "MemberExpression":
|
|
case "Identifier":
|
|
return this.getEnclosingFunctionExpression(aNode._parent);
|
|
default:
|
|
return null;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Gets the name and { line, column } location of a "new" expression or
|
|
* "call" expression's callee node.
|
|
*
|
|
* @param Node aNode
|
|
* The "new" expression or "call" expression to get the callee info for.
|
|
* @return object
|
|
* An object containing the name and location as properties, or
|
|
* null if nothing is found.
|
|
*/
|
|
getFunctionCalleeInfo: function PH_getFunctionCalleeInfo(aNode) {
|
|
switch (aNode.type) {
|
|
case "NewExpression":
|
|
case "CallExpression":
|
|
return this.getFunctionCalleeInfo(aNode.callee);
|
|
case "MemberExpression":
|
|
return this.getFunctionCalleeInfo(aNode.property);
|
|
case "Identifier":
|
|
return {
|
|
name: aNode.name,
|
|
loc: aNode.loc || (aNode._parent || {}).loc
|
|
};
|
|
default:
|
|
return null;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Determines if an identifier node is part of a "new" expression or
|
|
* "call" expression's callee arguments.
|
|
*
|
|
* @param Node aNode
|
|
* The node to determine if part of a function's arguments.
|
|
* @return boolean
|
|
* True if the identifier is an argument, false otherwise.
|
|
*/
|
|
isFunctionCalleeArgument: function PH_isFunctionCalleeArgument(aNode) {
|
|
if (!aNode._parent) {
|
|
return false;
|
|
}
|
|
switch (aNode._parent.type) {
|
|
case "NewExpression":
|
|
case "CallExpression":
|
|
return aNode._parent.arguments.indexOf(aNode) != -1;
|
|
default:
|
|
return this.isFunctionCalleeArgument(aNode._parent);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* A visitor for a syntax tree generated by the reflection API.
|
|
* See https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API.
|
|
*
|
|
* All node types implement the following interface:
|
|
* interface Node {
|
|
* type: string;
|
|
* loc: SourceLocation | null;
|
|
* }
|
|
*/
|
|
let SyntaxTreeVisitor = {
|
|
/**
|
|
* Walks a syntax tree.
|
|
*
|
|
* @param object aTree
|
|
* The AST nodes generated by the reflection API
|
|
* @param object aCallbacks
|
|
* A map of all the callbacks to invoke when passing through certain
|
|
* types of noes (e.g: onFunctionDeclaration, onBlockStatement etc.).
|
|
*/
|
|
walk: function STV_walk(aTree, aCallbacks) {
|
|
this[aTree.type](aTree, aCallbacks);
|
|
},
|
|
|
|
/**
|
|
* A flag checked on each node in the syntax tree. If true, walking is
|
|
* abruptly halted.
|
|
*/
|
|
break: false,
|
|
|
|
/**
|
|
* A complete program source tree.
|
|
*
|
|
* interface Program <: Node {
|
|
* type: "Program";
|
|
* body: [ Statement ];
|
|
* }
|
|
*/
|
|
Program: function STV_Program(aNode, aCallbacks) {
|
|
if (aCallbacks.onProgram) {
|
|
aCallbacks.onProgram(aNode);
|
|
}
|
|
for (let statement of aNode.body) {
|
|
this[statement.type](statement, aNode, aCallbacks);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Any statement.
|
|
*
|
|
* interface Statement <: Node { }
|
|
*/
|
|
Statement: function STV_Statement(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onStatement) {
|
|
aCallbacks.onStatement(aNode);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* An empty statement, i.e., a solitary semicolon.
|
|
*
|
|
* interface EmptyStatement <: Statement {
|
|
* type: "EmptyStatement";
|
|
* }
|
|
*/
|
|
EmptyStatement: function STV_EmptyStatement(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onEmptyStatement) {
|
|
aCallbacks.onEmptyStatement(aNode);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* A block statement, i.e., a sequence of statements surrounded by braces.
|
|
*
|
|
* interface BlockStatement <: Statement {
|
|
* type: "BlockStatement";
|
|
* body: [ Statement ];
|
|
* }
|
|
*/
|
|
BlockStatement: function STV_BlockStatement(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onBlockStatement) {
|
|
aCallbacks.onBlockStatement(aNode);
|
|
}
|
|
for (let statement of aNode.body) {
|
|
this[statement.type](statement, aNode, aCallbacks);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* An expression statement, i.e., a statement consisting of a single expression.
|
|
*
|
|
* interface ExpressionStatement <: Statement {
|
|
* type: "ExpressionStatement";
|
|
* expression: Expression;
|
|
* }
|
|
*/
|
|
ExpressionStatement: function STV_ExpressionStatement(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onExpressionStatement) {
|
|
aCallbacks.onExpressionStatement(aNode);
|
|
}
|
|
this[aNode.expression.type](aNode.expression, aNode, aCallbacks);
|
|
},
|
|
|
|
/**
|
|
* An if statement.
|
|
*
|
|
* interface IfStatement <: Statement {
|
|
* type: "IfStatement";
|
|
* test: Expression;
|
|
* consequent: Statement;
|
|
* alternate: Statement | null;
|
|
* }
|
|
*/
|
|
IfStatement: function STV_IfStatement(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onIfStatement) {
|
|
aCallbacks.onIfStatement(aNode);
|
|
}
|
|
this[aNode.test.type](aNode.test, aNode, aCallbacks);
|
|
this[aNode.consequent.type](aNode.consequent, aNode, aCallbacks);
|
|
if (aNode.alternate) {
|
|
this[aNode.alternate.type](aNode.alternate, aNode, aCallbacks);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* A labeled statement, i.e., a statement prefixed by a break/continue label.
|
|
*
|
|
* interface LabeledStatement <: Statement {
|
|
* type: "LabeledStatement";
|
|
* label: Identifier;
|
|
* body: Statement;
|
|
* }
|
|
*/
|
|
LabeledStatement: function STV_LabeledStatement(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onLabeledStatement) {
|
|
aCallbacks.onLabeledStatement(aNode);
|
|
}
|
|
this[aNode.label.type](aNode.label, aNode, aCallbacks);
|
|
this[aNode.body.type](aNode.body, aNode, aCallbacks);
|
|
},
|
|
|
|
/**
|
|
* A break statement.
|
|
*
|
|
* interface BreakStatement <: Statement {
|
|
* type: "BreakStatement";
|
|
* label: Identifier | null;
|
|
* }
|
|
*/
|
|
BreakStatement: function STV_BreakStatement(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onBreakStatement) {
|
|
aCallbacks.onBreakStatement(aNode);
|
|
}
|
|
if (aNode.label) {
|
|
this[aNode.label.type](aNode.label, aNode, aCallbacks);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* A continue statement.
|
|
*
|
|
* interface ContinueStatement <: Statement {
|
|
* type: "ContinueStatement";
|
|
* label: Identifier | null;
|
|
* }
|
|
*/
|
|
ContinueStatement: function STV_ContinueStatement(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onContinueStatement) {
|
|
aCallbacks.onContinueStatement(aNode);
|
|
}
|
|
if (aNode.label) {
|
|
this[aNode.label.type](aNode.label, aNode, aCallbacks);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* A with statement.
|
|
*
|
|
* interface WithStatement <: Statement {
|
|
* type: "WithStatement";
|
|
* object: Expression;
|
|
* body: Statement;
|
|
* }
|
|
*/
|
|
WithStatement: function STV_WithStatement(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onWithStatement) {
|
|
aCallbacks.onWithStatement(aNode);
|
|
}
|
|
this[aNode.object.type](aNode.object, aNode, aCallbacks);
|
|
this[aNode.body.type](aNode.body, aNode, aCallbacks);
|
|
},
|
|
|
|
/**
|
|
* A switch statement. The lexical flag is metadata indicating whether the
|
|
* switch statement contains any unnested let declarations (and therefore
|
|
* introduces a new lexical scope).
|
|
*
|
|
* interface SwitchStatement <: Statement {
|
|
* type: "SwitchStatement";
|
|
* discriminant: Expression;
|
|
* cases: [ SwitchCase ];
|
|
* lexical: boolean;
|
|
* }
|
|
*/
|
|
SwitchStatement: function STV_SwitchStatement(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onSwitchStatement) {
|
|
aCallbacks.onSwitchStatement(aNode);
|
|
}
|
|
this[aNode.discriminant.type](aNode.discriminant, aNode, aCallbacks);
|
|
for (let _case of aNode.cases) {
|
|
this[_case.type](_case, aNode, aCallbacks);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* A return statement.
|
|
*
|
|
* interface ReturnStatement <: Statement {
|
|
* type: "ReturnStatement";
|
|
* argument: Expression | null;
|
|
* }
|
|
*/
|
|
ReturnStatement: function STV_ReturnStatement(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onReturnStatement) {
|
|
aCallbacks.onReturnStatement(aNode);
|
|
}
|
|
if (aNode.argument) {
|
|
this[aNode.argument.type](aNode.argument, aNode, aCallbacks);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* A throw statement.
|
|
*
|
|
* interface ThrowStatement <: Statement {
|
|
* type: "ThrowStatement";
|
|
* argument: Expression;
|
|
* }
|
|
*/
|
|
ThrowStatement: function STV_ThrowStatement(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onThrowStatement) {
|
|
aCallbacks.onThrowStatement(aNode);
|
|
}
|
|
this[aNode.argument.type](aNode.argument, aNode, aCallbacks);
|
|
},
|
|
|
|
/**
|
|
* A try statement.
|
|
*
|
|
* interface TryStatement <: Statement {
|
|
* type: "TryStatement";
|
|
* block: BlockStatement;
|
|
* handler: CatchClause | null;
|
|
* guardedHandlers: [ CatchClause ];
|
|
* finalizer: BlockStatement | null;
|
|
* }
|
|
*/
|
|
TryStatement: function STV_TryStatement(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onTryStatement) {
|
|
aCallbacks.onTryStatement(aNode);
|
|
}
|
|
this[aNode.block.type](aNode.block, aNode, aCallbacks);
|
|
if (aNode.handler) {
|
|
this[aNode.handler.type](aNode.handler, aNode, aCallbacks);
|
|
}
|
|
for (let guardedHandler of aNode.guardedHandlers) {
|
|
this[guardedHandler.type](guardedHandler, aNode, aCallbacks);
|
|
}
|
|
if (aNode.finalizer) {
|
|
this[aNode.finalizer.type](aNode.finalizer, aNode, aCallbacks);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* A while statement.
|
|
*
|
|
* interface WhileStatement <: Statement {
|
|
* type: "WhileStatement";
|
|
* test: Expression;
|
|
* body: Statement;
|
|
* }
|
|
*/
|
|
WhileStatement: function STV_WhileStatement(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onWhileStatement) {
|
|
aCallbacks.onWhileStatement(aNode);
|
|
}
|
|
this[aNode.test.type](aNode.test, aNode, aCallbacks);
|
|
this[aNode.body.type](aNode.body, aNode, aCallbacks);
|
|
},
|
|
|
|
/**
|
|
* A do/while statement.
|
|
*
|
|
* interface DoWhileStatement <: Statement {
|
|
* type: "DoWhileStatement";
|
|
* body: Statement;
|
|
* test: Expression;
|
|
* }
|
|
*/
|
|
DoWhileStatement: function STV_DoWhileStatement(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onDoWhileStatement) {
|
|
aCallbacks.onDoWhileStatement(aNode);
|
|
}
|
|
this[aNode.body.type](aNode.body, aNode, aCallbacks);
|
|
this[aNode.test.type](aNode.test, aNode, aCallbacks);
|
|
},
|
|
|
|
/**
|
|
* A for statement.
|
|
*
|
|
* interface ForStatement <: Statement {
|
|
* type: "ForStatement";
|
|
* init: VariableDeclaration | Expression | null;
|
|
* test: Expression | null;
|
|
* update: Expression | null;
|
|
* body: Statement;
|
|
* }
|
|
*/
|
|
ForStatement: function STV_ForStatement(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onForStatement) {
|
|
aCallbacks.onForStatement(aNode);
|
|
}
|
|
if (aNode.init) {
|
|
this[aNode.init.type](aNode.init, aNode, aCallbacks);
|
|
}
|
|
if (aNode.test) {
|
|
this[aNode.test.type](aNode.test, aNode, aCallbacks);
|
|
}
|
|
if (aNode.update) {
|
|
this[aNode.update.type](aNode.update, aNode, aCallbacks);
|
|
}
|
|
this[aNode.body.type](aNode.body, aNode, aCallbacks);
|
|
},
|
|
|
|
/**
|
|
* A for/in statement, or, if each is true, a for each/in statement.
|
|
*
|
|
* interface ForInStatement <: Statement {
|
|
* type: "ForInStatement";
|
|
* left: VariableDeclaration | Expression;
|
|
* right: Expression;
|
|
* body: Statement;
|
|
* each: boolean;
|
|
* }
|
|
*/
|
|
ForInStatement: function STV_ForInStatement(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onForInStatement) {
|
|
aCallbacks.onForInStatement(aNode);
|
|
}
|
|
this[aNode.left.type](aNode.left, aNode, aCallbacks);
|
|
this[aNode.right.type](aNode.right, aNode, aCallbacks);
|
|
this[aNode.body.type](aNode.body, aNode, aCallbacks);
|
|
},
|
|
|
|
/**
|
|
* A for/of statement.
|
|
*
|
|
* interface ForOfStatement <: Statement {
|
|
* type: "ForOfStatement";
|
|
* left: VariableDeclaration | Expression;
|
|
* right: Expression;
|
|
* body: Statement;
|
|
* }
|
|
*/
|
|
ForOfStatement: function STV_ForOfStatement(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onForOfStatement) {
|
|
aCallbacks.onForOfStatement(aNode);
|
|
}
|
|
this[aNode.left.type](aNode.left, aNode, aCallbacks);
|
|
this[aNode.right.type](aNode.right, aNode, aCallbacks);
|
|
this[aNode.body.type](aNode.body, aNode, aCallbacks);
|
|
},
|
|
|
|
/**
|
|
* A let statement.
|
|
*
|
|
* interface LetStatement <: Statement {
|
|
* type: "LetStatement";
|
|
* head: [ { id: Pattern, init: Expression | null } ];
|
|
* body: Statement;
|
|
* }
|
|
*/
|
|
LetStatement: function STV_LetStatement(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onLetStatement) {
|
|
aCallbacks.onLetStatement(aNode);
|
|
}
|
|
for (let { id, init } of aNode.head) {
|
|
this[id.type](id, aNode, aCallbacks);
|
|
if (init) {
|
|
this[init.type](init, aNode, aCallbacks);
|
|
}
|
|
}
|
|
this[aNode.body.type](aNode.body, aNode, aCallbacks);
|
|
},
|
|
|
|
/**
|
|
* A debugger statement.
|
|
*
|
|
* interface DebuggerStatement <: Statement {
|
|
* type: "DebuggerStatement";
|
|
* }
|
|
*/
|
|
DebuggerStatement: function STV_DebuggerStatement(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onDebuggerStatement) {
|
|
aCallbacks.onDebuggerStatement(aNode);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Any declaration node. Note that declarations are considered statements;
|
|
* this is because declarations can appear in any statement context in the
|
|
* language recognized by the SpiderMonkey parser.
|
|
*
|
|
* interface Declaration <: Statement { }
|
|
*/
|
|
Declaration: function STV_Declaration(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onDeclaration) {
|
|
aCallbacks.onDeclaration(aNode);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* A function declaration.
|
|
*
|
|
* interface FunctionDeclaration <: Function, Declaration {
|
|
* type: "FunctionDeclaration";
|
|
* id: Identifier;
|
|
* params: [ Pattern ];
|
|
* defaults: [ Expression ];
|
|
* rest: Identifier | null;
|
|
* body: BlockStatement | Expression;
|
|
* generator: boolean;
|
|
* expression: boolean;
|
|
* }
|
|
*/
|
|
FunctionDeclaration: function STV_FunctionDeclaration(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onFunctionDeclaration) {
|
|
aCallbacks.onFunctionDeclaration(aNode);
|
|
}
|
|
this[aNode.id.type](aNode.id, aNode, aCallbacks);
|
|
for (let param of aNode.params) {
|
|
this[param.type](param, aNode, aCallbacks);
|
|
}
|
|
for (let _default of aNode.defaults) {
|
|
this[_default.type](_default, aNode, aCallbacks);
|
|
}
|
|
if (aNode.rest) {
|
|
this[aNode.rest.type](aNode.rest, aNode, aCallbacks);
|
|
}
|
|
this[aNode.body.type](aNode.body, aNode, aCallbacks);
|
|
},
|
|
|
|
/**
|
|
* A variable declaration, via one of var, let, or const.
|
|
*
|
|
* interface VariableDeclaration <: Declaration {
|
|
* type: "VariableDeclaration";
|
|
* declarations: [ VariableDeclarator ];
|
|
* kind: "var" | "let" | "const";
|
|
* }
|
|
*/
|
|
VariableDeclaration: function STV_VariableDeclaration(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onVariableDeclaration) {
|
|
aCallbacks.onVariableDeclaration(aNode);
|
|
}
|
|
for (let declaration of aNode.declarations) {
|
|
this[declaration.type](declaration, aNode, aCallbacks);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* A variable declarator.
|
|
*
|
|
* interface VariableDeclarator <: Node {
|
|
* type: "VariableDeclarator";
|
|
* id: Pattern;
|
|
* init: Expression | null;
|
|
* }
|
|
*/
|
|
VariableDeclarator: function STV_VariableDeclarator(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onVariableDeclarator) {
|
|
aCallbacks.onVariableDeclarator(aNode);
|
|
}
|
|
this[aNode.id.type](aNode.id, aNode, aCallbacks);
|
|
if (aNode.init) {
|
|
this[aNode.init.type](aNode.init, aNode, aCallbacks);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Any expression node. Since the left-hand side of an assignment may be any
|
|
* expression in general, an expression can also be a pattern.
|
|
*
|
|
* interface Expression <: Node, Pattern { }
|
|
*/
|
|
Expression: function STV_Expression(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onExpression) {
|
|
aCallbacks.onExpression(aNode);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* A this expression.
|
|
*
|
|
* interface ThisExpression <: Expression {
|
|
* type: "ThisExpression";
|
|
* }
|
|
*/
|
|
ThisExpression: function STV_ThisExpression(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onThisExpression) {
|
|
aCallbacks.onThisExpression(aNode);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* An array expression.
|
|
*
|
|
* interface ArrayExpression <: Expression {
|
|
* type: "ArrayExpression";
|
|
* elements: [ Expression | null ];
|
|
* }
|
|
*/
|
|
ArrayExpression: function STV_ArrayExpression(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onArrayExpression) {
|
|
aCallbacks.onArrayExpression(aNode);
|
|
}
|
|
for (let element of aNode.elements) {
|
|
// 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 STV_ObjectExpression(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onObjectExpression) {
|
|
aCallbacks.onObjectExpression(aNode);
|
|
}
|
|
for (let { key, value } of aNode.properties) {
|
|
this[key.type](key, aNode, aCallbacks);
|
|
this[value.type](value, aNode, aCallbacks);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* A function expression.
|
|
*
|
|
* interface FunctionExpression <: Function, Expression {
|
|
* type: "FunctionExpression";
|
|
* id: Identifier | null;
|
|
* params: [ Pattern ];
|
|
* defaults: [ Expression ];
|
|
* rest: Identifier | null;
|
|
* body: BlockStatement | Expression;
|
|
* generator: boolean;
|
|
* expression: boolean;
|
|
* }
|
|
*/
|
|
FunctionExpression: function STV_FunctionExpression(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onFunctionExpression) {
|
|
aCallbacks.onFunctionExpression(aNode);
|
|
}
|
|
if (aNode.id) {
|
|
this[aNode.id.type](aNode.id, aNode, aCallbacks);
|
|
}
|
|
for (let param of aNode.params) {
|
|
this[param.type](param, aNode, aCallbacks);
|
|
}
|
|
for (let _default of aNode.defaults) {
|
|
this[_default.type](_default, aNode, aCallbacks);
|
|
}
|
|
if (aNode.rest) {
|
|
this[aNode.rest.type](aNode.rest, aNode, aCallbacks);
|
|
}
|
|
this[aNode.body.type](aNode.body, aNode, aCallbacks);
|
|
},
|
|
|
|
/**
|
|
* An arrow expression.
|
|
*
|
|
* interface ArrowExpression <: Function, Expression {
|
|
* type: "ArrowExpression";
|
|
* params: [ Pattern ];
|
|
* defaults: [ Expression ];
|
|
* rest: Identifier | null;
|
|
* body: BlockStatement | Expression;
|
|
* generator: boolean;
|
|
* expression: boolean;
|
|
* }
|
|
*/
|
|
ArrowExpression: function STV_ArrowExpression(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onArrowExpression) {
|
|
aCallbacks.onArrowExpression(aNode);
|
|
}
|
|
for (let param of aNode.params) {
|
|
this[param.type](param, aNode, aCallbacks);
|
|
}
|
|
for (let _default of aNode.defaults) {
|
|
this[_default.type](_default, aNode, aCallbacks);
|
|
}
|
|
if (aNode.rest) {
|
|
this[aNode.rest.type](aNode.rest, aNode, aCallbacks);
|
|
}
|
|
this[aNode.body.type](aNode.body, aNode, aCallbacks);
|
|
},
|
|
|
|
/**
|
|
* A sequence expression, i.e., a comma-separated sequence of expressions.
|
|
*
|
|
* interface SequenceExpression <: Expression {
|
|
* type: "SequenceExpression";
|
|
* expressions: [ Expression ];
|
|
* }
|
|
*/
|
|
SequenceExpression: function STV_SequenceExpression(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onSequenceExpression) {
|
|
aCallbacks.onSequenceExpression(aNode);
|
|
}
|
|
for (let expression of aNode.expressions) {
|
|
this[expression.type](expression, aNode, aCallbacks);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* A unary operator expression.
|
|
*
|
|
* interface UnaryExpression <: Expression {
|
|
* type: "UnaryExpression";
|
|
* operator: UnaryOperator;
|
|
* prefix: boolean;
|
|
* argument: Expression;
|
|
* }
|
|
*/
|
|
UnaryExpression: function STV_UnaryExpression(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onUnaryExpression) {
|
|
aCallbacks.onUnaryExpression(aNode);
|
|
}
|
|
this[aNode.argument.type](aNode.argument, aNode, aCallbacks);
|
|
},
|
|
|
|
/**
|
|
* A binary operator expression.
|
|
*
|
|
* interface BinaryExpression <: Expression {
|
|
* type: "BinaryExpression";
|
|
* operator: BinaryOperator;
|
|
* left: Expression;
|
|
* right: Expression;
|
|
* }
|
|
*/
|
|
BinaryExpression: function STV_BinaryExpression(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onBinaryExpression) {
|
|
aCallbacks.onBinaryExpression(aNode);
|
|
}
|
|
this[aNode.left.type](aNode.left, aNode, aCallbacks);
|
|
this[aNode.right.type](aNode.right, aNode, aCallbacks);
|
|
},
|
|
|
|
/**
|
|
* An assignment operator expression.
|
|
*
|
|
* interface AssignmentExpression <: Expression {
|
|
* type: "AssignmentExpression";
|
|
* operator: AssignmentOperator;
|
|
* left: Expression;
|
|
* right: Expression;
|
|
* }
|
|
*/
|
|
AssignmentExpression: function STV_AssignmentExpression(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onAssignmentExpression) {
|
|
aCallbacks.onAssignmentExpression(aNode);
|
|
}
|
|
this[aNode.left.type](aNode.left, aNode, aCallbacks);
|
|
this[aNode.right.type](aNode.right, aNode, aCallbacks);
|
|
},
|
|
|
|
/**
|
|
* An update (increment or decrement) operator expression.
|
|
*
|
|
* interface UpdateExpression <: Expression {
|
|
* type: "UpdateExpression";
|
|
* operator: UpdateOperator;
|
|
* argument: Expression;
|
|
* prefix: boolean;
|
|
* }
|
|
*/
|
|
UpdateExpression: function STV_UpdateExpression(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onUpdateExpression) {
|
|
aCallbacks.onUpdateExpression(aNode);
|
|
}
|
|
this[aNode.argument.type](aNode.argument, aNode, aCallbacks);
|
|
},
|
|
|
|
/**
|
|
* A logical operator expression.
|
|
*
|
|
* interface LogicalExpression <: Expression {
|
|
* type: "LogicalExpression";
|
|
* operator: LogicalOperator;
|
|
* left: Expression;
|
|
* right: Expression;
|
|
* }
|
|
*/
|
|
LogicalExpression: function STV_LogicalExpression(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onLogicalExpression) {
|
|
aCallbacks.onLogicalExpression(aNode);
|
|
}
|
|
this[aNode.left.type](aNode.left, aNode, aCallbacks);
|
|
this[aNode.right.type](aNode.right, aNode, aCallbacks);
|
|
},
|
|
|
|
/**
|
|
* A conditional expression, i.e., a ternary ?/: expression.
|
|
*
|
|
* interface ConditionalExpression <: Expression {
|
|
* type: "ConditionalExpression";
|
|
* test: Expression;
|
|
* alternate: Expression;
|
|
* consequent: Expression;
|
|
* }
|
|
*/
|
|
ConditionalExpression: function STV_ConditionalExpression(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onConditionalExpression) {
|
|
aCallbacks.onConditionalExpression(aNode);
|
|
}
|
|
this[aNode.test.type](aNode.test, aNode, aCallbacks);
|
|
this[aNode.alternate.type](aNode.alternate, aNode, aCallbacks);
|
|
this[aNode.consequent.type](aNode.consequent, aNode, aCallbacks);
|
|
},
|
|
|
|
/**
|
|
* A new expression.
|
|
*
|
|
* interface NewExpression <: Expression {
|
|
* type: "NewExpression";
|
|
* callee: Expression;
|
|
* arguments: [ Expression | null ];
|
|
* }
|
|
*/
|
|
NewExpression: function STV_NewExpression(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onNewExpression) {
|
|
aCallbacks.onNewExpression(aNode);
|
|
}
|
|
this[aNode.callee.type](aNode.callee, aNode, aCallbacks);
|
|
for (let argument of aNode.arguments) {
|
|
if (argument) {
|
|
this[argument.type](argument, aNode, aCallbacks);
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* A function or method call expression.
|
|
*
|
|
* interface CallExpression <: Expression {
|
|
* type: "CallExpression";
|
|
* callee: Expression;
|
|
* arguments: [ Expression | null ];
|
|
* }
|
|
*/
|
|
CallExpression: function STV_CallExpression(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onCallExpression) {
|
|
aCallbacks.onCallExpression(aNode);
|
|
}
|
|
this[aNode.callee.type](aNode.callee, aNode, aCallbacks);
|
|
for (let argument of aNode.arguments) {
|
|
if (argument) {
|
|
this[argument.type](argument, aNode, aCallbacks);
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* A member expression. If computed is true, the node corresponds to a
|
|
* computed e1[e2] expression and property is an Expression. If computed is
|
|
* false, the node corresponds to a static e1.x expression and property is an
|
|
* Identifier.
|
|
*
|
|
* interface MemberExpression <: Expression {
|
|
* type: "MemberExpression";
|
|
* object: Expression;
|
|
* property: Identifier | Expression;
|
|
* computed: boolean;
|
|
* }
|
|
*/
|
|
MemberExpression: function STV_MemberExpression(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onMemberExpression) {
|
|
aCallbacks.onMemberExpression(aNode);
|
|
}
|
|
this[aNode.object.type](aNode.object, aNode, aCallbacks);
|
|
this[aNode.property.type](aNode.property, aNode, aCallbacks);
|
|
},
|
|
|
|
/**
|
|
* A yield expression.
|
|
*
|
|
* interface YieldExpression <: Expression {
|
|
* argument: Expression | null;
|
|
* }
|
|
*/
|
|
YieldExpression: function STV_YieldExpression(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onYieldExpression) {
|
|
aCallbacks.onYieldExpression(aNode);
|
|
}
|
|
if (aNode.argument) {
|
|
this[aNode.argument.type](aNode.argument, aNode, aCallbacks);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* An array comprehension. The blocks array corresponds to the sequence of
|
|
* for and for each blocks. The optional filter expression corresponds to the
|
|
* final if clause, if present.
|
|
*
|
|
* interface ComprehensionExpression <: Expression {
|
|
* body: Expression;
|
|
* blocks: [ ComprehensionBlock ];
|
|
* filter: Expression | null;
|
|
* }
|
|
*/
|
|
ComprehensionExpression: function STV_ComprehensionExpression(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onComprehensionExpression) {
|
|
aCallbacks.onComprehensionExpression(aNode);
|
|
}
|
|
this[aNode.body.type](aNode.body, aNode, aCallbacks);
|
|
for (let block of aNode.blocks) {
|
|
this[block.type](block, aNode, aCallbacks);
|
|
}
|
|
if (aNode.filter) {
|
|
this[aNode.filter.type](aNode.filter, aNode, aCallbacks);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* A generator expression. As with array comprehensions, the blocks array
|
|
* corresponds to the sequence of for and for each blocks, and the optional
|
|
* filter expression corresponds to the final if clause, if present.
|
|
*
|
|
* interface GeneratorExpression <: Expression {
|
|
* body: Expression;
|
|
* blocks: [ ComprehensionBlock ];
|
|
* filter: Expression | null;
|
|
* }
|
|
*/
|
|
GeneratorExpression: function STV_GeneratorExpression(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onGeneratorExpression) {
|
|
aCallbacks.onGeneratorExpression(aNode);
|
|
}
|
|
this[aNode.body.type](aNode.body, aNode, aCallbacks);
|
|
for (let block of aNode.blocks) {
|
|
this[block.type](block, aNode, aCallbacks);
|
|
}
|
|
if (aNode.filter) {
|
|
this[aNode.filter.type](aNode.filter, aNode, aCallbacks);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* A graph expression, aka "sharp literal," such as #1={ self: #1# }.
|
|
*
|
|
* interface GraphExpression <: Expression {
|
|
* index: uint32;
|
|
* expression: Literal;
|
|
* }
|
|
*/
|
|
GraphExpression: function STV_GraphExpression(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onGraphExpression) {
|
|
aCallbacks.onGraphExpression(aNode);
|
|
}
|
|
this[aNode.expression.type](aNode.expression, aNode, aCallbacks);
|
|
},
|
|
|
|
/**
|
|
* A graph index expression, aka "sharp variable," such as #1#.
|
|
*
|
|
* interface GraphIndexExpression <: Expression {
|
|
* index: uint32;
|
|
* }
|
|
*/
|
|
GraphIndexExpression: function STV_GraphIndexExpression(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onGraphIndexExpression) {
|
|
aCallbacks.onGraphIndexExpression(aNode);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* A let expression.
|
|
*
|
|
* interface LetExpression <: Expression {
|
|
* type: "LetExpression";
|
|
* head: [ { id: Pattern, init: Expression | null } ];
|
|
* body: Expression;
|
|
* }
|
|
*/
|
|
LetExpression: function STV_LetExpression(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onLetExpression) {
|
|
aCallbacks.onLetExpression(aNode);
|
|
}
|
|
for (let { id, init } of aNode.head) {
|
|
this[id.type](id, aNode, aCallbacks);
|
|
if (init) {
|
|
this[init.type](init, aNode, aCallbacks);
|
|
}
|
|
}
|
|
this[aNode.body.type](aNode.body, aNode, aCallbacks);
|
|
},
|
|
|
|
/**
|
|
* Any pattern.
|
|
*
|
|
* interface Pattern <: Node { }
|
|
*/
|
|
Pattern: function STV_Pattern(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onPattern) {
|
|
aCallbacks.onPattern(aNode);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* An object-destructuring pattern. A literal property in an object pattern
|
|
* can have either a string or number as its value.
|
|
*
|
|
* interface ObjectPattern <: Pattern {
|
|
* type: "ObjectPattern";
|
|
* properties: [ { key: Literal | Identifier, value: Pattern } ];
|
|
* }
|
|
*/
|
|
ObjectPattern: function STV_ObjectPattern(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onObjectPattern) {
|
|
aCallbacks.onObjectPattern(aNode);
|
|
}
|
|
for (let { key, value } of aNode.properties) {
|
|
this[key.type](key, aNode, aCallbacks);
|
|
this[value.type](value, aNode, aCallbacks);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* An array-destructuring pattern.
|
|
*
|
|
* interface ArrayPattern <: Pattern {
|
|
* type: "ArrayPattern";
|
|
* elements: [ Pattern | null ];
|
|
* }
|
|
*/
|
|
ArrayPattern: function STV_ArrayPattern(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onArrayPattern) {
|
|
aCallbacks.onArrayPattern(aNode);
|
|
}
|
|
for (let element of aNode.elements) {
|
|
if (element) {
|
|
this[element.type](element, aNode, aCallbacks);
|
|
}
|
|
}
|
|
},
|
|
|
|
/**
|
|
* A case (if test is an Expression) or default (if test is null) clause in
|
|
* the body of a switch statement.
|
|
*
|
|
* interface SwitchCase <: Node {
|
|
* type: "SwitchCase";
|
|
* test: Expression | null;
|
|
* consequent: [ Statement ];
|
|
* }
|
|
*/
|
|
SwitchCase: function STV_SwitchCase(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onSwitchCase) {
|
|
aCallbacks.onSwitchCase(aNode);
|
|
}
|
|
if (aNode.test) {
|
|
this[aNode.test.type](aNode.test, aNode, aCallbacks);
|
|
}
|
|
for (let consequent of aNode.consequent) {
|
|
this[consequent.type](consequent, aNode, aCallbacks);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* A catch clause following a try block. The optional guard property
|
|
* corresponds to the optional expression guard on the bound variable.
|
|
*
|
|
* interface CatchClause <: Node {
|
|
* type: "CatchClause";
|
|
* param: Pattern;
|
|
* guard: Expression | null;
|
|
* body: BlockStatement;
|
|
* }
|
|
*/
|
|
CatchClause: function STV_CatchClause(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onCatchClause) {
|
|
aCallbacks.onCatchClause(aNode);
|
|
}
|
|
this[aNode.param.type](aNode.param, aNode, aCallbacks);
|
|
if (aNode.guard) {
|
|
this[aNode.guard.type](aNode.guard, aNode, aCallbacks);
|
|
}
|
|
this[aNode.body.type](aNode.body, aNode, aCallbacks);
|
|
},
|
|
|
|
/**
|
|
* A for or for each block in an array comprehension or generator expression.
|
|
*
|
|
* interface ComprehensionBlock <: Node {
|
|
* left: Pattern;
|
|
* right: Expression;
|
|
* each: boolean;
|
|
* }
|
|
*/
|
|
ComprehensionBlock: function STV_ComprehensionBlock(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onComprehensionBlock) {
|
|
aCallbacks.onComprehensionBlock(aNode);
|
|
}
|
|
this[aNode.left.type](aNode.left, aNode, aCallbacks);
|
|
this[aNode.right.type](aNode.right, aNode, aCallbacks);
|
|
},
|
|
|
|
/**
|
|
* An identifier. Note that an identifier may be an expression or a
|
|
* destructuring pattern.
|
|
*
|
|
* interface Identifier <: Node, Expression, Pattern {
|
|
* type: "Identifier";
|
|
* name: string;
|
|
* }
|
|
*/
|
|
Identifier: function STV_Identifier(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onIdentifier) {
|
|
aCallbacks.onIdentifier(aNode);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* A literal token. Note that a literal can be an expression.
|
|
*
|
|
* interface Literal <: Node, Expression {
|
|
* type: "Literal";
|
|
* value: string | boolean | null | number | RegExp;
|
|
* }
|
|
*/
|
|
Literal: function STV_Literal(aNode, aParent, aCallbacks) {
|
|
aNode._parent = aParent;
|
|
|
|
if (this.break) {
|
|
return;
|
|
}
|
|
if (aCallbacks.onNode) {
|
|
if (aCallbacks.onNode(aNode, aParent) === false) {
|
|
return;
|
|
}
|
|
}
|
|
if (aCallbacks.onLiteral) {
|
|
aCallbacks.onLiteral(aNode);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Logs a warning.
|
|
*
|
|
* @param string aStr
|
|
* The message to be displayed.
|
|
* @param Exception aEx
|
|
* The thrown exception.
|
|
*/
|
|
function log(aStr, aEx) {
|
|
let msg = "Warning: " + aStr + ", " + aEx + "\n" + aEx.stack;
|
|
Cu.reportError(msg);
|
|
dump(msg + "\n");
|
|
};
|
|
|
|
XPCOMUtils.defineLazyGetter(Parser, "reflectionAPI", function() Reflect);
|