Skip to content

Commit c134cca

Browse files
authored
Merge pull request #1 from whyboris/convert
Show a call graph originating from a specific function
2 parents bf931b5 + 51cacaa commit c134cca

34 files changed

+4628
-58
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
node_modules
2+
graphing/temp.png

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ Suggestions or PRs for how to improve this CLI are very welcome 🙇
3030

3131
### Thank you
3232

33+
- [Matteo Abrate](https://observablehq.com/@nitaku/tangled-tree-visualization-ii) for the _tangled tree visualization_
34+
- [Mike Bostock](https://observablehq.com/@d3/arc-diagram) for the _arc diagram_
35+
- [GraphViz](https://graphviz.org/), [node-graphviz](https://github.com/glejeune/node-graphviz), and [d3-graphviz](https://github.com/magjac/d3-graphviz) for the simple graph
36+
- [Mermaid-JS](https://github.com/mermaid-js/mermaid) for a way to create a graph
3337
- [Tutorial](https://convincedcoder.com/2019/01/19/Processing-TypeScript-using-TypeScript/) and code for processing TypeScript (AST)
3438
- [Tutorial](https://developer.okta.com/blog/2019/06/18/command-line-app-with-nodejs) for creating a *CLI*
3539
- [TS-Call-Graph](https://github.com/Deskbot/TS-Call-Graph) for inspiration

arc.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/**
2+
* Convert the call map to format D3 wants
3+
* @param calledFunctions
4+
*/
5+
export function convertForArc(allFunctions: string[], calledFunctions: Map<string, string[]>) {
6+
7+
const nodes = [];
8+
const links = [];
9+
10+
allFunctions.forEach((func: string) => {
11+
nodes.push({
12+
id: func,
13+
group: 1, // later make this tied to an integer representing the file it came from
14+
})
15+
});
16+
17+
calledFunctions.forEach((childArr, key) => {
18+
childArr.forEach((child) => {
19+
links.push({
20+
source: key,
21+
target: child,
22+
value: 1, // indicates 'strength' of connection -- leave as 1 for now
23+
});
24+
});
25+
});
26+
27+
const all = {
28+
nodes: nodes,
29+
links: links,
30+
};
31+
32+
return all;
33+
}

bin/arc.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"use strict";
2+
exports.__esModule = true;
3+
exports.convertForArc = void 0;
4+
/**
5+
* Convert the call map to format D3 wants
6+
* @param calledFunctions
7+
*/
8+
function convertForArc(allFunctions, calledFunctions) {
9+
var nodes = [];
10+
var links = [];
11+
allFunctions.forEach(function (func) {
12+
nodes.push({
13+
id: func,
14+
group: 1
15+
});
16+
});
17+
calledFunctions.forEach(function (childArr, key) {
18+
childArr.forEach(function (child) {
19+
links.push({
20+
source: key,
21+
target: child,
22+
value: 1
23+
});
24+
});
25+
});
26+
var all = {
27+
nodes: nodes,
28+
links: links
29+
};
30+
return all;
31+
}
32+
exports.convertForArc = convertForArc;

bin/cascade.js

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
"use strict";
2+
var __spreadArrays = (this && this.__spreadArrays) || function () {
3+
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
4+
for (var r = Array(s), k = 0, i = 0; i < il; i++)
5+
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
6+
r[k] = a[j];
7+
return r;
8+
};
9+
exports.__esModule = true;
10+
exports.convertForCascade = void 0;
11+
// Some globals
12+
var myMap;
13+
var final = [];
14+
var calledAlready = []; // to keep track of what has been called; prevent reculsion!
15+
/**
16+
* Take a parent function and return all children in proper format
17+
* @param parent
18+
*/
19+
function generateFromOneParent(parent) {
20+
var newElements = [];
21+
if (myMap.has(parent)) {
22+
var children = myMap.get(parent);
23+
children.forEach(function (element) {
24+
if (!calledAlready.includes(element)) { // PREVENT RECURSION !
25+
newElements.push({ id: element, parents: [parent] });
26+
}
27+
});
28+
}
29+
return newElements;
30+
}
31+
/**
32+
* Recursive function to generate each subsequent level
33+
* @param parents
34+
* @param stackDepth - maximum depth of calls to trace
35+
*/
36+
function generateNextLevel(parents, stackDepth) {
37+
calledAlready.push.apply(calledAlready, parents);
38+
if (stackDepth === 0) {
39+
return;
40+
}
41+
// Generate the NodeForGraphing[] for this 'level'
42+
var thisLevel = [];
43+
parents.forEach(function (parent) {
44+
thisLevel.push.apply(thisLevel, generateFromOneParent(parent));
45+
});
46+
if (thisLevel.length) {
47+
final.push(__spreadArrays(thisLevel));
48+
}
49+
// start building the next `level`
50+
var nextLevel = [];
51+
parents.forEach(function (parent) {
52+
if (myMap.has(parent)) {
53+
nextLevel.push.apply(nextLevel, myMap.get(parent));
54+
}
55+
});
56+
if (nextLevel.length) {
57+
generateNextLevel(nextLevel, stackDepth - 1);
58+
}
59+
}
60+
/**
61+
* Convert the call map to format D3 wants
62+
* @param calledFunctions
63+
* @param startFunction -- string of the function name we want to use as start of call graph
64+
*/
65+
function convertForCascade(calledFunctions, startFunction) {
66+
myMap = calledFunctions;
67+
final = [];
68+
calledAlready = [];
69+
// 1st case -- handle manually
70+
final.push([{ id: startFunction }]);
71+
// all next cases generate automatically
72+
generateNextLevel([startFunction], 10);
73+
return final;
74+
}
75+
exports.convertForCascade = convertForCascade;

bin/convert.js

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
"use strict";
2+
var __spreadArrays = (this && this.__spreadArrays) || function () {
3+
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
4+
for (var r = Array(s), k = 0, i = 0; i < il; i++)
5+
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
6+
r[k] = a[j];
7+
return r;
8+
};
9+
exports.__esModule = true;
10+
exports.convertForCascade = void 0;
11+
// Some globals
12+
var myMap;
13+
var final = [];
14+
var calledAlready = []; // to keep track of what has been called; prevent reculsion!
15+
/**
16+
* Take a parent function and return all children in proper format
17+
* @param parent
18+
*/
19+
function generateFromOneParent(parent) {
20+
var newElements = [];
21+
if (myMap.has(parent)) {
22+
var children = myMap.get(parent);
23+
children.forEach(function (element) {
24+
if (!calledAlready.includes(element)) { // PREVENT RECURSION !
25+
newElements.push({ id: element, parents: [parent] });
26+
}
27+
});
28+
}
29+
return newElements;
30+
}
31+
/**
32+
* Recursive function to generate each subsequent level
33+
* @param parents
34+
* @param stackDepth - maximum depth of calls to trace
35+
*/
36+
function generateNextLevel(parents, stackDepth) {
37+
calledAlready.push.apply(calledAlready, parents);
38+
if (stackDepth === 0) {
39+
return;
40+
}
41+
// Generate the NodeForGraphing[] for this 'level'
42+
var thisLevel = [];
43+
parents.forEach(function (parent) {
44+
thisLevel.push.apply(thisLevel, generateFromOneParent(parent));
45+
});
46+
if (thisLevel.length) {
47+
final.push(__spreadArrays(thisLevel));
48+
}
49+
// start building the next `level`
50+
var nextLevel = [];
51+
parents.forEach(function (parent) {
52+
if (myMap.has(parent)) {
53+
nextLevel.push.apply(nextLevel, myMap.get(parent));
54+
}
55+
});
56+
if (nextLevel.length) {
57+
generateNextLevel(nextLevel, stackDepth - 1);
58+
}
59+
}
60+
/**
61+
* Convert the call map to format D3 wants
62+
* @param calledFunctions
63+
*/
64+
function convertForCascade(calledFunctions) {
65+
myMap = calledFunctions;
66+
final = [];
67+
calledAlready = [];
68+
// 1st case -- handle manually
69+
final.push([{ id: 'proceed' }]);
70+
// all next cases generate automatically
71+
generateNextLevel(['proceed'], 10);
72+
console.log('======================================');
73+
console.log(final);
74+
console.log('--------------------------------------');
75+
console.log(JSON.stringify(final));
76+
console.log('');
77+
return final;
78+
}
79+
exports.convertForCascade = convertForCascade;

bin/extract.js

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ exports.__esModule = true;
33
exports.processFiles = void 0;
44
var ts = require("typescript");
55
var fs = require('fs');
6+
var _a = require('kleur'), green = _a.green, red = _a.red;
67
var functionsToIgnore = []; // optionally ['require', 'parseInt', 'exec', 'reject', 'resolve'];
78
var allFunctions = [];
89
var calledFunctions = new Map();
@@ -83,18 +84,29 @@ function processFiles(filenames) {
8384
// then do recursion for each
8485
filenames.forEach(function (filename) {
8586
var rootNodes = [];
86-
var codeAsString = fs.readFileSync(filename).toString();
87-
var sourceFile = ts.createSourceFile(filename, codeAsString, ts.ScriptTarget.Latest);
88-
sourceFile.forEachChild(function (child) {
89-
rootNodes.push(child);
90-
});
91-
rootNodes.forEach(function (node) {
92-
currentFunction = undefined;
93-
extractFunctionCalls(node, sourceFile, 1);
94-
});
87+
var codeAsString;
88+
var skipFile = false;
89+
try {
90+
codeAsString = fs.readFileSync(filename).toString();
91+
}
92+
catch (err) {
93+
console.log('File', green(filename), red('not found!'), ' - skipping');
94+
skipFile = true;
95+
}
96+
if (!skipFile) {
97+
var sourceFile_1 = ts.createSourceFile(filename, codeAsString, ts.ScriptTarget.Latest);
98+
sourceFile_1.forEachChild(function (child) {
99+
rootNodes.push(child);
100+
});
101+
rootNodes.forEach(function (node) {
102+
currentFunction = undefined;
103+
extractFunctionCalls(node, sourceFile_1, 1);
104+
});
105+
}
95106
});
96107
calledFunctions["delete"](undefined);
97108
// Output
109+
console.log('');
98110
console.log('======================================');
99111
console.log(allFunctions);
100112
console.log('--------------------------------------');
@@ -108,7 +120,15 @@ function processFiles(filenames) {
108120
calledFunctions.set(key, value.filter(function (calledFunc) {
109121
return allFunctions.includes(calledFunc);
110122
}));
123+
if (!calledFunctions.get(key).length) {
124+
calledFunctions["delete"](key);
125+
}
111126
});
112127
console.log(calledFunctions);
128+
var functions = {
129+
all: allFunctions,
130+
called: calledFunctions
131+
};
132+
return functions;
113133
}
114134
exports.processFiles = processFiles;

bin/graphviz.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
"use strict";
2+
exports.__esModule = true;
3+
exports.convertForGraphViz = void 0;
4+
var graphviz = require('graphviz');
5+
function convertForGraphViz(functionMap) {
6+
var g = graphviz.digraph("G");
7+
g.set("rankdir", "LR");
8+
functionMap.forEach(function (value, key) {
9+
g.addNode(key);
10+
});
11+
functionMap.forEach(function (childArr, key) {
12+
childArr.forEach(function (child) {
13+
g.addEdge(key, child);
14+
});
15+
});
16+
return g.to_dot();
17+
}
18+
exports.convertForGraphViz = convertForGraphViz;

bin/helper.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
"use strict";
2+
exports.__esModule = true;
3+
exports.showServerRunning = exports.showHelpMessage = void 0;
4+
var _a = require('kleur'), green = _a.green, bold = _a.bold;
5+
/**
6+
* Shown when user runs `tcg` without arguments
7+
*/
8+
function showHelpMessage() {
9+
console.log(green('╭───────────────────────────╮'));
10+
console.log(green('│ │'));
11+
console.log(green('│ ') + bold('Typescript Node Graph') + green(' │'));
12+
console.log(green('│ │'));
13+
console.log(green('╰───────────────────────────╯'));
14+
console.log('Please provide a list of input files and/or folders');
15+
console.log('e.g. `'
16+
+ green('myFile.ts') + '`, `'
17+
+ green('*') + '`, `'
18+
+ green('**/*') + '`, `'
19+
+ green('myFolder/*') + '`');
20+
console.log('or any combination of the above, like `' + green('myFile.ts myFolder/*') + '`');
21+
}
22+
exports.showHelpMessage = showHelpMessage;
23+
/**
24+
* Console log that server is running
25+
* @param filePath
26+
*/
27+
function showServerRunning(filePath) {
28+
// Helpful message
29+
console.log(green('╭───────────────────────────╮'));
30+
console.log(green('│ ') + 'Graph visible @ ' + green(' │'));
31+
console.log(green('│ ') + filePath + green(' │'));
32+
console.log(green('│ ') + 'Ctrl + C to quit ' + green(' │'));
33+
console.log(green('╰───────────────────────────╯'));
34+
}
35+
exports.showServerRunning = showServerRunning;

0 commit comments

Comments
 (0)