2012-04-16 13 views
29

我看過「https://stackoverflow.com/questions/1385335/how-to-generate-function-call-graphs-for-javascript」,並嘗試過。如果你想得到一個抽象的語法樹,它運作良好。如何爲給定的javascript生成調用圖?

不幸的是Closure Compiler似乎只提供--print_tree,--print_ast--print_pass_graph。沒有一個對我有用。

我想查看哪些函數調用哪些函數的圖表。

+0

+1個不錯的問題 - – miku 2012-04-16 22:09:32

+0

爲什麼不使用支持分析javascript的開發工具? – Tushar 2013-06-30 14:20:43

+0

它看起來原來的線程已經消失,現在鏈接斷開了。 :-( – 2015-09-24 00:46:48

回答

5

如果你篩選closure --print_tree的輸出,你會得到你想要的。

例如採取以下文件:的closure --print_tree

  NAME fib 1 
       FUNCTION 1 
            CALL 5 
             NAME fib 5 
             SUB 5 
              NAME a 5 
              NUMBER 1.0 5 
            CALL 5 
             NAME fib 5 
             SUB 5 
              NAME a 5 
              NUMBER 2.0 5 
     EXPR_RESULT 9 
      CALL 9 
       GETPROP 9 
        NAME console 9 
        STRING log 9 
       CALL 9 
       CALL 9 
        NAME fib 9 
        CALL 9 
        CALL 9 
         NAME fib 9 
         NUMBER 5.0 9 

var fib = function(n) { 
    if (n < 2) { 
     return n; 
    } else { 
     return fib(n - 1) + fib(n - 2); 
    } 
}; 

console.log(fib(fib(5))); 

濾波器的輸出你可以看到所有的調用語句。

我寫了下面的腳本來做到這一點。

./call_tree

#! /usr/bin/env sh 
function make_tree() { 
    closure --print_tree $1 | grep $1 
} 

function parse_tree() { 
    gawk -f parse_tree.awk 
} 

if [[ "$1" = "--tree" ]]; then 
    make_tree $2 
else 
    make_tree $1 | parse_tree 
fi 

parse_tree.awk

BEGIN { 
    lines_c = 0 
    indent_width = 4 
    indent_offset = 0 
    string_offset = "" 
    calling = 0 
    call_indent = 0 
} 

{ 
    sub(/\[source_file.*$/, "") 
    sub(/\[free_call.*$/, "") 
} 

/SCRIPT/ { 
    indent_offset = calculate_indent($0) 
    root_indent = indent_offset - 1 
} 

/FUNCTION/ { 
    pl = get_previous_line() 
    if (calculate_indent(pl) < calculate_indent($0)) 
     print pl 
    print 
} 

{ 
    lines_v[lines_c] = $0 
    lines_c += 1 
} 

{ 
    indent = calculate_indent($0) 
    if (indent <= call_indent) { 
     calling = 0 
    } 
    if (calling) { 
     print 
    } 
} 

/CALL/ { 
    calling = 1 
    call_indent = calculate_indent($0) 
    print 
} 

/EXPR/{ 
    line_indent = calculate_indent($0) 
    if (line_indent == root_indent) { 
     if ($0 !~ /(FUNCTION)/) { 
      print 
     } 
    } 
} 

function calculate_indent(line) { 
    match(line, /^ */) 
    return int(RLENGTH/indent_width) - indent_offset 
} 

function get_previous_line() { 
    return lines_v[lines_c - 1] 
} 
+0

這是非常有趣的方法。我會多挖一點,但是謝謝!! – beatak 2012-04-19 16:04:13

+0

有沒有辦法獲得在腳本中計算每個函數調用的行號? – 2012-10-20 06:00:52

2

https://github.com/mishoo/UglifyJS 可以訪問在JavaScript中AST。

ast.coffee

util = require 'util' 
jsp = require('uglify-js').parser 

orig_code = """ 

var a = function (x) { 
    return x * x; 
}; 

function b (x) { 
    return a(x) 
} 

console.log(a(5)); 
console.log(b(5)); 

""" 

ast = jsp.parse(orig_code) 

console.log util.inspect ast, true, null, true 
4

我終於用這個和UglifyJS2Dot/GraphViz,在一種以上的答案相結合的答案和鏈接的問題。

對我而言,缺少的部分是如何過濾解析的AST。事實證明,UglifyJS擁有TreeWalker對象,它基本上將一個函數應用於AST的每個節點。這是我到目前爲止的代碼:我node運行

//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, '}'); 

,然後把輸出通過

dot -Tpng -o graph_name.png dot_file_name.dot

注:

它提供了一個非常基本的圖形 - 只有黑色和白色,沒有格式。

它根本不抓ajax,也可能不是像evalwith那樣的東西,如others have mentioned。另外,它在圖中包含:由其他函數調用的函數(以及因此調用其他函數的函數),獨立調用的函數,已定義但未調用的AND函數。

由於這一切,它可能會錯過相關的事情,或包括那些不相關的事情。儘管這是一個開始,並且似乎完成了我之前的任務,而首先是什麼使我想到了這個問題。

+0

有趣的是,它爲一個簡單的javascript創建了一個調用圖表感謝您的努力!(旁註:最近我開始用Esprima http://開始挖掘該區域esprima.org/和Esprima非常有趣) – beatak 2013-01-10 21:14:11

15

code2flow完全是這樣。充分披露,我開始這個項目

運行

$ code2flow source1.js source2.js -o out.gv 

然後,打開out.gv與graphviz的

編輯:就目前來說,這個項目是沒有維護。我會建議在使用code2flow之前嘗試一個不同的解決方案。

+0

這真棒,如果它可以在jQuery上運行,它也必須能夠處理我的項目,我一定會嘗試。謝謝!!! – beatak 2013-06-07 07:07:54

+1

@scottmrogowski,你的項目對我來說真的很好,對於使用這個解決方案的任何人,我想指出[this page](http://dl9obn.darc.de/programming/python/dottoxml/),它將graphviz轉換爲yEd可以打開Scott,我調整了你的python腳本根據函數名稱命名節點,並且它產生了很好的yEd可讀輸出。 – 2014-01-03 18:15:20

+0

不幸的是,這個項目似乎沒有得到維護。我無法使我的Windows和Linux筆記本電腦上的code2flow工作。 – Achille 2016-03-19 03:29:58

相關問題