Skip to content

Commit 06c660c

Browse files
committed
arc diagram
1 parent e409bfe commit 06c660c

28 files changed

+554
-126
lines changed

README.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@ 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 graphing code I could use
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
3436
- [Tutorial](https://convincedcoder.com/2019/01/19/Processing-TypeScript-using-TypeScript/) and code for processing TypeScript (AST)
3537
- [Tutorial](https://developer.okta.com/blog/2019/06/18/command-line-app-with-nodejs) for creating a *CLI*
36-
- [GraphViz](https://graphviz.org/) and [node-graphviz](https://github.com/glejeune/node-graphviz)
3738
- [TS-Call-Graph](https://github.com/Deskbot/TS-Call-Graph) for inspiration

arc.ts

+33
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

+32
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

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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+
return final;
73+
}
74+
exports.convertForCascade = convertForCascade;

bin/convert.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ var __spreadArrays = (this && this.__spreadArrays) || function () {
77
return r;
88
};
99
exports.__esModule = true;
10-
exports.convertForD3 = void 0;
10+
exports.convertForCascade = void 0;
1111
// Some globals
1212
var myMap;
1313
var final = [];
@@ -61,7 +61,7 @@ function generateNextLevel(parents, stackDepth) {
6161
* Convert the call map to format D3 wants
6262
* @param calledFunctions
6363
*/
64-
function convertForD3(calledFunctions) {
64+
function convertForCascade(calledFunctions) {
6565
myMap = calledFunctions;
6666
final = [];
6767
calledAlready = [];
@@ -76,4 +76,4 @@ function convertForD3(calledFunctions) {
7676
console.log('');
7777
return final;
7878
}
79-
exports.convertForD3 = convertForD3;
79+
exports.convertForCascade = convertForCascade;

bin/extract.js

+25-10
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,15 +84,25 @@ 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
@@ -114,6 +125,10 @@ function processFiles(filenames) {
114125
}
115126
});
116127
console.log(calledFunctions);
117-
return calledFunctions;
128+
var functions = {
129+
all: allFunctions,
130+
called: calledFunctions
131+
};
132+
return functions;
118133
}
119134
exports.processFiles = processFiles;

bin/graphviz.js

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
"use strict";
22
exports.__esModule = true;
3-
exports.generateGraphViz = void 0;
3+
exports.convertForGraphViz = void 0;
44
var graphviz = require('graphviz');
5-
function generateGraphViz(functionMap) {
5+
function convertForGraphViz(functionMap) {
66
var g = graphviz.digraph("G");
77
g.set("rankdir", "LR");
88
functionMap.forEach(function (value, key) {
@@ -13,7 +13,6 @@ function generateGraphViz(functionMap) {
1313
g.addEdge(key, child);
1414
});
1515
});
16-
// console.log(g.to_dot());
1716
return g.to_dot();
1817
}
19-
exports.generateGraphViz = generateGraphViz;
18+
exports.convertForGraphViz = convertForGraphViz;

bin/helper.js

+35
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;

bin/index.js

+14-36
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22
"use strict";
33
exports.__esModule = true;
44
var open = require("open");
5-
var _a = require('kleur'), green = _a.green, bold = _a.bold;
65
var extract_1 = require("./extract");
7-
var convert_1 = require("./convert");
6+
var arc_1 = require("./arc");
7+
var cascade_1 = require("./cascade");
88
var graphviz_1 = require("./graphviz");
9+
var helper_1 = require("./helper");
910
var myArgs = process.argv.slice(2);
1011
var onlyTypescript = myArgs.filter(function (file) { return file.endsWith('ts'); });
1112
var withoutNodeModules = onlyTypescript.filter(function (file) { return !file.includes('node_modules'); });
@@ -26,56 +27,33 @@ if (withoutNodeModules.length) {
2627
});
2728
}
2829
else {
29-
showHelpMessage();
30-
}
31-
/**
32-
* Shown when user runs `tcg` without arguments
33-
*/
34-
function showHelpMessage() {
35-
console.log(green('╭───────────────────────────╮'));
36-
console.log(green('│ │'));
37-
console.log(green('│ ') + bold('Typescript Node Graph') + green(' │'));
38-
console.log(green('│ │'));
39-
console.log(green('╰───────────────────────────╯'));
40-
console.log('Please provide a list of input files and/or folders');
41-
console.log('e.g. `'
42-
+ green('myFile.ts') + '`, `'
43-
+ green('*') + '`, `'
44-
+ green('**/*') + '`, `'
45-
+ green('myFolder/*') + '`');
46-
console.log('or any combination of the above, like `' + green('myFile.ts myFolder/*') + '`');
30+
helper_1.showHelpMessage();
4731
}
4832
/**
4933
* If user confirms the files they want to analyze, proceed
5034
*/
5135
function proceed() {
52-
var functionMap = extract_1.processFiles(withoutNodeModules);
53-
startServer(functionMap);
36+
var functions = extract_1.processFiles(withoutNodeModules);
37+
startServer(functions.all, functions.called);
5438
}
5539
/**
5640
* Start Express server with static files and API endpoints
5741
* @param functionMap
5842
*/
59-
function startServer(functionMap) {
43+
function startServer(allFunctions, functionMap) {
6044
var express = require('express');
6145
var app = express();
6246
var path = require('path');
6347
app.use(express.static(path.join(__dirname, '..', 'graphing')));
64-
app.use('/graphviz', express.static(path.join(__dirname, '..', 'graphing/graphviz')));
48+
app.use('/arc', express.static(path.join(__dirname, '..', 'graphing/arc')));
6549
app.use('/cascade', express.static(path.join(__dirname, '..', 'graphing/cascade')));
66-
app.get('/hi', function (req, res) {
67-
res.json(convert_1.convertForD3(functionMap));
68-
});
69-
app.get('/dot', function (req, res) {
70-
res.json(graphviz_1.generateGraphViz(functionMap));
71-
});
50+
app.use('/graphviz', express.static(path.join(__dirname, '..', 'graphing/graphviz')));
51+
app.use('/vendor', express.static(path.join(__dirname, '..', 'graphing/vendor')));
52+
app.get('/arcAPI', function (req, res) { res.json(arc_1.convertForArc(allFunctions, functionMap)); });
53+
app.get('/cascadeAPI', function (req, res) { res.json(cascade_1.convertForCascade(functionMap)); });
54+
app.get('/graphvizAPI', function (req, res) { res.json(graphviz_1.convertForGraphViz(functionMap)); });
7255
app.listen(3000);
7356
var filePath = 'http://localhost:3000';
74-
// Helpful message
75-
console.log(green('╭───────────────────────────╮'));
76-
console.log(green('│ ') + 'Graph visible @ ' + green(' │'));
77-
console.log(green('│ ') + filePath + green(' │'));
78-
console.log(green('│ ') + 'Ctrl + C to quit ' + green(' │'));
79-
console.log(green('╰───────────────────────────╯'));
57+
helper_1.showServerRunning(filePath);
8058
open(filePath);
8159
}

convert.ts renamed to cascade.ts

+1-7
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ function generateNextLevel(parents: string[], stackDepth: number): void {
7070
* Convert the call map to format D3 wants
7171
* @param calledFunctions
7272
*/
73-
export function convertForD3(calledFunctions: Map<string, string[]>) {
73+
export function convertForCascade(calledFunctions: Map<string, string[]>) {
7474
myMap = calledFunctions;
7575
final = [];
7676
calledAlready = [];
@@ -80,11 +80,5 @@ export function convertForD3(calledFunctions: Map<string, string[]>) {
8080
// all next cases generate automatically
8181
generateNextLevel(['proceed'], 10);
8282

83-
console.log('======================================');
84-
console.log(final);
85-
console.log('--------------------------------------');
86-
console.log(JSON.stringify(final));
87-
console.log('');
88-
8983
return final;
9084
}

0 commit comments

Comments
 (0)