Comment générer des graphiques d'appel pour un javascript donné?

J'ai vu " https://stackoverflow.com/questions/1385335/how-to-generate-function-call-graphs-for-javascript ", et l'ai essayé. Cela fonctionne bien, si vous voulez obtenir un arbre de syntaxe abstraite.

Le compilateur de fermeture Malheureusement semble offrir --print_tree , --print_ast et --print_pass_graph . Aucun d'entre eux ne me sont utiles.

Je veux voir un graphique dont la fonction appelle les autres fonctions.

Code2flow fait exactement cela. La divulgation complète, j'ai commencé ce projet

Courir

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

Ensuite, ouvrez.gv avec graphviz

Edit : Pour l'instant, ce projet n'est pas encore utilisé. Je suggérerais d'essayer une solution différente avant d'utiliser code2flow.

Si vous filtrez la sortie de closure --print_tree vous obtenez ce que vous voulez.

Par exemple, prenez le fichier suivant:

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

Filtrer la sortie de 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 

Et vous pouvez voir toutes les déclarations d'appel.

J'ai écrit les scripts suivants pour le faire.

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

J'ai finalement réussi à utiliser UglifyJS2 et Dot / GraphViz , dans une sorte de combinaison de la réponse ci-dessus et les réponses à la question liée.

La partie manquante, pour moi, était comment filtrer l'AST analysé. Il s'avère que UglifyJS possède l'objet TreeWalker, qui applique essentiellement une fonction à chaque nœud de l'AST. C'est le code que j'ai jusqu'ici:

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

Je l'exécute avec un nœud , puis je lance la sortie

dot -Tpng -o graph_name.png dot_file_name.dot

Remarques:

Il donne un joli graphique de base – uniquement en noir et blanc et sans formatage.

Il n'attrape pas l'ajax, et il est probable que ce ne sont pas des choses comme eval ou with un ou l'autre, comme d' autres l'ont mentionné .

En outre, tel qu'il se trouve, il comprend dans le graphique: fonctions appelées par d'autres fonctions (et par conséquent fonctions qui appellent d'autres fonctions), fonctions appelées indépendamment, fonctions AND définies mais non appelées.

En raison de tout cela, il peut manquer des éléments pertinents ou inclure des éléments qui ne le sont pas. C'est un début, et semble accomplir ce que je voulais et ce qui m'a conduit à cette question en premier lieu.

https://github.com/mishoo/UglifyJS donne accès à un ast en 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