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