我最终使用UglifyJS2 和Dot/GraphViz 解决了这个问题,结合了上述答案和链接问题的答案。
对我来说,缺少的部分是如何过滤已解析的 AST。事实证明,UglifyJS 有 TreeWalker 对象,它基本上对 AST 的每个节点都应用了一个函数。这是我到目前为止的代码:
//to be run using nodejs
var UglifyJS = require('uglify-js')
var fs = require('fs');
var util = require('util');
var file = 'path/to/file...';
//read in the code
var code = fs.readFileSync(file, "utf8");
//parse it to AST
var toplevel = UglifyJS.parse(code);
//open the output DOT file
var out = fs.openSync('path/to/output/file...', 'w');
//output the start of a directed graph in DOT notation
fs.writeSync(out, 'digraph test{\n');
//use a tree walker to examine each node
var walker = new UglifyJS.TreeWalker(function(node){
//check for function calls
if (node instanceof UglifyJS.AST_Call) {
if(node.expression.name !== undefined)
{
//find where the calling function is defined
var p = walker.find_parent(UglifyJS.AST_Defun);
if(p !== undefined)
{
//filter out unneccessary stuff, eg calls to external libraries or constructors
if(node.expression.name == "$" || node.expression.name == "Number" || node.expression.name =="Date")
{
//NOTE: $ is from jquery, and causes problems if it's in the DOT file.
//It's also very frequent, so even replacing it with a safe string
//results in a very cluttered graph
}
else
{
fs.writeSync(out, p.name.name);
fs.writeSync(out, " -> ");
fs.writeSync(out, node.expression.name);
fs.writeSync(out, "\n");
}
}
else
{
//it's a top level function
fs.writeSync(out, node.expression.name);
fs.writeSync(out, "\n");
}
}
}
if(node instanceof UglifyJS.AST_Defun)
{
//defined but not called
fs.writeSync(out, node.name.name);
fs.writeSync(out, "\n");
}
});
//analyse the AST
toplevel.walk(walker);
//finally, write out the closing bracket
fs.writeSync(out, '}');
我用node运行它,然后把输出通过
dot -Tpng -o graph_name.png dot_file_name.dot
注意事项:
它提供了一个非常基本的图表——只有黑白,没有格式。
它根本不捕获 ajax,而且大概也不是 eval 或 with 之类的东西,如 others have mentioned。
此外,就目前而言,它在图中包括:由其他函数调用的函数(以及因此调用其他函数的函数)、独立调用的函数以及已定义但未调用的函数。
因此,它可能会遗漏相关的内容,或包含不相关的内容。虽然这是一个开始,而且似乎完成了我所追求的目标,也是最初导致我提出这个问题的原因。