From 2f1d8205094894b379169263b02bff4ae23f5c77 Mon Sep 17 00:00:00 2001 From: Sven Slootweg Date: Mon, 6 Feb 2017 10:41:46 +0100 Subject: [PATCH] Initial commit --- .gitignore | 1 + index.js | 57 +++++++++++++++++++ lib/ast/render/colorize-type.js | 18 ++++++ lib/ast/render/mark-code.js | 11 ++++ lib/ast/render/section-tag.js | 21 +++++++ lib/ast/render/short-description.js | 27 +++++++++ lib/ast/visualize.js | 87 +++++++++++++++++++++++++++++ package.json | 26 +++++++++ test/1.js | 14 +++++ 9 files changed, 262 insertions(+) create mode 100644 .gitignore create mode 100644 index.js create mode 100644 lib/ast/render/colorize-type.js create mode 100644 lib/ast/render/mark-code.js create mode 100644 lib/ast/render/section-tag.js create mode 100644 lib/ast/render/short-description.js create mode 100644 lib/ast/visualize.js create mode 100644 package.json create mode 100644 test/1.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/index.js b/index.js new file mode 100644 index 0000000..533a12f --- /dev/null +++ b/index.js @@ -0,0 +1,57 @@ +'use strict'; + +const blessed = require("blessed"); +const visualizeAST = require("./lib/ast/visualize"); +const esprima = require("esprima"); +const util = require("util"); +const fs = require("fs"); + +const markCode = require("./lib/ast/render/mark-code"); + +let code = fs.readFileSync("./test/1.js").toString(); +let _testdata = esprima.parse(code, { + range: true, + loc: true +}); + +let ast = visualizeAST(_testdata); + +let screen = blessed.screen({ + smartCSR: true, + terminal: "xterm-256color", + fullUnicode: true +}); + +let list = blessed.list({ + parent: screen, + selectedBg: "blue", + mouse: true, + keys: true, + width: "50%" +}); + +list.setItems(ast.getLines()); +list.select(0); + +list.on("select item", (_, index) => { + let [start, end] = ast.getItemAtIndex(index).range; + codeBox.setContent(markCode(code, start, end)); + screen.render(); +}); + +let codeBox = blessed.box({ + parent: screen, + width: "50%", + left: "50%", + border: { + type: "line", + fg: "#ffffff" + }, + content: code +}); + +screen.key("q", function(ch, key) { + return process.exit(0); +}); + +screen.render(); diff --git a/lib/ast/render/colorize-type.js b/lib/ast/render/colorize-type.js new file mode 100644 index 0000000..febf404 --- /dev/null +++ b/lib/ast/render/colorize-type.js @@ -0,0 +1,18 @@ +'use strict'; + +const chalk = require("chalk"); +const ansiStyles = require("ansi-styles"); + +module.exports = function colorizedNodeType(astNode) { + let nodeType = astNode.type; + + if (nodeType === "FunctionDeclaration" || nodeType === "ArrowFunctionExpression") { + return chalk.cyan(nodeType); + } else if (nodeType === "CallExpression") { + return chalk.green(nodeType); + } else if (nodeType === "ReturnStatement") { + return chalk.red(nodeType); + } else { + return ansiStyles.color.ansi256.rgb(100, 100, 100) + nodeType; + } +} diff --git a/lib/ast/render/mark-code.js b/lib/ast/render/mark-code.js new file mode 100644 index 0000000..ac1cd1d --- /dev/null +++ b/lib/ast/render/mark-code.js @@ -0,0 +1,11 @@ +'use strict'; + +const chalk = require("chalk"); + +module.exports = function markCode(code, startPosition, endPosition) { + return [ + code.slice(0, startPosition), + chalk.bgBlue(code.slice(startPosition, endPosition)), + code.slice(endPosition) + ].join(""); +} diff --git a/lib/ast/render/section-tag.js b/lib/ast/render/section-tag.js new file mode 100644 index 0000000..f9ea8cb --- /dev/null +++ b/lib/ast/render/section-tag.js @@ -0,0 +1,21 @@ +'use strict'; + +const chalk = require("chalk"); +const ansiStyles = require("ansi-styles"); +const pad = require("pad"); + +function grayBackground(string) { + return ansiStyles.bgColor.ansi256.rgb(33, 33, 33) + string + ansiStyles.bgColor.close; +} + +module.exports = function sectionTag(sectionName, {isArray} = {isArray: false}) { + let arrayPrefix; + + if (isArray) { + arrayPrefix = chalk.red.bold("[]"); + } else { + arrayPrefix = " "; + } + + return `${arrayPrefix} ${grayBackground(`${pad(10, sectionName)} ->`)}`; +} diff --git a/lib/ast/render/short-description.js b/lib/ast/render/short-description.js new file mode 100644 index 0000000..7bb178a --- /dev/null +++ b/lib/ast/render/short-description.js @@ -0,0 +1,27 @@ +'use strict'; + +function shortDescription(astNode) { + if (astNode.type === "FunctionDeclaration") { + return `function ${shortDescription(astNode.id)}(${renderArguments(astNode.params)}) { ... }`; + } else if (astNode.type === "ReturnStatement") { + return `return ${shortDescription(astNode.argument)}`; + } else if (astNode.type === "CallExpression") { + return `${shortDescription(astNode.callee)}(${renderArguments(astNode.arguments)})`; + } else if (astNode.type === "MemberExpression") { + return `${shortDescription(astNode.object)}.${shortDescription(astNode.property)}`; + } else if (astNode.type === "Identifier") { + return `${astNode.name}`; + } else if (astNode.type === "ArrowFunctionExpression") { + return `(${renderArguments(astNode.params)}) => { ... }`; + } else if (astNode.type === "Literal") { + return astNode.raw; + } else { + return ""; + } +} + +function renderArguments(args) { + return args.map(arg => shortDescription(arg)).join(", "); +} + +module.exports = shortDescription; diff --git a/lib/ast/visualize.js b/lib/ast/visualize.js new file mode 100644 index 0000000..a849070 --- /dev/null +++ b/lib/ast/visualize.js @@ -0,0 +1,87 @@ +'use strict'; + +const archy = require("archy"); +const chalk = require("chalk"); +const memoizee = require("memoizee"); +const assureArray = require("assure-array"); +const util = require("util"); + +const colorizedNodeType = require("./render/colorize-type"); +const shortDescription = require("./render/short-description"); +const sectionTag = require("./render/section-tag"); + +module.exports = function visualizeAST(ast) { + let indexedItems = []; // NOTE: Impure, but is necessary to make getItemAtIndex work... + + function sectionLabel(name, items) { + return { + label: chalk.yellow(`${name}:`), + nodes: items + } + } + + function convertChildNodes(key, childNodes) { + let arrayifiedNodes = assureArray(childNodes); + + let treeNodes = arrayifiedNodes.map((node) => { + let treeNode = astNodeToTreeNode(node); + treeNode.label = sectionTag(key, {isArray: Array.isArray(childNodes)}) + " " + treeNode.label; + return treeNode; + }); + + return treeNodes; + } + + function astNodeToTreeNode(astNode) { + indexedItems.push(astNode); + + let nodeLabel = `${chalk.bold(colorizedNodeType(astNode))} ${shortDescription(astNode)}`; + + let primitiveProperties = [], singleChildren = [], multipleChildren = []; + + Object.keys(astNode).forEach((key) => { + if (key === "loc" || key === "range") { + /* These are Esprima annotations. */ + return; + } + + let value = astNode[key]; + + if (typeof value === "string" || typeof value === "boolean" || typeof value === "number" || value == null) { + primitiveProperties.push(key); + } else if (Array.isArray(value)) { + if (value.length > 0) { + multipleChildren.push(key); + } + } else if (typeof value === "object" && value.type != null) { + singleChildren.push(key); + } else { + throw new Error(`Encountered unrecognized value type in AST: ${typeof value} -- ${util.inspect(value)}`); + } + }); + + let childNodes = (singleChildren.concat(multipleChildren)).map((key) => { + return convertChildNodes(key, astNode[key]); + }).reduce((combined, array) => { + return combined.concat(array); + }, []); + + return { + label: nodeLabel, + nodes: childNodes + } + } + + return { + getString: memoizee(function getString() { + let treeNodes = astNodeToTreeNode(ast); + return archy(treeNodes); + }), + getLines: function getLines() { + return this.getString().split("\n"); + }, + getItemAtIndex: function getItemAtIndex(index) { + return indexedItems[index]; + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..b552579 --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "vulnerability-scanner", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git@git.cryto.net:joepie91/vulnerability-scanner.git" + }, + "keywords": [], + "author": "Sven Slootweg", + "license": "WTFPL", + "dependencies": { + "ansi-styles": "^3.0.0", + "archy": "^1.0.0", + "assure-array": "^1.0.0", + "blessed": "^0.1.81", + "chalk": "^1.1.3", + "esprima": "^3.1.3", + "memoizee": "^0.4.1", + "pad": "^1.0.2" + } +} diff --git a/test/1.js b/test/1.js new file mode 100644 index 0000000..3bdb05e --- /dev/null +++ b/test/1.js @@ -0,0 +1,14 @@ +'use strict'; + +function getContentWithComments() { + return Promise.try(() => { + return knex('content'); + }).map((row) => { + return Promise.try(() => { + return knex('comments').where('content', row.id); + }).then((comments) => { + row.comments = comments; + return row; + }); + }); +}