Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
node_modules
graphing/temp.png
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ Suggestions or PRs for how to improve this CLI are very welcome 🙇

### Thank you

- [Matteo Abrate](https://observablehq.com/@nitaku/tangled-tree-visualization-ii) for the _tangled tree visualization_
- [Mike Bostock](https://observablehq.com/@d3/arc-diagram) for the _arc diagram_
- [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
- [Mermaid-JS](https://github.com/mermaid-js/mermaid) for a way to create a graph
- [Tutorial](https://convincedcoder.com/2019/01/19/Processing-TypeScript-using-TypeScript/) and code for processing TypeScript (AST)
- [Tutorial](https://developer.okta.com/blog/2019/06/18/command-line-app-with-nodejs) for creating a *CLI*
- [TS-Call-Graph](https://github.com/Deskbot/TS-Call-Graph) for inspiration
33 changes: 33 additions & 0 deletions arc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/**
* Convert the call map to format D3 wants
* @param calledFunctions
*/
export function convertForArc(allFunctions: string[], calledFunctions: Map<string, string[]>) {

const nodes = [];
const links = [];

allFunctions.forEach((func: string) => {
nodes.push({
id: func,
group: 1, // later make this tied to an integer representing the file it came from
})
});

calledFunctions.forEach((childArr, key) => {
childArr.forEach((child) => {
links.push({
source: key,
target: child,
value: 1, // indicates 'strength' of connection -- leave as 1 for now
});
});
});

const all = {
nodes: nodes,
links: links,
};

return all;
}
32 changes: 32 additions & 0 deletions bin/arc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
"use strict";
exports.__esModule = true;
exports.convertForArc = void 0;
/**
* Convert the call map to format D3 wants
* @param calledFunctions
*/
function convertForArc(allFunctions, calledFunctions) {
var nodes = [];
var links = [];
allFunctions.forEach(function (func) {
nodes.push({
id: func,
group: 1
});
});
calledFunctions.forEach(function (childArr, key) {
childArr.forEach(function (child) {
links.push({
source: key,
target: child,
value: 1
});
});
});
var all = {
nodes: nodes,
links: links
};
return all;
}
exports.convertForArc = convertForArc;
75 changes: 75 additions & 0 deletions bin/cascade.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"use strict";
var __spreadArrays = (this && this.__spreadArrays) || function () {
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
for (var r = Array(s), k = 0, i = 0; i < il; i++)
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
r[k] = a[j];
return r;
};
exports.__esModule = true;
exports.convertForCascade = void 0;
// Some globals
var myMap;
var final = [];
var calledAlready = []; // to keep track of what has been called; prevent reculsion!
/**
* Take a parent function and return all children in proper format
* @param parent
*/
function generateFromOneParent(parent) {
var newElements = [];
if (myMap.has(parent)) {
var children = myMap.get(parent);
children.forEach(function (element) {
if (!calledAlready.includes(element)) { // PREVENT RECURSION !
newElements.push({ id: element, parents: [parent] });
}
});
}
return newElements;
}
/**
* Recursive function to generate each subsequent level
* @param parents
* @param stackDepth - maximum depth of calls to trace
*/
function generateNextLevel(parents, stackDepth) {
calledAlready.push.apply(calledAlready, parents);
if (stackDepth === 0) {
return;
}
// Generate the NodeForGraphing[] for this 'level'
var thisLevel = [];
parents.forEach(function (parent) {
thisLevel.push.apply(thisLevel, generateFromOneParent(parent));
});
if (thisLevel.length) {
final.push(__spreadArrays(thisLevel));
}
// start building the next `level`
var nextLevel = [];
parents.forEach(function (parent) {
if (myMap.has(parent)) {
nextLevel.push.apply(nextLevel, myMap.get(parent));
}
});
if (nextLevel.length) {
generateNextLevel(nextLevel, stackDepth - 1);
}
}
/**
* Convert the call map to format D3 wants
* @param calledFunctions
* @param startFunction -- string of the function name we want to use as start of call graph
*/
function convertForCascade(calledFunctions, startFunction) {
myMap = calledFunctions;
final = [];
calledAlready = [];
// 1st case -- handle manually
final.push([{ id: startFunction }]);
// all next cases generate automatically
generateNextLevel([startFunction], 10);
return final;
}
exports.convertForCascade = convertForCascade;
79 changes: 79 additions & 0 deletions bin/convert.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
"use strict";
var __spreadArrays = (this && this.__spreadArrays) || function () {
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
for (var r = Array(s), k = 0, i = 0; i < il; i++)
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
r[k] = a[j];
return r;
};
exports.__esModule = true;
exports.convertForCascade = void 0;
// Some globals
var myMap;
var final = [];
var calledAlready = []; // to keep track of what has been called; prevent reculsion!
/**
* Take a parent function and return all children in proper format
* @param parent
*/
function generateFromOneParent(parent) {
var newElements = [];
if (myMap.has(parent)) {
var children = myMap.get(parent);
children.forEach(function (element) {
if (!calledAlready.includes(element)) { // PREVENT RECURSION !
newElements.push({ id: element, parents: [parent] });
}
});
}
return newElements;
}
/**
* Recursive function to generate each subsequent level
* @param parents
* @param stackDepth - maximum depth of calls to trace
*/
function generateNextLevel(parents, stackDepth) {
calledAlready.push.apply(calledAlready, parents);
if (stackDepth === 0) {
return;
}
// Generate the NodeForGraphing[] for this 'level'
var thisLevel = [];
parents.forEach(function (parent) {
thisLevel.push.apply(thisLevel, generateFromOneParent(parent));
});
if (thisLevel.length) {
final.push(__spreadArrays(thisLevel));
}
// start building the next `level`
var nextLevel = [];
parents.forEach(function (parent) {
if (myMap.has(parent)) {
nextLevel.push.apply(nextLevel, myMap.get(parent));
}
});
if (nextLevel.length) {
generateNextLevel(nextLevel, stackDepth - 1);
}
}
/**
* Convert the call map to format D3 wants
* @param calledFunctions
*/
function convertForCascade(calledFunctions) {
myMap = calledFunctions;
final = [];
calledAlready = [];
// 1st case -- handle manually
final.push([{ id: 'proceed' }]);
// all next cases generate automatically
generateNextLevel(['proceed'], 10);
console.log('======================================');
console.log(final);
console.log('--------------------------------------');
console.log(JSON.stringify(final));
console.log('');
return final;
}
exports.convertForCascade = convertForCascade;
38 changes: 29 additions & 9 deletions bin/extract.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ exports.__esModule = true;
exports.processFiles = void 0;
var ts = require("typescript");
var fs = require('fs');
var _a = require('kleur'), green = _a.green, red = _a.red;
var functionsToIgnore = []; // optionally ['require', 'parseInt', 'exec', 'reject', 'resolve'];
var allFunctions = [];
var calledFunctions = new Map();
Expand Down Expand Up @@ -83,18 +84,29 @@ function processFiles(filenames) {
// then do recursion for each
filenames.forEach(function (filename) {
var rootNodes = [];
var codeAsString = fs.readFileSync(filename).toString();
var sourceFile = ts.createSourceFile(filename, codeAsString, ts.ScriptTarget.Latest);
sourceFile.forEachChild(function (child) {
rootNodes.push(child);
});
rootNodes.forEach(function (node) {
currentFunction = undefined;
extractFunctionCalls(node, sourceFile, 1);
});
var codeAsString;
var skipFile = false;
try {
codeAsString = fs.readFileSync(filename).toString();
}
catch (err) {
console.log('File', green(filename), red('not found!'), ' - skipping');
skipFile = true;
}
if (!skipFile) {
var sourceFile_1 = ts.createSourceFile(filename, codeAsString, ts.ScriptTarget.Latest);
sourceFile_1.forEachChild(function (child) {
rootNodes.push(child);
});
rootNodes.forEach(function (node) {
currentFunction = undefined;
extractFunctionCalls(node, sourceFile_1, 1);
});
}
});
calledFunctions["delete"](undefined);
// Output
console.log('');
console.log('======================================');
console.log(allFunctions);
console.log('--------------------------------------');
Expand All @@ -108,7 +120,15 @@ function processFiles(filenames) {
calledFunctions.set(key, value.filter(function (calledFunc) {
return allFunctions.includes(calledFunc);
}));
if (!calledFunctions.get(key).length) {
calledFunctions["delete"](key);
}
});
console.log(calledFunctions);
var functions = {
all: allFunctions,
called: calledFunctions
};
return functions;
}
exports.processFiles = processFiles;
18 changes: 18 additions & 0 deletions bin/graphviz.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
"use strict";
exports.__esModule = true;
exports.convertForGraphViz = void 0;
var graphviz = require('graphviz');
function convertForGraphViz(functionMap) {
var g = graphviz.digraph("G");
g.set("rankdir", "LR");
functionMap.forEach(function (value, key) {
g.addNode(key);
});
functionMap.forEach(function (childArr, key) {
childArr.forEach(function (child) {
g.addEdge(key, child);
});
});
return g.to_dot();
}
exports.convertForGraphViz = convertForGraphViz;
35 changes: 35 additions & 0 deletions bin/helper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"use strict";
exports.__esModule = true;
exports.showServerRunning = exports.showHelpMessage = void 0;
var _a = require('kleur'), green = _a.green, bold = _a.bold;
/**
* Shown when user runs `tcg` without arguments
*/
function showHelpMessage() {
console.log(green('╭───────────────────────────╮'));
console.log(green('│ │'));
console.log(green('│ ') + bold('Typescript Node Graph') + green(' │'));
console.log(green('│ │'));
console.log(green('╰───────────────────────────╯'));
console.log('Please provide a list of input files and/or folders');
console.log('e.g. `'
+ green('myFile.ts') + '`, `'
+ green('*') + '`, `'
+ green('**/*') + '`, `'
+ green('myFolder/*') + '`');
console.log('or any combination of the above, like `' + green('myFile.ts myFolder/*') + '`');
}
exports.showHelpMessage = showHelpMessage;
/**
* Console log that server is running
* @param filePath
*/
function showServerRunning(filePath) {
// Helpful message
console.log(green('╭───────────────────────────╮'));
console.log(green('│ ') + 'Graph visible @ ' + green(' │'));
console.log(green('│ ') + filePath + green(' │'));
console.log(green('│ ') + 'Ctrl + C to quit ' + green(' │'));
console.log(green('╰───────────────────────────╯'));
}
exports.showServerRunning = showServerRunning;
Loading