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