Comparing version 1.0.3 to 1.0.4
210
index.js
@@ -11,117 +11,135 @@ // TODO take scope in consideration when parsing calls | ||
const UglifyJS = require('uglify-js') | ||
const babel = require('babel-core') | ||
const execSync = require('child_process').execSync; | ||
const fs = require('fs'); | ||
const path = require('path') | ||
const execSync = require('child_process').execSync; | ||
const promisify = require('es6-promisify') | ||
const UglifyJS = require('uglify-js') | ||
const queue = require('queue') | ||
const localize = (currentFile, fn) => `[${currentFile}]${fn}` | ||
const fsReadFile = promisify(fs.readFile) | ||
const readFile = file => fsReadFile(file, 'utf-8') | ||
const inputPath = path.parse(input) | ||
let files = [inputPath.base] | ||
const calls = [] | ||
const definedFunctions = [] | ||
const rootDir = path.join('./', inputPath.dir) | ||
const q = queue() | ||
const processedFiles = [] | ||
while (files.length > 0) { | ||
const currentFile = files[0].replace(/\.js$/, '') | ||
files.shift() | ||
let totalRunning = 0 | ||
function processFile(requiredFile, cb) { | ||
if (processedFiles.indexOf(requiredFile) > -1) return cb() | ||
processedFiles.push(requiredFile) | ||
totalRunning++ | ||
const currentFile = requiredFile.replace(/\.js$/, '') | ||
const file = path.join(rootDir, currentFile+'.js') | ||
console.log('file', file) | ||
let babelOk = false | ||
try { | ||
execSync('./node_modules/.bin/babel -o bundle.js '+ file) | ||
babelOk = true | ||
} catch (e) { | ||
console.log(`Couldn't read file ${file}`) | ||
console.log(e.message) | ||
break | ||
} | ||
console.log(file) | ||
readFile(file) | ||
.then(es6 => { | ||
let code | ||
try { | ||
code = babel | ||
.transform(es6, { | ||
presets: ['es2015'], | ||
}).code | ||
} catch (e) { console.error(e) } | ||
definedFunctions.push(localize(currentFile, 'Program')) | ||
const code = fs.readFileSync('bundle.js', 'utf-8') | ||
const toplevel = UglifyJS.parse(code); | ||
const localModules = [] | ||
const walker = new UglifyJS.TreeWalker(function(node){ | ||
//check for function calls | ||
if (node instanceof UglifyJS.AST_Definitions) { | ||
// TODO: Should loop though all definitions | ||
if (node.definitions[0].value.expression && node.definitions[0].value.expression.name === 'require') { | ||
const localName = node.definitions[0].name.name | ||
const location = node.definitions[0].value.args[0].value | ||
if (/^\./.test(location)) { | ||
localModules.push([localName, location]) | ||
} | ||
} | ||
} else if (node instanceof UglifyJS.AST_Call) { | ||
//find where the calling function is defined | ||
const p = walker.find_parent(UglifyJS.AST_Defun); | ||
if (/(Number|Date|callback|require)/.test(node.expression.name)) return | ||
if (p !== undefined) { | ||
if (node.expression.name !== undefined) { | ||
// common call e.g. function() | ||
calls.push([localize(currentFile, p.name.name), localize(currentFile, node.expression.name)]) | ||
} else { | ||
// method call e.g. lib.function() | ||
const name = p.name.name | ||
const module = node.expression.start.value | ||
const moduleAsFile = localModules | ||
.filter(t => t[0] == module) | ||
.map(t => t[1]) | ||
.pop() | ||
const prop = node.expression.property | ||
if (moduleAsFile) { | ||
calls.push([localize(currentFile, name), localize(moduleAsFile, prop)]) | ||
definedFunctions.push(localize(currentFile, 'Program')) | ||
const toplevel = UglifyJS.parse(code); | ||
const localModules = [] | ||
const walker = new UglifyJS.TreeWalker(function(node){ | ||
//check for function calls | ||
if (node instanceof UglifyJS.AST_Definitions) { | ||
// TODO: Should loop though all definitions | ||
if (node.definitions[0].value.expression && node.definitions[0].value.expression.name === 'require') { | ||
const localName = node.definitions[0].name.name | ||
const location = node.definitions[0].value.args[0].value | ||
if (/^\./.test(location)) { | ||
localModules.push([localName, location]) | ||
} | ||
} | ||
} else if (node instanceof UglifyJS.AST_Call) { | ||
//find where the calling function is defined | ||
const p = walker.find_parent(UglifyJS.AST_Defun); | ||
if (/(Number|Date|callback|require)/.test(node.expression.name)) return | ||
if (p !== undefined) { | ||
if (node.expression.name !== undefined) { | ||
// common call e.g. function() | ||
calls.push([localize(currentFile, p.name.name), localize(currentFile, node.expression.name)]) | ||
} else { | ||
// method call e.g. lib.function() | ||
const name = p.name.name | ||
const module = node.expression.start.value | ||
const moduleAsFile = localModules | ||
.filter(t => t[0] == module) | ||
.map(t => t[1]) | ||
.pop() | ||
const prop = node.expression.property | ||
if (moduleAsFile) { | ||
calls.push([localize(currentFile, name), localize(moduleAsFile, prop)]) | ||
} else { | ||
calls.push([localize(currentFile, name), localize(currentFile, `${module}.${prop}`)]) | ||
} | ||
} | ||
} else { | ||
calls.push([localize(currentFile, name), localize(currentFile, `${module}.${prop}`)]) | ||
// it's a top level function | ||
if (node.expression.name !== undefined) { | ||
calls.push([localize(currentFile, 'Program'), localize(currentFile, node.expression.name)]) | ||
} else { | ||
calls.push([localize(currentFile, 'Program'), localize(currentFile, `${node.expression.start.value}.${node.expression.property}`)]) | ||
} | ||
} | ||
} | ||
if(node instanceof UglifyJS.AST_Defun) | ||
{ | ||
//defined but not called | ||
definedFunctions.push(localize(currentFile, node.name.name)) | ||
} | ||
} else { | ||
// it's a top level function | ||
if (node.expression.name !== undefined) { | ||
calls.push([localize(currentFile, 'Program'), localize(currentFile, node.expression.name)]) | ||
} else { | ||
calls.push([localize(currentFile, 'Program'), localize(currentFile, `${node.expression.start.value}.${node.expression.property}`)]) | ||
} | ||
} | ||
} | ||
if(node instanceof UglifyJS.AST_Defun) | ||
{ | ||
//defined but not called | ||
definedFunctions.push(localize(currentFile, node.name.name)) | ||
} | ||
}); | ||
toplevel.walk(walker); | ||
const modulesAsFiles = localModules.map(t => t[1]) | ||
files = files.concat(modulesAsFiles) | ||
}) | ||
toplevel.walk(walker); | ||
const modulesAsFiles = localModules.map(t => t[1]) | ||
modulesAsFiles.forEach(file => { | ||
q.push(processFile.bind(null, file)) | ||
}) | ||
totalRunning-- | ||
cb() | ||
}).catch(e => { | ||
console.error(e) | ||
cb() | ||
}) | ||
} | ||
const out = fs.openSync('callgraph.dot', 'w'); | ||
fs.writeSync(out, ` | ||
digraph test{ | ||
overlap=scalexy; | ||
`); | ||
q.push(processFile.bind(null, inputPath.base)) | ||
const filterTuple = tuple => { | ||
// This removes calls to libraries i.e. functions not defined in the file | ||
return definedFunctions.indexOf(tuple[1]) > -1 | ||
} | ||
const mapTupleToString = t => `"${t[0]}" -> "${t[1]}"` | ||
const callsStr = calls | ||
.filter(filterTuple) | ||
.map(mapTupleToString) | ||
.join(`\n`) | ||
fs.writeSync(out, callsStr + '}'); | ||
// console.log('definedFunctions', definedFunctions) | ||
// console.log('calls', calls) | ||
const unshown = calls | ||
.filter(tuple => !filterTuple(tuple)) | ||
.map(mapTupleToString) | ||
.join(`\n`) | ||
// console.log('unshown calls', unshown) | ||
q.start(function(err) { | ||
console.log('all done'); | ||
const out = fs.openSync('callgraph.dot', 'w'); | ||
fs.writeSync(out, `\ndigraph test{\noverlap=scalexy;\n`); | ||
const filterTuple = tuple => { | ||
// This removes calls to libraries i.e. functions not defined in the file | ||
return definedFunctions.indexOf(tuple[1]) > -1 | ||
} | ||
const mapTupleToString = t => `"${t[0]}" -> "${t[1]}"` | ||
const callsStr = calls | ||
.filter(filterTuple) | ||
.map(mapTupleToString) | ||
.join(`\n`) | ||
fs.writeSync(out, callsStr + '}'); | ||
// console.log('definedFunctions', definedFunctions) | ||
// console.log('calls', calls) | ||
const unshown = calls | ||
.filter(tuple => !filterTuple(tuple)) | ||
.map(mapTupleToString) | ||
.join(`\n`) | ||
// console.log('unshown calls', unshown) | ||
execSync('dot -Tpng -o callgraph.png callgraph.dot') | ||
execSync('viewnior callgraph.png') | ||
execSync('dot -Tpng -o callgraph.png callgraph.dot') | ||
execSync('viewnior callgraph.png') | ||
}); | ||
// q.on('success', function(result, job) { | ||
// console.log('job finished processing:', job.toString().replace(/\n/g, '')); | ||
// }); |
{ | ||
"name": "callgraph", | ||
"version": "1.0.3", | ||
"version": "1.0.4", | ||
"description": "Parse JS into call graphs", | ||
@@ -15,4 +15,6 @@ "scripts": { | ||
"dependencies": { | ||
"babel-cli": "^6.6.5", | ||
"babel-core": "^6.7.4", | ||
"babel-preset-es2015": "^6.6.0", | ||
"es6-promisify": "^3.0.0", | ||
"queue": "^3.1.0", | ||
"uglify-js": "^2.6.2" | ||
@@ -19,0 +21,0 @@ }, |
@@ -5,4 +5,12 @@ # Callgraph | ||
![output example](example.png) | ||
## Questions | ||
### Why UglifyJS for AST parsing and not X? | ||
Does X have `walker.find_parent`? Please let me know. | ||
## License | ||
MIT [http://gunar.mit-license.org]() |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
12853
148
16
5
+ Addedbabel-core@^6.7.4
+ Addedes6-promisify@^3.0.0
+ Addedqueue@^3.1.0
+ Addedes6-promise@3.3.1(transitive)
+ Addedes6-promisify@3.0.0(transitive)
+ Addedqueue@3.1.0(transitive)
- Removedbabel-cli@^6.6.5
- Removedanymatch@1.3.2(transitive)
- Removedarr-diff@2.0.04.0.0(transitive)
- Removedarr-flatten@1.1.0(transitive)
- Removedarr-union@3.1.0(transitive)
- Removedarray-unique@0.2.10.3.2(transitive)
- Removedassign-symbols@1.0.0(transitive)
- Removedasync-each@1.0.6(transitive)
- Removedatob@2.1.2(transitive)
- Removedbabel-cli@6.26.0(transitive)
- Removedbabel-polyfill@6.26.0(transitive)
- Removedbase@0.11.2(transitive)
- Removedbinary-extensions@1.13.1(transitive)
- Removedbindings@1.5.0(transitive)
- Removedbraces@1.8.52.3.2(transitive)
- Removedcache-base@1.0.1(transitive)
- Removedchokidar@1.7.0(transitive)
- Removedclass-utils@0.3.6(transitive)
- Removedcollection-visit@1.0.0(transitive)
- Removedcommander@2.20.3(transitive)
- Removedcomponent-emitter@1.3.1(transitive)
- Removedcopy-descriptor@0.1.1(transitive)
- Removedcore-util-is@1.0.3(transitive)
- Removeddecode-uri-component@0.2.2(transitive)
- Removeddefine-property@0.2.51.0.02.0.2(transitive)
- Removedexpand-brackets@0.1.52.1.4(transitive)
- Removedexpand-range@1.8.2(transitive)
- Removedextend-shallow@2.0.13.0.2(transitive)
- Removedextglob@0.3.22.0.4(transitive)
- Removedfile-uri-to-path@1.0.0(transitive)
- Removedfilename-regex@2.0.1(transitive)
- Removedfill-range@2.2.44.0.0(transitive)
- Removedfor-in@1.0.2(transitive)
- Removedfor-own@0.1.5(transitive)
- Removedfragment-cache@0.2.1(transitive)
- Removedfs-readdir-recursive@1.1.0(transitive)
- Removedfs.realpath@1.0.0(transitive)
- Removedfsevents@1.2.13(transitive)
- Removedfunction-bind@1.1.2(transitive)
- Removedget-value@2.0.6(transitive)
- Removedglob@7.2.3(transitive)
- Removedglob-base@0.3.0(transitive)
- Removedglob-parent@2.0.0(transitive)
- Removedgraceful-fs@4.2.11(transitive)
- Removedhas-value@0.3.11.0.0(transitive)
- Removedhas-values@0.1.41.0.0(transitive)
- Removedhasown@2.0.2(transitive)
- Removedinflight@1.0.6(transitive)
- Removedis-accessor-descriptor@1.0.1(transitive)
- Removedis-binary-path@1.0.1(transitive)
- Removedis-data-descriptor@1.0.1(transitive)
- Removedis-descriptor@0.1.71.0.3(transitive)
- Removedis-dotfile@1.0.3(transitive)
- Removedis-equal-shallow@0.1.3(transitive)
- Removedis-extendable@0.1.11.0.1(transitive)
- Removedis-extglob@1.0.0(transitive)
- Removedis-glob@2.0.1(transitive)
- Removedis-number@2.1.03.0.04.0.0(transitive)
- Removedis-plain-object@2.0.4(transitive)
- Removedis-posix-bracket@0.1.1(transitive)
- Removedis-primitive@2.0.0(transitive)
- Removedis-windows@1.0.2(transitive)
- Removedisarray@1.0.0(transitive)
- Removedisobject@2.1.03.0.1(transitive)
- Removedkind-of@4.0.06.0.3(transitive)
- Removedmap-cache@0.2.2(transitive)
- Removedmap-visit@1.0.0(transitive)
- Removedmath-random@1.0.4(transitive)
- Removedmicromatch@2.3.113.1.10(transitive)
- Removedmixin-deep@1.3.2(transitive)
- Removednan@2.22.0(transitive)
- Removednanomatch@1.2.13(transitive)
- Removednormalize-path@2.1.1(transitive)
- Removedobject-assign@4.1.1(transitive)
- Removedobject-copy@0.1.0(transitive)
- Removedobject-visit@1.0.1(transitive)
- Removedobject.omit@2.0.1(transitive)
- Removedobject.pick@1.3.0(transitive)
- Removedonce@1.4.0(transitive)
- Removedoutput-file-sync@1.1.2(transitive)
- Removedparse-glob@3.0.4(transitive)
- Removedpascalcase@0.1.1(transitive)
- Removedposix-character-classes@0.1.1(transitive)
- Removedpreserve@0.2.0(transitive)
- Removedprocess-nextick-args@2.0.1(transitive)
- Removedrandomatic@3.1.1(transitive)
- Removedreadable-stream@2.3.8(transitive)
- Removedreaddirp@2.2.1(transitive)
- Removedregenerator-runtime@0.10.5(transitive)
- Removedregex-cache@0.4.4(transitive)
- Removedregex-not@1.0.2(transitive)
- Removedremove-trailing-separator@1.1.0(transitive)
- Removedrepeat-element@1.1.4(transitive)
- Removedresolve-url@0.2.1(transitive)
- Removedret@0.1.15(transitive)
- Removedsafe-buffer@5.1.2(transitive)
- Removedsafe-regex@1.1.0(transitive)
- Removedset-value@2.0.1(transitive)
- Removedsnapdragon@0.8.2(transitive)
- Removedsnapdragon-node@2.1.1(transitive)
- Removedsnapdragon-util@3.0.1(transitive)
- Removedsource-map-resolve@0.5.3(transitive)
- Removedsource-map-url@0.4.1(transitive)
- Removedsplit-string@3.1.0(transitive)
- Removedstatic-extend@0.1.2(transitive)
- Removedstring_decoder@1.1.1(transitive)
- Removedto-object-path@0.3.0(transitive)
- Removedto-regex@3.0.2(transitive)
- Removedto-regex-range@2.1.1(transitive)
- Removedunion-value@1.0.1(transitive)
- Removedunset-value@1.0.0(transitive)
- Removedurix@0.1.0(transitive)
- Removeduse@3.1.1(transitive)
- Removeduser-home@1.1.1(transitive)
- Removedutil-deprecate@1.0.2(transitive)
- Removedv8flags@2.1.1(transitive)
- Removedwrappy@1.0.2(transitive)