2012-04-16 2 views
29

Я видел «https://stackoverflow.com/questions/1385335/how-to-generate-function-call-graphs-for-javascript» и попробовал. Он работает хорошо, если вы хотите получить абстрактное синтаксическое дерево.Как сгенерировать графы для данного javascript?

К сожалению, Closure компилятор только предлагает --print_tree, --print_ast и --print_pass_graph. Ни один из них не полезен для меня.

Я хочу увидеть график, функция которого вызывает другие функции.

+0

+1 отличный вопрос - – miku

+0

Почему вы не используете инструменты dev, встроенные в поддержку профилирования javascript? – Tushar

+0

Похоже, что исходная нить прошла, и теперь ссылка сломана. :-( –

ответ

5

Если вы фильтруете выход closure --print_tree, вы получаете то, что хотите.

Например, возьмем следующий файл:

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

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

Фильтр выход 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 

И вы можете видеть все операторы вызова.

Для этого я написал следующие сценарии.

./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

+0

Есть ли способ получить номер строки каждого вызова функции, который оценивается в скрипте? –

2

https://github.com/mishoo/UglifyJS дает доступ к аст в JavaScript.

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

я, наконец, удалось это с помощью UglifyJS2 и Dot/GraphViz, в какой-то комбинации вышеуказанного ответа и ответов на связанный вопрос.

Недостающая часть для меня - это то, как фильтровать анализируемый АСТ. Оказывается, что 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.

Кроме того, поскольку он стоит, он включает в себя: функции, называемые другими функциями (и, следовательно, функциями, которые вызывают другие функции), функции, которые называются независимыми, и функции, которые определены, но не вызваны.

В результате все это может пропустить то, что имеет отношение к делу, или включать в себя то, что нет.Это, однако, начало, и, похоже, я выполнил то, что было после, и что привело меня к этому вопросу в первую очередь.

+0

Интересно, он делает граф вызовов для простого javascript. Спасибо за ваши усилия (обратите внимание: недавно я начал копать эту область с помощью Esprima http: // esprima.org/ и Esprima настолько интересны.) – beatak

15

code2flow делает именно это. Полное раскрытие, я начал этот проект

Для запуска

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

Затем откройте out.gv с GraphViz

Edit: На данный момент этот проект не сопровождается. Я бы предложил попробовать другое решение, прежде чем использовать code2flow.

+0

Это потрясающе. Если он может работать на jQuery, он также сможет обрабатывать мои проекты. Я обязательно попробую. Спасибо! – beatak

+1

@scottmrogowski, ваш проект работал очень хорошо для меня. Для тех, кто использует это решение, я хотел бы указать [эту страницу] (http://dl9obn.darc.de/programming/python/dottoxml/), которая преобразует graphviz в файлы, которые yEd Скотт, я подправил ваш питон сценарий, чтобы называть узлы на основе имен функций, и он выдавал отличный yEd-читаемый вывод. –

+0

К сожалению, кажется, что проект остался без изменений. Мне не удалось заставить code2flow работать только на моем ноутбуке Windows и Linux. – Achille

Смежные вопросы