├── .gitignore ├── LICENSE ├── README.md ├── package.json ├── public ├── css │ └── styles.css ├── index.html └── js │ ├── callGraph.js │ └── index.js ├── server.js └── src ├── ast.js ├── callGraph.js └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | uploads/ 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Javascript Explorer 2 | 3 | Generates a call graph for a Javascript file based on Abstract Syntax Tree. 4 | 5 | D3's Force Directed Graph is used for the call graph visualization. 6 | 7 | ## How to use? 8 | 9 | `npm install` 10 | 11 | `npm start` 12 | 13 | Open _http://localhost:3000_ on the browser 14 | 15 | Upload a javascript file 16 | 17 | See the Call graph 18 | 19 | ![image](https://cloud.githubusercontent.com/assets/27330002/26558583/2cfe343e-4467-11e7-9cb6-6e3554ef5e17.png) 20 | 21 | 22 | ## Technical Details 23 | 24 | ### Client Side 25 | 26 | - UI is designed with Bootstrap 27 | 28 | - JQuery is used for File uploads 29 | 30 | - D3.js is used for call graph visualizations 31 | 32 | ### Server Side 33 | 34 | - Escodegen is used for generating the Abstract Syntax Tree for a given Javascript 35 | 36 | - ExpressJS is used for serving the app on port 3000 37 | 38 | ## About the Author 39 | 40 | Sudharsan Rajagopalan is a Solution Architect and a full-stack developer with 11 years experience in cloud, container, front-end, back-end and mobile technologies. 41 | 42 | 43 | **Enjoy!** 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-ast-explorer", 3 | "version": "0.0.0", 4 | "private": true, 5 | "license": "see LICENSE file", 6 | "description": "Explore AST using D3.js", 7 | "main": "server.js", 8 | "dependencies": { 9 | "body-parser": "^1.17.1", 10 | "express": "^4.15.2", 11 | "escodegen": "^1.8.1", 12 | "esprima": "^3.1.3", 13 | "multer": "^1.3.0" 14 | }, 15 | "devDependencies": {}, 16 | "scripts": { 17 | "test": "echo \"Error: no test specified\" && exit 1", 18 | "start": "node server.js" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/reactjs/react-tutorial.git" 23 | }, 24 | "author": "Sudharsan Rajagopalan" 25 | } 26 | -------------------------------------------------------------------------------- /public/css/styles.css: -------------------------------------------------------------------------------- 1 | .links line { 2 | stroke: #999; 3 | stroke-opacity: 0.6; 4 | } 5 | 6 | .nodes circle { 7 | stroke: #fff; 8 | stroke-width: 1.5px; 9 | } 10 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Javascript Explorer 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 37 |
38 |
39 | 52 |
53 | 54 |
55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /public/js/callGraph.js: -------------------------------------------------------------------------------- 1 | function callGraph(graph){ 2 | var svg = d3.select("svg"), 3 | width = +svg.attr("width"), 4 | height = +svg.attr("height"); 5 | 6 | svg.selectAll("*").remove(); 7 | 8 | var color = d3.scaleOrdinal(d3.schemeCategory20); 9 | 10 | var simulation = d3.forceSimulation() 11 | .force("link", d3.forceLink().id(function(d) { return d.id; }).distance(120).strength(1)) 12 | .force("charge", d3.forceManyBody()) 13 | .force("center", d3.forceCenter(width / 2, height / 2)) 14 | 15 | var link = svg.append("g") 16 | .attr("class", "links") 17 | .selectAll("line") 18 | .data(graph.links) 19 | .enter().append("line") 20 | .attr("stroke-width", 1) 21 | .style("marker-end", "url(#suit)") //Added 22 | 23 | var node = svg.selectAll(".node") 24 | .data(graph.nodes) 25 | .enter().append("g") 26 | .attr("class","node") 27 | .call(d3.drag() 28 | .on("start", dragstarted) 29 | .on("drag", dragged) 30 | .on("end", dragended)) 31 | // .on('click', connectedNodes); //Added code 32 | .on("mouseover", fade(.1)).on("mouseout", fade(1)) 33 | 34 | node.append("circle") 35 | .attr("r", 5) 36 | .style("fill", function (d) { 37 | return color(d.group); 38 | }) 39 | 40 | node.append("text") 41 | .attr("dx", 10) 42 | .attr("dy", ".35em") 43 | .text(function(d) { return d.id }); 44 | 45 | node.append("title") 46 | .text(function(d) { return d.code; }); 47 | 48 | simulation 49 | .nodes(graph.nodes) 50 | .on("tick", ticked); 51 | 52 | simulation.force("link") 53 | .links(graph.links); 54 | 55 | function ticked() { 56 | link 57 | .attr("x1", function(d) { return d.source.x; }) 58 | .attr("y1", function(d) { return d.source.y; }) 59 | .attr("x2", function(d) { return d.target.x; }) 60 | .attr("y2", function(d) { return d.target.y; }); 61 | 62 | 63 | d3.selectAll("circle").attr("cx", function (d) { 64 | return d.x; 65 | }) 66 | .attr("cy", function (d) { 67 | return d.y; 68 | }); 69 | 70 | d3.selectAll("text").attr("x", function (d) { 71 | return d.x; 72 | }) 73 | .attr("y", function (d) { 74 | return d.y; 75 | }); 76 | 77 | } 78 | 79 | 80 | function dragstarted(d) { 81 | if (!d3.event.active) simulation.alphaTarget(0.3).restart(); 82 | d.fx = d.x; 83 | d.fy = d.y; 84 | } 85 | 86 | function dragged(d) { 87 | d.fx = d3.event.x; 88 | d.fy = d3.event.y; 89 | } 90 | 91 | function dragended(d) { 92 | if (!d3.event.active) simulation.alphaTarget(0); 93 | d.fx = null; 94 | d.fy = null; 95 | } 96 | 97 | //---Insert------- 98 | svg.append("defs").selectAll("marker") 99 | .data(["suit"]) 100 | .enter().append("marker") 101 | .attr("id", function(d) { return d; }) 102 | .attr("viewBox", "0 -5 10 10") 103 | .attr("refX", 25) 104 | .attr("refY", 0) 105 | .attr("markerWidth", 6) 106 | .attr("markerHeight", 6) 107 | .attr("orient", "auto") 108 | .append("path") 109 | .attr("d", "M0,-5L10,0L0,5 L10,0 L0, -5") 110 | .style("stroke", "#4679BD") 111 | .style("opacity", "0.6") 112 | .on("mouseover", fade(0.6)).on("mouseleave",fade(0.1)) 113 | 114 | 115 | //Toggle stores whether the highlighting is on 116 | var toggle = 0; 117 | //Create an array logging what is connected to what 118 | var linkedByIndex = {}; 119 | for (i = 0; i < graph.nodes.length; i++) { 120 | linkedByIndex[i + "," + i] = 1; 121 | }; 122 | graph.links.forEach(function (d) { 123 | linkedByIndex[d.source.index + "," + d.target.index] = 1; 124 | }); 125 | 126 | function isConnected(a, b) { 127 | return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index] || a.index == b.index; 128 | } 129 | 130 | 131 | function fade(opacity) { 132 | return function(d) { 133 | node.style("stroke-opacity", function(o) { 134 | thisOpacity = isConnected(d, o) ? 1 : opacity; 135 | this.setAttribute('fill-opacity', thisOpacity); 136 | return thisOpacity; 137 | }); 138 | 139 | link.style("stroke-opacity", opacity).style("stroke-opacity", function(o) { 140 | return o.source === d || o.target === d ? 1 : opacity; 141 | }); 142 | link.style("stroke", "#999").style("stroke", function(o) { 143 | return o.source === d || o.target === d ? "red" : "#999"; 144 | }); 145 | }; 146 | } 147 | 148 | 149 | } 150 | 151 | -------------------------------------------------------------------------------- /public/js/index.js: -------------------------------------------------------------------------------- 1 | $(document).on('click', '.browse', function(){ 2 | var file = $(this).parent().parent().parent().find('.file'); 3 | file.trigger('click'); 4 | }); 5 | $(document).on('change', '.file', function(){ 6 | var filename=$(this).val().replace(/C:\\fakepath\\/i, ''); 7 | $(this).parent().find('.form-control').val(filename); 8 | if (filename){ 9 | $('#uploadFile').submit(); 10 | } 11 | }); 12 | 13 | $(document).ready(function(){ 14 | 15 | $('form').submit(function(e){ 16 | e.stopPropagation(); 17 | e.preventDefault(); 18 | var fd = new FormData(); 19 | fd.append('file', $('#file')[0].files[0]); 20 | $.ajax({ 21 | url: '/upload', 22 | data: fd, 23 | processData: false, 24 | contentType: false, 25 | type: 'POST', 26 | success: function(data){ 27 | callGraph(data); 28 | } 29 | }); 30 | }); 31 | 32 | }); 33 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | var fs = require("fs"); 2 | var path = require("path"); 3 | var express = require("express"); 4 | var bodyParser = require("body-parser"); 5 | var app = express(); 6 | var esprima = require("esprima"); 7 | var callGraph = require("./src/callGraph"); 8 | var multer = require("multer"); 9 | var upload = multer({ dest: "uploads/" }); 10 | var handler = upload.single("file"); 11 | 12 | app.set("port", process.env.PORT || 3000); 13 | 14 | app.use("/", express.static(path.join(__dirname, "public"))); 15 | app.use(bodyParser.json()); 16 | app.use(bodyParser.urlencoded({ extended: true })); 17 | 18 | // Additional middleware which will set headers that we need on each request. 19 | app.use(function(req, res, next) { 20 | // Set permissive CORS header - this allows this server to be used only as 21 | // an API server in conjunction with something like webpack-dev-server. 22 | res.setHeader("Access-Control-Allow-Origin", "*"); 23 | 24 | // Disable caching so we'll always get the latest comments. 25 | res.setHeader("Cache-Control", "no-cache"); 26 | next(); 27 | }); 28 | 29 | app.get("/", function(req, res) { 30 | res.render("index", { title: "Javascript Explorer" }); 31 | }); 32 | 33 | app.post("/upload", handler, function(req, res, next) { 34 | callGraph(req.file.path, function(response) { 35 | res.json(response); 36 | }); 37 | }); 38 | 39 | app.listen(app.get("port"), function() { 40 | console.log("Server started: http://localhost:" + app.get("port") + "/"); 41 | }); 42 | -------------------------------------------------------------------------------- /src/ast.js: -------------------------------------------------------------------------------- 1 | var es = require("esprima"); 2 | var fs = require("fs"); 3 | var graphList = []; 4 | 5 | const ast = (file, cb) => { 6 | try { 7 | fs.readFile(file, "utf-8", function(err, data) { 8 | try { 9 | const ast = es.parse(data); 10 | 11 | var graph = traverse(ast, "root", []); 12 | 13 | cb(graph); 14 | } catch (e) { 15 | cb(null, e); 16 | } 17 | }); 18 | } catch (e) { 19 | cb(null, e); 20 | } 21 | }; 22 | 23 | const func = (node, parent) => { 24 | var args = ""; 25 | var callee = ""; 26 | var signature = ""; 27 | var obj = {}; 28 | 29 | if (node.params !== undefined && node.params) { 30 | signature = node.params; 31 | } 32 | 33 | if (node.callee !== undefined && node.callee.name) { 34 | callee = node.callee.name; 35 | args = node.arguments; 36 | } 37 | if ( 38 | node.callee !== undefined && 39 | node.callee.object !== undefined && 40 | node.callee.object.name 41 | ) { 42 | callee = node.callee.object.name + "." + node.callee.property.name; 43 | args = node.arguments; 44 | } 45 | 46 | obj["parent"] = parent; 47 | obj["node"] = node; 48 | obj["callee"] = callee; 49 | obj["args"] = args; 50 | obj["signature"] = signature; 51 | obj["type"] = node.type; 52 | return obj; 53 | // console.log(parent,node.type, callee, args, signature); 54 | //console.log(codegen.generate(node)); 55 | }; 56 | 57 | const traverse = (ast, parent, gl) => { 58 | var graph = {}; 59 | graph = Object.assign(graph, func(ast, parent)); 60 | gl.push(graph); 61 | 62 | // See if it has a new parent. 63 | if (ast.id !== null && ast.id !== undefined && ast.id.name) { 64 | parent = ast.id.name; 65 | } 66 | 67 | for (var key in ast) { 68 | if (ast.hasOwnProperty(key)) { 69 | var child = ast[key]; 70 | if (typeof child === "object" && child !== null) { 71 | if (Array.isArray(child)) { 72 | child.forEach(d => { 73 | traverse(d, parent, gl); 74 | }); 75 | } else { 76 | traverse(child, parent, gl); 77 | } 78 | } 79 | } 80 | } 81 | return gl; 82 | }; 83 | 84 | module.exports = ast; 85 | -------------------------------------------------------------------------------- /src/callGraph.js: -------------------------------------------------------------------------------- 1 | var ast = require("./ast"); 2 | var codegen = require("escodegen"); 3 | var errorTree = { 4 | nodes: [ 5 | { id: "Error", group: 1, code: "" }, 6 | { id: "Processing", group: 2, code: "" } 7 | ], 8 | links: [{ source: "Error", target: "Processing" }] 9 | }; 10 | 11 | function callGraph(file, callback) { 12 | const cb = (data, err) => { 13 | if (err) { 14 | console.log(err); 15 | callback(errorTree); 16 | return; 17 | } else { 18 | callback(formGraph(data)); 19 | } 20 | }; 21 | try { 22 | var graph = ast(file, cb); 23 | } catch (err) { 24 | callback(errorTree); 25 | } 26 | } 27 | 28 | function formGraph(data) { 29 | var parent = {}, 30 | children = [], 31 | graph = {}; 32 | 33 | data.forEach(d => { 34 | if ( 35 | d.type === "FunctionExpression" || 36 | d.type === "ArrowFunctionExpression" || 37 | d.type === "CallExpression" || 38 | d.type === "MemberExpression" 39 | ) { 40 | children = []; 41 | 42 | if (!parent[d.parent]) { 43 | parent[d.parent] = {}; 44 | parent[d.parent]["children"] = []; 45 | parent[d.parent]["code"] = ""; 46 | } 47 | 48 | if (parent[d.parent]["children"]) { 49 | children = parent[d.parent]["children"]; 50 | } 51 | d.callee ? children.push(d.callee) : false; 52 | parent[d.parent]["children"] = children; 53 | parent[d.parent]["code"] = codegen.generate(d.node); 54 | } 55 | }); 56 | 57 | graph["nodes"] = []; 58 | graph["links"] = []; 59 | let i = 0; 60 | var group = {}; 61 | for (var p of Object.keys(parent)) { 62 | graph["nodes"].push({ id: p, group: ++i, code: parent[p].code }); 63 | group[p] = i; 64 | parent[p].children.forEach(c => { 65 | graph["links"].push({ source: p, target: c }); 66 | }); 67 | } 68 | graph["links"].forEach(l => { 69 | if (!group[l.target]) { 70 | graph["nodes"].push({ id: l.target, group: group[l.source] }); 71 | group[l.target] = group[l.source]; 72 | } 73 | }); 74 | return graph; 75 | } 76 | 77 | module.exports = callGraph; 78 | -------------------------------------------------------------------------------- /src/test.js: -------------------------------------------------------------------------------- 1 | const b = () => { 2 | console.log("test"); 3 | c(); 4 | }; 5 | 6 | const a = next => { 7 | b(); 8 | c(); 9 | }; 10 | 11 | function c() { 12 | var test = 1; 13 | test++; 14 | } 15 | a(1); 16 | --------------------------------------------------------------------------------