Progress so far

master
Sven Slootweg 6 years ago
commit 3993998322

@ -0,0 +1,2 @@
node_modules/**/*
src/testcases/**/*

@ -0,0 +1,78 @@
module.exports = {
"env": {
"browser": true,
"commonjs": true,
"es6": true,
"node": true
},
"parserOptions": {
"ecmaFeatures": {
"experimentalObjectRestSpread": true,
"jsx": true
}
},
"plugins": [
"react"
],
"rules": {
/* Things that should effectively be syntax errors. */
"indent": [ "error", "tab", {
SwitchCase: 1
}],
"linebreak-style": [ "error", "unix" ],
"semi": [ "error", "always" ],
/* Things that are always mistakes. */
"getter-return": [ "error" ],
"no-compare-neg-zero": [ "error" ],
"no-dupe-args": [ "error" ],
"no-dupe-keys": [ "error" ],
"no-duplicate-case": [ "error" ],
"no-empty": [ "error" ],
"no-empty-character-class": [ "error" ],
"no-ex-assign": [ "error" ],
"no-extra-semi": [ "error" ],
"no-func-assign": [ "error" ],
"no-invalid-regexp": [ "error" ],
"no-irregular-whitespace": [ "error" ],
"no-obj-calls": [ "error" ],
"no-sparse-arrays": [ "error" ],
"no-undef": [ "error" ],
"no-unreachable": [ "error" ],
"no-unsafe-finally": [ "error" ],
"use-isnan": [ "error" ],
"valid-typeof": [ "error" ],
"curly": [ "error" ],
"no-caller": [ "error" ],
"no-fallthrough": [ "error" ],
"no-extra-bind": [ "error" ],
"no-extra-label": [ "error" ],
"array-callback-return": [ "error" ],
"prefer-promise-reject-errors": [ "error" ],
"no-with": [ "error" ],
"no-useless-concat": [ "error" ],
"no-unused-labels": [ "error" ],
"no-unused-expressions": [ "error" ],
"no-unused-vars": [ "error" ],
"no-return-assign": [ "error" ],
"no-self-assign": [ "error" ],
"no-new-wrappers": [ "error" ],
"no-redeclare": [ "error" ],
"no-loop-func": [ "error" ],
"no-implicit-globals": [ "error" ],
"strict": [ "error", "global" ],
/* Make JSX not cause 'unused variable' errors. */
"react/jsx-uses-react": ["error"],
"react/jsx-uses-vars": ["error"],
/* Development code that should be removed before deployment. */
"no-console": [ "warn" ],
"no-constant-condition": [ "warn" ],
"no-debugger": [ "warn" ],
"no-alert": [ "warn" ],
"no-warning-comments": ["warn", {
terms: ["fixme"]
}],
/* Common mistakes that can *occasionally* be intentional. */
"no-template-curly-in-string": ["warn"],
"no-unsafe-negation": [ "warn" ],
}
};

1
.gitignore vendored

@ -0,0 +1 @@
node_modules

@ -0,0 +1,67 @@
'use strict';
const Promise = require("bluebird");
const gulp = require("gulp");
const gulpNamedLog = require("gulp-named-log");
const gulpNodemon = require("gulp-nodemon");
const presetSCSS = require("@joepie91/gulp-preset-scss");
const awaitServer = require("await-server");
const gulpLivereload = require("gulp-livereload");
const patchLivereloadLogger = require("@joepie91/gulp-partial-patch-livereload-logger");
patchLivereloadLogger(gulpLivereload);
let config = {
scss: {
source: "./src/scss/**/*.scss",
destination: "./public/"
}
};
let serverLogger = gulpNamedLog("server");
gulp.task("nodemon", ["scss", "livereload"], () => {
gulpNodemon({
script: "server.js",
ignore: [
"gulpfile.js",
"node_modules",
"public",
"src/frontend"
],
ext: "js pug"
}).on("start", () => {
Promise.try(() => {
serverLogger.info("Starting...");
return awaitServer(3000);
}).then(() => {
serverLogger.info("Started!");
gulpLivereload.changed("*");
});
});
});
gulp.task("scss", () => {
return gulp.src("./src/scss/style.scss")
.pipe(presetSCSS({
livereload: gulpLivereload,
cacheKey: false
}))
.pipe(gulp.dest(config.scss.destination));
});
gulp.task("livereload", () => {
gulpLivereload.listen({
quiet: true
});
});
gulp.task("watch-css", () => {
gulp.watch(config.scss.source, ["scss"]);
});
gulp.task("watch", ["nodemon", "watch-css"]);
gulp.task("default", ["watch"]);

11446
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -0,0 +1,60 @@
{
"name": "js-analyzer",
"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/js-analyzer.git"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@joepie91/gulp-partial-patch-livereload-logger": "^1.0.1",
"@joepie91/gulp-preset-scss": "^1.1.0",
"@joepie91/webpack-preset-babel": "^1.0.0",
"@joepie91/webpack-preset-hot-reload": "^1.0.0",
"@joepie91/webpack-preset-simple-bundle": "^1.0.0",
"await-server": "^1.0.0",
"babel-core": "^6.26.0",
"babel-loader": "^7.1.4",
"babel-preset-es2015": "^6.24.1",
"babel-preset-react": "^6.24.1",
"classnames": "^2.2.5",
"create-react-class": "^15.6.3",
"document-ready-promise": "^3.0.1",
"eslint": "^4.19.1",
"eslint-plugin-react": "^7.7.0",
"gulp": "^3.9.1",
"gulp-cached": "^1.1.1",
"gulp-livereload": "^3.8.1",
"gulp-named-log": "^1.0.1",
"gulp-nodemon": "^2.2.1",
"gulp-rename": "^1.2.2",
"raw-loader": "^0.5.1",
"react": "^16.2.0",
"react-dom": "^16.3.1",
"react-hot-loader": "^4.0.1",
"simple-line-icons": "^2.4.1",
"webpack": "^4.4.1",
"webpack-dev-middleware": "^3.1.0",
"webpack-hot-middleware": "^2.21.2",
"webpack-serve": "^0.3.1",
"webpack-stream-fixed": "^3.2.2",
"webpack-preset-loader": "^1.0.0"
},
"dependencies": {
"bluebird": "^3.5.1",
"chalk": "^2.3.2",
"create-event-emitter": "^1.0.0",
"esprima": "^4.0.0",
"express": "^4.16.3",
"express-promise-router": "^3.0.1",
"filter-values": "^0.4.1",
"repeat-string": "^1.6.1"
}
}

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>JS Analyzer Demo</title>
<script src="/static/bundle.js"></script>
<link rel="stylesheet" href="/static/style.css">
</head>
<body>
<div id="app"></div>
</body>
</html>

@ -0,0 +1,89 @@
body {
font-family: sans-serif; }
.astItem.hasBlockScope {
background: linear-gradient(#e7e1d3, transparent 64px); }
.astItem.hasFunctionScope {
background: linear-gradient(#ddd3e7, transparent 64px); }
.astItem .arrow, .astItem .type {
cursor: default;
user-select: none;
-webkit-user-select: none;
-mozilla-user-select: none; }
.astItem .type {
font-weight: bold;
color: #2b1d82; }
.astItem .type > .preview {
font-weight: normal;
margin-left: 8px;
margin-right: 8px;
color: #661402; }
.astItem .scopeId {
display: inline-block;
font-weight: bold;
font-size: 13px;
margin-left: 5px;
padding: 1px 4px;
background-color: #d3e7dd;
border: 1px solid #4ca175;
border-radius: 5px; }
.astItem .attributes, .astItem .children, .astItem .childSequences {
margin-left: 20px; }
.astItem .children .child > .name, .astItem .childSequences .child > .name {
font-weight: bold;
border-bottom: 1px solid; }
.astItem .children .child .items, .astItem .childSequences .child .items {
padding-left: 2px;
border-left: 1px solid; }
.astItem .childSequences > .child > .name {
border-bottom-color: #450303; }
.astItem .childSequences > .child > .name:after {
margin-left: 5px;
font-size: 14px;
color: #450303;
content: "(array)"; }
.astItem .childSequences > .child > .items {
border-left-color: #450303; }
.astItem .children > .child > .name {
border-bottom-color: #132890; }
.astItem .children > .child > .items {
border-left-color: #132890; }
.astItem .attributes {
font-size: 14px; }
.astItem .attributes .attribute {
display: inline-block; }
.astItem .attributes .attribute:not(:last-child):after {
content: ",";
margin-right: 8px; }
.astItem .attributes .attribute .name {
font-weight: bold;
margin-right: 5px; }
.astItem .attributes .attribute .name:after {
content: ":"; }
.astItem .attributes .attribute .value {
background-color: #f2f2f2;
padding: 0px 3px;
border: 1px solid silver;
font-family: "monospace"; }
.astItem .attributes .attribute .value .boolean {
color: #3e0029; }
.astItem .attributes .attribute .value .number {
color: #441200; }
.astItem .attributes .attribute .value .string {
color: #132e00; }
.astItem .attributes .attribute .value .string:before, .astItem .attributes .attribute .value .string:after {
content: "\""; }
.astItem .attributes .attribute .value .null {
color: gray; }

@ -0,0 +1,25 @@
'use strict';
const express = require("express");
const path = require("path");
const webpackDevMiddleware = require("webpack-dev-middleware");
const webpackHotMiddleware = require("webpack-hot-middleware");
const webpack = require("webpack")(require("./webpack.config.js"));
let app = express();
app.use(webpackDevMiddleware(webpack, {
publicPath: "/static/"
}));
app.use(webpackHotMiddleware(webpack));
app.use("/static", express.static(path.join(__dirname, "public")));
app.get("/", (req, res) => {
res.sendFile(path.join(__dirname, "public/index.html"));
});
app.listen(3000, () => {
console.log("Server listening on port 3000");
});

@ -0,0 +1,9 @@
'use strict';
const isNode = require("./node");
const isNodes = require("./nodes");
module.exports = function isAttribute(value) {
/* We assume that anything that isn't a node or a list of them, is therefore an attribute. */
return (!isNode(value) && !isNodes(value) && typeof value !== "function");
};

@ -0,0 +1,5 @@
'use strict';
module.exports = function isInternal(key) {
return (key[0] === "_");
};

@ -0,0 +1,5 @@
'use strict';
module.exports = function isNode(value) {
return (value != null && typeof value === "object" && !Array.isArray(value) && value.type != null);
};

@ -0,0 +1,8 @@
'use strict';
const isNode = require("./node");
module.exports = function isNodes(value) {
/* We're cheating a little bit here; an empty array is considered an array of nodes, and we only check whether the first element is a node or not. */
return (Array.isArray(value) && (value.length === 0 || isNode(value[0])));
};

@ -0,0 +1,6 @@
'use strict';
const createNodePropertyFilterFunction = require("./filter-node-properties");
const getAttributes = require("./get-attributes");
module.exports = createNodePropertyFilterFunction(getAttributes);

@ -0,0 +1,10 @@
'use strict';
module.exports = function createNodePropertyFilterFunction(predicate) {
return function (astItem) {
return predicate(astItem).reduce((obj, key) => {
obj[key] = astItem[key];
return obj;
}, {});
};
};

@ -0,0 +1,6 @@
'use strict';
const createNodePropertyFilterFunction = require("./filter-node-properties");
const getNodeSequences = require("./get-node-sequences");
module.exports = createNodePropertyFilterFunction(getNodeSequences);

@ -0,0 +1,8 @@
'use strict';
const createNodePropertyFilterFunction = require("./filter-node-properties");
const getNodes = require("./get-nodes");
/* FIXME: Possibly 'nodes' should be renamed to 'children' or 'child nodes' in all of the node/ modules, for clarity? */
module.exports = createNodePropertyFilterFunction(getNodes);

@ -0,0 +1,9 @@
'use strict';
const getFiltered = require("./get-filtered");
const isAttribute = require("../is/attribute");
const isInternal = require("../is/internal");
module.exports = getFiltered("attributes", (key, node) => {
return (!isInternal(key) && key !== "type" && isAttribute(node[key]));
});

@ -0,0 +1,15 @@
'use strict';
module.exports = function (cacheName, filterCallback) {
let cacheKey = `_cacheFilteredProperties_${cacheName}`;
return function getFilteredProperties(node) {
if (node._clean === true && node[cacheKey] != null) {
return node[cacheKey];
} else {
let filteredProperties = Object.keys(node).filter((key) => filterCallback(key, node));
node[cacheKey] = filteredProperties;
return filteredProperties;
}
};
};

@ -0,0 +1,9 @@
'use strict';
const getFiltered = require("./get-filtered");
const isNodes = require("../is/nodes");
const isInternal = require("../is/internal");
module.exports = getFiltered("nodeSequences", (key, node) => {
return (!isInternal(key) && key !== "type" && isNodes(node[key]));
});

@ -0,0 +1,9 @@
'use strict';
const getFiltered = require("./get-filtered");
const isNode = require("../is/node");
const isInternal = require("../is/internal");
module.exports = getFiltered("nodes", (key, node) => {
return (!isInternal(key) && key !== "type" && isNode(node[key]));
});

@ -0,0 +1,42 @@
'use strict';
const createAstWalker = require("./walker");
let processors = [
require("./processors/add-parent"),
require("./processors/mark-scopes"),
require("./processors/add-variable-declarator-kind"),
require("./processors/utility-methods"),
];
module.exports = function processAst(ast) {
let initializedProcessors = processors.map((processor) => {
return processor();
});
let walker = createAstWalker(ast);
let finishedWalking = false;
walker.on("enterNode", function processNode(node) {
for (let processor of initializedProcessors) {
if (processor.onEnter != null) {
processor.onEnter(node);
}
}
});
walker.on("exitNode", function processNode(node) {
for (let processor of initializedProcessors) {
if (processor.onExit != null) {
processor.onExit(node);
}
}
});
while (finishedWalking === false) {
walker.step();
finishedWalking = walker.done;
}
return;
};

@ -0,0 +1,20 @@
'use strict';
module.exports = function createAddParentProcessor() {
let currentLevel = 0;
let lastKnownNodes = [];
return {
onEnter: function onEnter(node) {
if (currentLevel > 0) {
node._parent = lastKnownNodes[currentLevel - 1];
}
lastKnownNodes[currentLevel] = node;
currentLevel += 1;
},
onExit: function onExit() {
currentLevel -= 1;
}
};
};

@ -0,0 +1,11 @@
'use strict';
module.exports = function () {
return {
onEnter: function onEnter(node) {
if (node.type === "VariableDeclarator") {
node._kind = node._parent.kind;
}
}
};
};

@ -0,0 +1,24 @@
'use strict';
const createsBlockScope = require("../scope/creates-block-scope");
const createsFunctionScope = require("../scope/creates-function-scope");
/* FIXME: Find a better solution for this? */
const FUNCTION_SCOPE = 1;
const BLOCK_SCOPE = 2;
module.exports = function () {
let scopeId = 0;
return {
onEnter: function onEnter(node) {
if (createsFunctionScope(node)) {
node._scopeId = scopeId++;
node._scopeType = FUNCTION_SCOPE;
} if (createsBlockScope(node)) {
node._scopeId = scopeId++;
node._scopeType = BLOCK_SCOPE;
}
}
};
};

@ -0,0 +1,100 @@
'use strict';
const getNodes = require("../node/get-nodes");
const getNodeSequences = require("../node/get-node-sequences");
/* Simple node types are always simple, no matter what. */
let simpleNodeTypes = new Set([
"Literal",
"Identifier",
"ThisExpression",
"ContinueStatement",
"MemberExpression", /* FIXME: This may be wrong if a computed property is involved? */
]);
/* "Semi-simple" node types are only considered simple if all of their children are also simple. */
let semiSimpleNodeTypes = new Set([
"BinaryExpression",
"UnaryExpression",
"UpdateExpression",
"LogicalExpression",
"AssignmentPattern",
"AssignmentExpression",
"VariableDeclarator",
"Property",
"ReturnStatement",
"IfStatement",
"ConditionalExpression",
"ExpressionStatement"
]);
/* TODO: CallExpression is only simple if nested one level deep at most. */
function checkChildNodes(node, predicate) {
let nodeProperties = node.getNodeProperties();
let nodeSequenceProperties = node.getNodeSequenceProperties();
let checkFailed = false;
for (let property of nodeProperties) {
if (!predicate(node[property])) {
checkFailed = true;
}
}
for (let property of nodeSequenceProperties) {
for (let item of node[property]) {
if (!predicate(item)) {
checkFailed = true;
}
}
}
return !checkFailed;
}
function _isSimple(node) {
if (simpleNodeTypes.has(node.type)) {
return true;
} else if (semiSimpleNodeTypes.has(node.type)) {
return checkChildNodes(node, (childNode) => childNode.isSimple());
} else if (node.type === "CallExpression") {
return checkChildNodes(node, (childNode) => childNode.type !== "CallExpression" && childNode.isSimple());
} else if (node.type === "ObjectExpression") {
return (node.properties.length === 0);
} else if (node.type === "ArrayExpression") {
return (node.elements.length === 0);
} else {
return false;
}
}
function isSimple() {
if (this._cacheIsSimple != null) {
return this._cacheIsSimple;
} else {
let result = _isSimple(this);
this._cacheIsSimple = result;
return result;
}
}
function getNodeProperties() {
return getNodes(this);
}
function getNodeSequenceProperties() {
return getNodeSequences(this);
}
module.exports = function createUtilityMethodsProcessor() {
return {
onEnter: function onEnter(node) {
Object.assign(node, {
isSimple: isSimple,
getNodeProperties: getNodeProperties,
getNodeSequenceProperties: getNodeSequenceProperties
});
}
};
};

@ -0,0 +1,26 @@
'use strict';
/*
special case: in a function...
if defaults: function param env + body declaration env
if no defaults: single shared env
this is to keep default-expressions from seeing the body-declared variables
* eval (effectively produces a function scope in strict mode, but a block scope in non-strict mode)
- for direct eval, parent scope is the calling scope
- for indirect eval, parent scope is the global scope
* Block
* for loop (including the iterator value!) - unique scope for each iteration?
* catch clause (for the error argument)
*/
module.exports = function createsBlockScope(node) {
return (
node.type === "BlockStatement" ||
node.type === "CatchClause" ||
node.type === "ForStatement" ||
/* The below is not quite correct; needs to take into consideration strict mode, whether it references global eval, etc. */
(node.type === "CallExpression" && node.callee.name === "eval")
);
};

@ -0,0 +1,17 @@
'use strict';
/*
special case: in a function...
if defaults: function param env + body declaration env
if no defaults: single shared env
this is to keep default-expressions from seeing the body-declared variables
*/
module.exports = function createsFunctionScope(node) {
return (
node.type === "FunctionDeclaration" ||
node.type === "FunctionExpression" ||
node.type === "ArrowFunctionExpression"
);
};

@ -0,0 +1,108 @@
'use strict';
module.exports = function createScopeTracker() {
let scopeLevel = 0;
let lastFunctionScopeLevel = 0;
let scopes = [];
let references = [];
let scopeId = 0;
let declarationId = 0;
function createDeclaration(type, name) {
return {
id: declarationId++,
type: type,
name: name
};
}
function createScope(type) {
return {
_declarations: [],
_declarationNames: new Set(),
id: scopeId++,
type: type,
addDeclaration: function addDeclaration(type, name) {
this._declarations.push(createDeclaration(type, name));
this._declarationNames.add(name);
},
hasDeclaration: function hasDeclaration(name) {
return this._declarationNames.has(name);
}
};
}
return {
_openScope: function _openScope(type) {
scopeLevel++;
scopes[scopeLevel] = createScope(type);
references[scopeLevel] = [];
},
openBlockScope: function openBlockScope() {
this._openScope("block");
},
openFunctionScope: function openFunctionScope() {
this._openScope("function");
lastFunctionScopeLevel = scopeLevel;
},
markDeclaration: function (node) {
if (node.kind === "var") {
scopes[lastFunctionScopeLevel].addDeclaration(node.kind, name);
} else if (node.kind === "let" || node.kind === "const") {
scopes[scopeLevel].addDeclaration(node.kind, name);
}
},
markReference: function markReference(node) {
/* FIXME: Temporal dead zone! */
references[scopeLevel].push(node);
},
_closeScope: function _closeScope(type) {
if (scopes[scopeLevel].type === type) {
let scope = scopes[scopeLevel];
/* MARKER: reconcile references */
let remainingReferences = [];
let satisfiedReferences = [];
for (let reference of references) {
if (scope.hasDeclaration(reference.name)) {
satisfiedReferences.push(reference);
} else {
remainingReferences.push(reference);
}
}
references = remainingReferences;
scopeLevel--;
if (lastFunctionScopeLevel > scopeLevel) {
/* Our last-known function scope level is no longer valid, so we need to recalculate it. */
for (let i = scopeLevel; i >= 0; i--) {
if (scopes[i].type === "function") {
lastFunctionScopeLevel = i;
break;
}
}
/* Should this ever happen? */
lastFunctionScopeLevel = 0;
}
return {
scope: scope,
references: references
};
} else {
throw new Error(`Bug! Expected ${type} scope, but found ${scopes[scopeLevel].type} scope`);
}
},
closeBlockScope: function closeBlockScope() {
return this._closeScope("block");
},
closeFunctionScope: function closeFunctionScope() {
return this._closeScope("function");
}
};
};

@ -0,0 +1,26 @@
'use strict';
module.exports = function findNestedProperties(node) {
return Object.keys(node).filter((property) => {
/* We want to ignore any _-prefixed properties; we assume these to be internal, non-AST data. */
return (property[0] !== "_");
}).map((property) => {
let value = node[property];
if (Array.isArray(value) && value.length > 0) {
return {
property: property,
type: "array"
};
} else if (value != null && value.type != null) {
return {
property: property,
type: "node"
};
} else {
return null;
}
}).filter((result) => {
return (result != null);
});
};

@ -0,0 +1,149 @@
'use strict';
const chalk = require("chalk");
const repeatString = require("repeat-string");
const createEventEmitter = require("create-event-emitter");
const findNestedProperties = require("./find-nested-properties");
module.exports = function createWalker(tree, logging = false) {
let properties = findNestedProperties(tree);
return createEventEmitter({
tree: tree,
done: false,
currentLevel: 0,
currentProperty: properties[0],
currentPropertyIndex: 0,
currentStack: [ tree ],
currentIndex: 0,
currentNode: tree,
properties: [ null ],
indexes: [ null ],
propertyIndexes: [ null ],
stacks: [ [tree] ],
nodes: [ tree ], // FIXME: This might be redundant?
_padding: function _padding() {
return repeatString(" ", this.currentLevel);
},
_debugStatus: function () {
return `(level: ${this.currentLevel}, property: ${this.currentProperty.property}, index: ${this.currentIndex})`;
},
getPath: function getPath() {
let path = new Array(this.currentLevel * 2);
for (let i = 1; i <= this.currentLevel; i++) {
path[(i-1) * 2] = this.properties[i][this.propertyIndexes[i]].property;
path[(i-1) * 2 + 1] = this.indexes[i];
}
return path;
},
step: function step() {
if (this.done !== true) {
let node = this.currentStack[this.currentIndex];
if (logging) {
console.log(this._padding(), chalk.bold.red(`Node encountered: ${node.type}`), this._debugStatus());
}
this.emit("enterNode", node);
this.levelDown(node);
} else {
throw new Error("Walker reached end");
}
},
nextIndex: function nextIndex() {
this.emit("exitNode", this.currentStack[this.currentIndex]);
if (this.currentIndex < this.currentStack.length - 1) {
this.updateIndex(this.currentIndex + 1);
if (logging) {
console.log(this._padding(), chalk.bold.blue(`New index: ${this.currentIndex}`), this._debugStatus());
}
} else {
this.nextProperty();
}
},
nextProperty: function nextProperty() {
if (this.currentPropertyIndex < this.properties[this.currentLevel].length - 1) {
this.updateIndex(0);
this.updatePropertyIndex(this.currentPropertyIndex + 1);
if (logging) {
console.log(this._padding(), chalk.bold.yellow(`New property: ${this.currentProperty.property}`), this._debugStatus());
console.log(this._padding(), chalk.bold.blue(`New index: ${this.currentIndex}`), this._debugStatus());
}
} else {
this.levelUp();
}
},
levelUp: function levelUp() {
if (this.currentLevel > 1) {
this.currentLevel -= 1;
this.currentNode = this.nodes[this.currentLevel];
this.updateIndex(this.indexes[this.currentLevel]);
this.updatePropertyIndex(this.propertyIndexes[this.currentLevel]);
if (logging) {
console.log(this._padding(), chalk.bold.green(`New level: ${this.currentLevel}`), this._debugStatus());
}
this.nextIndex();
} else {
this.emit("exitNode", tree);
this.done = true;
}
},
levelDown: function levelDown(node) {
let nestedProperties = findNestedProperties(node);
if (nestedProperties.length > 0) {
this.currentLevel += 1;
this.nodes[this.currentLevel] = node;
this.currentNode = node;
this.properties[this.currentLevel] = nestedProperties;
this.updateIndex(0);
this.updatePropertyIndex(0);
if (logging) {
console.log(this._padding(), chalk.bold.green(`New level: ${this.currentLevel}`), this._debugStatus());
console.log(this._padding(), chalk.bold.yellow(`New property: ${this.currentProperty.property}`), this._debugStatus());
console.log(this._padding(), chalk.bold.blue(`New index: ${this.currentIndex}`), this._debugStatus());
}
} else {
this.nextIndex();
}
},
updateIndex: function updateIndex(index) {
this.currentIndex = index;
this.indexes[this.currentLevel] = index;
},
updatePropertyIndex: function updatePropertyIndex(index) {
this.currentPropertyIndex = index;
this.propertyIndexes[this.currentLevel] = index;
this.currentProperty = this.properties[this.currentLevel][index];
let newStack;
if (this.currentProperty.type === "array") {
newStack = this.currentNode[this.currentProperty.property];
} else if (this.currentProperty.type === "node") {
newStack = [ this.currentNode[this.currentProperty.property] ];
} else {
throw new Error("Unrecognized property type");
}
this.stacks[this.currentLevel] = newStack;
this.currentStack = newStack;
}
});
};

@ -0,0 +1,27 @@
'use strict';
const React = require("react");
const reactHotLoader = require("react-hot-loader");
const createReactClass = require("create-react-class");
const esprima = require("esprima");
const AstView = require("./components/ast-view");
const processAst = require("../ast/process");
module.exports = reactHotLoader.hot(module)(createReactClass({
displayName: "App",
getInitialState: function () {
let ast = esprima.parseScript(require("raw-loader!../testcases/abbrev.js"));
processAst(ast);
return {
ast: ast
};
},
render: function () {
return (<div>
<AstView ast={this.state.ast} />
</div>);
}
}));

@ -0,0 +1,40 @@
'use strict';
/* This is a workaround for handling circular dependencies in CommonJS. */
// module.exports = function createDependencyContainer(cb) {
// let api = {
// _dependencies: new Map(),
// _moduleContents: undefined,
// get: function getModuleContents () {
// return this._moduleContents;
// },
// set: function setDependency(name, dependency) {
// this._dependencies.set(name, dependency);
// }
// };
//
// let getDependency = function getDependency(name) {
// return api._dependencies.get(name);
// };
//
// api._moduleContents = cb(getDependency);
//
// return api;
// };
module.exports = function createDependencyContainer() {
return {
_dependencies: new Map(),
get: function getDependency(name) {
if (this._dependencies.has(name)) {
return this._dependencies.get(name);
} else {
throw new Error(`No dependency named '${name}' exists in this dependency container`);
}
},
set: function setDependency(name, contents) {
return this._dependencies.set(name, contents);
}
};
};

@ -0,0 +1,538 @@
'use strict';
const React = require("react");
const createReactClass = require("create-react-class");
/* FIXME: Replace all inline simpleness determinations with calls to .isSimple */
function stringifyNode(node) {
if (node.type === "Identifier") {
return node.name;
} else if (node.type === "AssignmentPattern") {
return `${stringifyNode(node.left)} = ${stringifyNode(node.right)}`;
} else if (node.type === "Literal") {
return node.raw;
} else if (node.type === "MemberExpression") {
/* FIXME: Handle dynamic properties. */
if (node.computed === true) {
return `${stringifyNode(node.object)}[${stringifyNode(node.property)}]`;
} else {
return `${stringifyNode(node.object)}.${stringifyNode(node.property)}`;
}
} else if (node.type === "ThisExpression") {
return "this";
} else if (node.type === "ContinueStatement") {
return "continue";
} else if (node.type === "LogicalExpression" || node.type === "BinaryExpression") {
return `${stringifyNode(node.left)} ${node.operator} ${stringifyNode(node.right)}`;
} else if (node.type === "UnaryExpression" || node.type === "UpdateExpression") {
/* FIXME: Add spacing for typeof etc. but not for symbol operators like ++? */
if (node.prefix === true) {
return `${node.operator} ${stringifyNode(node.argument)}`;
} else {
return `${stringifyNode(node.argument)} ${node.operator}`;
}
} else if (node.type === "CallExpression") {
return `${stringifyNode(node.callee)}(${node.arguments.map((arg) => stringifyNode(arg)).join(", ")})`;
} else if (node.type === "ObjectExpression") {
if (node.properties.length > 0) {
throw new Error("Can only stringify zero-property object literals");
} else {
return "{}";
}
} else if (node.type === "ArrayExpression") {
if (node.elements.length > 0) {
throw new Error("Can only stringify zero-element array literals");
} else {
return "[]";
}
} else if (node.type === "ConditionalExpression") {
return `(${stringifyNode(node.test)} ? ${stringifyNode(node.consequent)} : ${stringifyNode(node.alternate)})`;
} else if (node.type === "AssignmentExpression") {
/* FIXME: Other assignment operators - does that include things like += ? */
return `${stringifyNode(node.left)} = ${stringifyNode(node.right)}`;
} else {
throw new Error(`Unrecognized node type: ${node.type}`);
}
}
function collectNestedExpressions(root, type, attribute) {
let collected = [ root ];
let last = root;
while (last[attribute] != null && last[attribute].type === type) {
collected.push(last[attribute]);
last = last[attribute];
}
return [collected, last];
}
module.exports = function ({dependencies}) {
let FunctionDeclarationItem = createReactClass({
displayName: "FunctionDeclarationItem",
render: function () {
let GenericAstItem = dependencies.get("GenericAstItem");
let AstItem = dependencies.get("AstItem");
if (this.props.data.id == null || this.props.data.id.type !== "Identifier") {
return (<GenericAstItem {...this.props} />);
} else {
let functionName = this.props.data.id.name;
let stringifiedParameters = this.props.data.params.map((param) => {
return stringifyNode(param);
});
let itemProps = {
abbreviated: true,
hasFunctionScope: true, // TODO: Should this be hardcoded like this?
hasBlockScope: false,
type: this.props.data.type,
preview: `function ${functionName} (${stringifiedParameters.join(", ")})`,
scopeId: this.props.data._scopeId,
scopeType: "function",
children: {
body: this.props.data.body
}
};
return (
<AstItem {...itemProps} data={this.props.data} />
);
}
}
});
let CallExpressionItem = createReactClass({
displayName: "CallExpressionItem",
render: function () {
let AstItem = dependencies.get("AstItem");
let showArguments = false;
let stringifiedArguments = this.props.data.arguments.map((arg) => {
try {
return stringifyNode(arg);
} catch (err) {
/* FIXME: Either catch specific error, or use a .isSimpleNode method on AST nodes to determine whether we can stringify this call expression */
showArguments = true;
return `<${arg.type}>`;
}
});
let itemProps = {
abbreviated: true,
type: this.props.data.type,
preview: `${stringifyNode(this.props.data.callee)}(${stringifiedArguments.join(", ")})`,
childSequences: {
arguments: (showArguments === true ? this.props.data.arguments : null)
}
};
return (
<AstItem {...itemProps} data={this.props.data} />
);
}
});
let MemberExpressionItem = createReactClass({
displayName: "MemberExpressionItem",
render: function () {
let AstItem = dependencies.get("AstItem");
let GenericAstItem = dependencies.get("GenericAstItem");
/* The below is for handling recursive MemberExpressions, eg. for foo.bar.baz */
/* FIXME: How to handle the 'computed' property? */
// let [collected, last] = collectNestedExpressions(this.props.data, "MemberExpression", "object");
let hasNonLiteralProperty = (this.props.data.property.type !== "Identifier");
let hasNonLiteralObject = (this.props.data.object.type !== "Identifier");
if (hasNonLiteralObject || hasNonLiteralProperty) {
return (<GenericAstItem {...this.props} />);
} else {
let itemProps = {
abbreviated: true,
type: this.props.data.type,
preview: stringifyNode(this.props.data)
};
return (
<AstItem {...itemProps} data={this.props.data} />
);
}
}
});
let LiteralItem = createReactClass({
displayName: "LiteralItem",
render: function () {
let AstItem = dependencies.get("AstItem");
let itemProps = {
abbreviated: true,
type: this.props.data.type,
preview: stringifyNode(this.props.data)
};
return (
<AstItem {...itemProps} data={this.props.data} />
);
}
});
let ObjectPropertyItem = createReactClass({
displayName: "ObjectPropertyItem",
render: function () {
let AstItem = dependencies.get("AstItem");
let GenericAstItem = dependencies.get("GenericAstItem");
if (this.props.data.computed === true || this.props.data.method === true || this.props.data.shorthand === true) {
return (<GenericAstItem {...this.props} />);
} else {
let preview, complexValue;
if (this.props.data.value.type === "Literal") {
preview = `${stringifyNode(this.props.data.key)}: ${this.props.data.value.raw}`;
complexValue = null;
} else {
preview = `${stringifyNode(this.props.data.key)}: <${this.props.data.value.type}>`;
complexValue = this.props.data.value;
}
let itemProps = {
abbreviated: true,
type: this.props.data.type,
preview: preview,
children: {
value: complexValue
}
};
return (
<AstItem {...itemProps} data={this.props.data} />
);
}
}
});
let VariableDeclaratorItem = createReactClass({
displayName: "VariableDeclaratorItem",
render: function () {
let GenericAstItem = dependencies.get("GenericAstItem");
let AstItem = dependencies.get("AstItem");
if (this.props.data.id.type !== "Identifier") {
return (<GenericAstItem {...this.props} />);
} else {
let preview;
let simpleValue = false;
if (this.props.data.init != null) {
try {
let stringifiedValue = stringifyNode(this.props.data.init);
simpleValue = true;
preview = `${this.props.data._kind} ${this.props.data.id.name} = ${stringifiedValue}`;
} catch (err) {
/* FIXME: Error type */
preview = `${this.props.data._kind} ${this.props.data.id.name} = <${this.props.data.init.type}>`;
}
} else {
preview = `${this.props.data._kind} ${this.props.data.id.name}`;
}
let itemProps = {
abbreviated: true,
type: this.props.data.type,
preview: preview,
children: {
init: (simpleValue === false) ? this.props.data.init : null
}
};
return (
<AstItem {...itemProps} data={this.props.data} />
);
}
}
});
let VariableDeclarationItem = createReactClass({
displayName: "VariableDeclarationItem",
render: function () {
let AstItem = dependencies.get("AstItem");
let itemProps = {
abbreviated: true,
type: this.props.data.type,
// preview: `(${this.props.data.kind})`,
childSequences: {
declarations: this.props.data.declarations
}
};
return (
<AstItem {...itemProps} data={this.props.data} />
);
}
});
let AssignmentExpressionItem = createReactClass({
displayName: "AssignmentExpressionItem",
render: function () {
let AstItem = dependencies.get("AstItem");
let preview, simpleValue, leftHand, rightHand, collectedAssignments;
if (this.props.data.right.type === "AssignmentExpression") {
/* We want to collapse multiple nested AssignmentExpressions into one (eg. `foo = bar = baz`) */
let [collected, last] = collectNestedExpressions(this.props.data, "AssignmentExpression", "right");
leftHand = collected.map((node) => {
return stringifyNode(node.left);
}).join(" = ");
rightHand = last.right;
collectedAssignments = collected;
} else {
leftHand = stringifyNode(this.props.data.left);
rightHand = this.props.data.right;
}
if (rightHand.isSimple()) {
preview = `${leftHand} = ${stringifyNode(rightHand)}`;
simpleValue = true;
} else {
preview = `${leftHand} = <${rightHand.type}>`;
simpleValue = false;
}
let itemProps = {
abbreviated: true,
type: "AssignmentExpression (nested)",
preview: preview,
children: {
value: (simpleValue === false ? this.props.data.right : null)
},
childSequences: {
assignments: (collectedAssignments != null ? collectedAssignments.map((node) => node.left) : null)
}
};
return (
<AstItem {...itemProps} data={this.props.data} />
);
}
});
let ObjectExpressionItem = createReactClass({
displayName: "ObjectExpressionItem",
render: function () {
let AstItem = dependencies.get("AstItem");
let stringifiedProperties = this.props.data.properties.map((property) => {
if (property.computed === true) {
/* FIXME */
return "[computed]";
} else if (property.value.isSimple()) {
return `${stringifyNode(property.key)}: ${property.value.raw}`;
} else {
return stringifyNode(property.key);
}
});
let propertyPreviewLimit = 3;
let shortenedStringifiedProperties;
if (stringifiedProperties.length > propertyPreviewLimit) {
shortenedStringifiedProperties = stringifiedProperties.slice(0, propertyPreviewLimit).concat([`(... ${stringifiedProperties.length - propertyPreviewLimit} more)`]);
} else {
shortenedStringifiedProperties = stringifiedProperties;
}
let itemProps = {
abbreviated: true,
type: this.props.data.type,
preview: `{ ${shortenedStringifiedProperties.join(", ")} }`,
childSequences: {
properties: this.props.data.properties
}
};
return (
<AstItem {...itemProps} data={this.props.data} />
);
}
});
let FixedKeywordItem = createReactClass({
displayName: "FixedKeywordItem",
render: function () {
let AstItem = dependencies.get("AstItem");
let itemProps = {
abbreviated: true,
type: this.props.data.type,
preview: stringifyNode(this.props.data)
};
return (
<AstItem {...itemProps} data={this.props.data} />
);
}
});
let OperatorExpressionItem = createReactClass({
displayName: "OperatorExpressionItem",
render: function () {
let AstItem = dependencies.get("AstItem");
let GenericAstItem = dependencies.get("GenericAstItem");
try {
let itemProps = {
abbreviated: true,
type: this.props.data.type,
preview: stringifyNode(this.props.data)
};
return (
<AstItem {...itemProps} data={this.props.data} />
);
} catch (err) {
/* FIXME: Error type */
return (<GenericAstItem {...this.props} />);
}
}
});
let IfStatementItem = createReactClass({
displayName: "IfStatementItem",
render: function () {
let AstItem = dependencies.get("AstItem");
let GenericAstItem = dependencies.get("GenericAstItem");
if (this.props.data.test.isSimple()) {
let stringifiedTest = stringifyNode(this.props.data.test);
let itemProps = {
abbreviated: true,
type: this.props.data.type,
preview: `if (${stringifiedTest}) ...`,
children: {
consequent: this.props.data.consequent,
/* FIXME: else-if chains */
alternate: this.props.data.alternate
}
};
return (
<AstItem {...itemProps} data={this.props.data} />
);
} else {
return (<GenericAstItem {...this.props} />);
}
}
});
let ReturnStatementItem = createReactClass({
displayName: "ReturnStatementItem",
render: function () {
let AstItem = dependencies.get("AstItem");
let GenericAstItem = dependencies.get("GenericAstItem");
if (this.props.data.isSimple()) {
let itemProps = {
abbreviated: true,
type: this.props.data.type,
preview: `return ${stringifyNode(this.props.data.argument)}`
};
return (
<AstItem {...itemProps} data={this.props.data} />
);
} else {
return (<GenericAstItem {...this.props} />);
}
}
});
let ExpressionStatementItem = createReactClass({
displayName: "ExpressionStatementItem",
render: function () {
let AstItem = dependencies.get("AstItem");
let GenericAstItem = dependencies.get("GenericAstItem");
if (this.props.data.isSimple()) {
let itemProps = {
abbreviated: true,
type: this.props.data.type,
preview: stringifyNode(this.props.data.expression)
};
return (
<AstItem {...itemProps} data={this.props.data} />
);
} else {
return (<GenericAstItem {...this.props} />);
}
}
});
let IdentifierItem = createReactClass({
displayName: "IdentifierItem",
render: function () {
let AstItem = dependencies.get("AstItem");
let itemProps = {
abbreviated: true,
type: this.props.data.type,
preview: this.props.data.name
};
return (
<AstItem {...itemProps} data={this.props.data} />
);
}
});
let abbreviatedTypeMap = {
FunctionDeclaration: FunctionDeclarationItem,
FunctionExpression: FunctionDeclarationItem,
MemberExpression: MemberExpressionItem,
Identifier: IdentifierItem,
Literal: LiteralItem,
Property: ObjectPropertyItem,
CallExpression: CallExpressionItem,
VariableDeclarator: VariableDeclaratorItem,
VariableDeclaration: VariableDeclarationItem,
AssignmentExpression: AssignmentExpressionItem,
ObjectExpression: ObjectExpressionItem,
ThisExpression: FixedKeywordItem,
ContinueStatement: FixedKeywordItem,
IfStatement: IfStatementItem,
ConditionalExpression: IfStatementItem,
UpdateExpression: OperatorExpressionItem,
UnaryExpression: OperatorExpressionItem,
BinaryExpression: OperatorExpressionItem,
LogicalExpression: OperatorExpressionItem,
ReturnStatement: ReturnStatementItem,
ExpressionStatement: ExpressionStatementItem
};
dependencies.set("AbbreviatedAstItem", createReactClass({
displayName: "AbbreviatedAstItem",
render: function () {
let GenericAstItem = dependencies.get("GenericAstItem");
if (abbreviatedTypeMap[this.props.data.type] != null) {
let AbbreviatedItemType = abbreviatedTypeMap[this.props.data.type];
return (<AbbreviatedItemType {...this.props} />);
} else {
return (<GenericAstItem {...this.props} />);
}
}
}));
};

@ -0,0 +1,19 @@
'use strict';
const React = require("react");
const createReactClass = require("create-react-class");
module.exports = createReactClass({
displayName: "AstItemAttributeSeries",
render: function () {
let Item = this.props.itemType;
return (
<div className={this.props.className}>
{Object.keys(this.props.items).map((key) => {
return (<Item key={key} itemKey={key} value={this.props.items[key]} />);
})}
</div>
);
}
});

@ -0,0 +1,37 @@
'use strict';
const React = require("react");
const createReactClass = require("create-react-class");
const filterAttributes = require("../../../ast/node/filter-attributes");
const filterNodes = require("../../../ast/node/filter-nodes");
const filterNodeSequences = require("../../../ast/node/filter-node-sequences");
const dependencies = require("../../circular-dependency-container")();
require("./sub-items/child")({dependencies});
require("./sub-items/child-sequence")({dependencies});
require("./item")({dependencies});
let GenericAstItem = createReactClass({
displayName: "GenericAstItem",
render: function () {
let AstItem = dependencies.get("AstItem");
let itemProps = {
hasFunctionScope: (this.props.data._scopeType === 1),
hasBlockScope: (this.props.data._scopeType === 2),
type: this.props.data.type,
scopeId: this.props.data._scopeId,
scopeType: (this.props.data._scopeType === 1 ? "function" : "block"),
attributes: filterAttributes(this.props.data),
children: filterNodes(this.props.data),
childSequences: filterNodeSequences(this.props.data)
};
return (<AstItem {...itemProps} data={this.props.data} />);
}
});
dependencies.set("GenericAstItem", GenericAstItem);
module.exports = GenericAstItem;

@ -0,0 +1,15 @@
'use strict';
const React = require("react");
const createReactClass = require("create-react-class");
const GenericAstItem = require("./generic-item");
module.exports = createReactClass({
displayName: "AstView",
render: function () {
return (
<GenericAstItem data={this.props.ast} />
);
}
});

@ -0,0 +1,69 @@
'use strict';
const React = require("react");
const createReactClass = require("create-react-class");
const classnames = require("classnames");
const filterValues = require("filter-values");
const renderIf = require("../../render-if");
const AstItemAttributeSeries = require("./attribute-series");
const AstItemAttribute = require("./sub-items/attribute");
function normalizeItemAttributes(attributes) {
if (attributes == null) {
return {};
} else {
return filterValues(attributes, (value) => {
return (value != null);
});
}
}
module.exports = function ({dependencies}) {
dependencies.set("AstItem", createReactClass({
displayName: "AstItem",
getInitialState: function () {
return {
collapsed: false
};
},
toggleCollapsed: function () {
this.setState({
collapsed: !this.state.collapsed
});
},
render: function () {
let attributes = normalizeItemAttributes(this.props.attributes);
let children = normalizeItemAttributes(this.props.children);
let childSequences = normalizeItemAttributes(this.props.childSequences);
let hasContents = (Object.keys(attributes).length > 0 || Object.keys(children).length > 0 || Object.keys(childSequences).length > 0);
return (
<div className={classnames("astItem", {hasFunctionScope: this.props.hasFunctionScope, hasBlockScope: this.props.hasBlockScope})}>
{renderIf(hasContents, <span className="arrow" onClick={this.toggleCollapsed}>
{this.state.collapsed ? "▶" : "▼"}
</span>)}
<span className="type" onDoubleClick={this.toggleCollapsed}>
{renderIf(this.props.abbreviated === true, "✂ ")}
{this.props.type}
{renderIf(this.props.preview != null, <span className="preview">{this.props.preview}</span>)}
</span>
{renderIf(this.props.scopeId != null, <span className="scopeId">
Scope ID: {this.props.scopeId} [type: {this.props.scopeType}]
</span>)}
{/*(simple: {String(this.props.data.isSimple())})*/}
{renderIf(this.state.collapsed === false, <span>
<AstItemAttributeSeries className="attributes" itemType={AstItemAttribute} items={attributes} />
<AstItemAttributeSeries className="children" itemType={dependencies.get("AstItemChild")} items={children} />
<AstItemAttributeSeries className="childSequences" itemType={dependencies.get("AstItemChildSequence")} items={childSequences} />
</span>)}
</div>
);
}
}));
};

@ -0,0 +1,34 @@
'use strict';
const React = require("react");
const createReactClass = require("create-react-class");
function debugValue(value) {
let stringifiedValue = `${value}`;
if (typeof value === "boolean") {
return (<span className="boolean">{stringifiedValue}</span>);
} else if (typeof value === "number") {
return (<span className="number">{stringifiedValue}</span>);
} else if (typeof value === "string") {
return (<span className="string">{value}</span>);
} else if (value === null) {
return (<span className="null">null</span>);
} else if (value === undefined) {
return (<span className="null">undefined</span>);
} else {
throw new Error(`Unrecognized attribute value type`, value);
} /* FIXME: array, object */
}
module.exports = createReactClass({
displayName: "AstItemAttribute",
render: function () {
return (
<div key={this.props.itemKey} className="attribute">
<span className="name">{this.props.itemKey}</span>
<span className="value">{debugValue(this.props.value)}</span>
</div>
);
}
});

@ -0,0 +1,31 @@
'use strict';
const React = require("react");
const createReactClass = require("create-react-class");
module.exports = function ({dependencies}) {
require("../abbreviated-item")({dependencies});
dependencies.set("AstItemChildSequence", createReactClass({
displayName: "AstItemChildSequence",
render: function () {
const AbbreviatedAstItem = dependencies.get("AbbreviatedAstItem");
return (
<div key={this.props.itemKey} className="child">
<span className="name">{this.props.itemKey}</span>
<div className="items">
{this.props.value.map((item, i) => {
return (
<span key={`${this.props.itemKey}:${i}`} className="item">
{/* FIXME: The way the key is generated here, may break rendering after AST mutations. */}
<AbbreviatedAstItem data={item} />
</span>
);
})}
</div>
</div>
);
}
}));
};

@ -0,0 +1,26 @@
'use strict';
const React = require("react");
const createReactClass = require("create-react-class");
module.exports = function ({dependencies}) {
require("../abbreviated-item")({dependencies});
dependencies.set("AstItemChild", createReactClass({
displayName: "AstItemChild",
render: function () {
let AbbreviatedAstItem = dependencies.get("AbbreviatedAstItem");
return (
<div key={this.props.itemKey} className="child">
<span className="name">{this.props.itemKey}</span>
<div className="items">
<span key={`${this.props.itemKey}`} className="item">
<AbbreviatedAstItem data={this.props.value} />
</span>
</div>
</div>
);
}
}));
};

@ -0,0 +1,15 @@
'use strict';
const Promise = require("bluebird");
const documentReadyPromise = require("document-ready-promise");
const React = require("react");
const ReactDOM = require("react-dom");
const App = require("./app");
Promise.try(() => {
return documentReadyPromise();
}).then(() => {
console.log("Foo Bar");
ReactDOM.render(<App />, document.getElementById("app"));
});

@ -0,0 +1,7 @@
'use strict';
module.exports = function renderIf(condition, content) {
if (condition) {
return content;
}
};

@ -0,0 +1,142 @@
body {
font-family: sans-serif;
}
.astItem {
&.hasBlockScope {
background: linear-gradient(rgb(231, 225, 211), transparent 64px);
}
&.hasFunctionScope {
background: linear-gradient(rgb(221, 211, 231), transparent 64px);
}
.arrow, .type {
cursor: default;
user-select: none;
-webkit-user-select: none;
-mozilla-user-select: none;
}
.type {
font-weight: bold;
color: rgb(43, 29, 130);
& > .preview {
font-weight: normal;
margin-left: 8px;
margin-right: 8px;
color: rgb(102, 20, 2);
}
}
.scopeId {
display: inline-block;
font-weight: bold;
font-size: 13px;
margin-left: 5px;
padding: 1px 4px;
background-color: rgb(211, 231, 221);
border: 1px solid rgb(76, 161, 117);
border-radius: 5px;
}
.attributes, .children, .childSequences {
margin-left: 20px;
}
.children, .childSequences {
.child {
& > .name {
font-weight: bold;
border-bottom: 1px solid;
}
.items {
padding-left: 2px;
border-left: 1px solid;
}
}
}
.childSequences > .child {
// $lineColor: rgb(12, 101, 14);
$lineColor: rgb(69, 3, 3);
& > .name {
border-bottom-color: $lineColor;
&:after {
margin-left: 5px;
font-size: 14px;
color: $lineColor;
content: "(array)"
}
}
& > .items {
border-left-color: $lineColor;
}
}
.children > .child {
$lineColor: rgb(19, 40, 144);
& > .name {
border-bottom-color: $lineColor;
}
& > .items {
border-left-color: $lineColor;
}
}
.attributes {
font-size: 14px;
.attribute {
display: inline-block;
&:not(:last-child):after {
content: ",";
margin-right: 8px;
}
.name {
font-weight: bold;
margin-right: 5px;
&:after {
content: ":";
}
}
.value {
background-color: rgb(242, 242, 242);
padding: 0px 3px;
border: 1px solid silver;
font-family: "monospace";
.boolean {
color: rgb(62, 0, 41);
}
.number {
color: rgb(68, 18, 0)
}
.string {
color: rgb(19, 46, 0);
&:before, &:after {
content: "\"";
}
}
.null {
color: gray;
}
}
}
}
}

@ -0,0 +1,61 @@
module.exports = exports = abbrev.abbrev = abbrev
abbrev.monkeyPatch = monkeyPatch
function monkeyPatch () {
Object.defineProperty(Array.prototype, 'abbrev', {
value: function () { return abbrev(this) },
enumerable: false, configurable: true, writable: true
})
Object.defineProperty(Object.prototype, 'abbrev', {
value: function () { return abbrev(Object.keys(this)) },
enumerable: false, configurable: true, writable: true
})
}
function abbrev (list) {
if (arguments.length !== 1 || !Array.isArray(list)) {
list = Array.prototype.slice.call(arguments, 0)
}
for (var i = 0, l = list.length, args = [] ; i < l ; i ++) {
args[i] = typeof list[i] === "string" ? list[i] : String(list[i])
}
// sort them lexicographically, so that they're next to their nearest kin
args = args.sort(lexSort)
// walk through each, seeing how much it has in common with the next and previous
var abbrevs = {}
, prev = ""
for (var i = 0, l = args.length ; i < l ; i ++) {
var current = args[i]
, next = args[i + 1] || ""
, nextMatches = true
, prevMatches = true
if (current === next) continue
for (var j = 0, cl = current.length ; j < cl ; j ++) {
var curChar = current.charAt(j)
nextMatches = nextMatches && curChar === next.charAt(j)
prevMatches = prevMatches && curChar === prev.charAt(j)
if (!nextMatches && !prevMatches) {
j ++
break
}
}
prev = current
if (j === cl) {
abbrevs[current] = current
continue
}
for (var a = current.substr(0, j) ; j <= cl ; j ++) {
abbrevs[a] = current
a += current.charAt(j)
}
}
return abbrevs
}
function lexSort (a, b) {
return a === b ? 0 : a > b ? 1 : -1
}

@ -0,0 +1,13 @@
'use strict';
function doThing(someArg) {
console.log(someArg);
var someArg = 42;
console.log(someArg);
var foo = someArg;
var someArg = 43;
console.log(foo);
console.log(someArg);
}
doThing(41);

@ -0,0 +1,47 @@
'use strict';
let insideArrowFunction = function insideArrowFunctionName(array) {
console.log(array);
}
function someFunction(array) {
console.log(array);
array.map((array) => {
let letItem = array;
var varItem = array;
if (array === 3) {
let letItem = array + 1;
var varItem = array + 1;
console.log("incremented", letItem, varItem);
} else {
for (let i = 0; i < 1; i++) {
console.log("count", i);
}
console.log("final", i);
try {
console.log("try section");
throw new Error("some error");
} catch (err) {
console.log("error section", err);
}
eval("console.log('direct eval');");
let indirectEval = eval;
indirectEval("console.log('indirect eval');");
}
console.log("original", letItem, varItem);
insideArrowFunction(array);
});
}
array = [1, 2, 3];
someFunction(array);

@ -0,0 +1,24 @@
function doTheThing(item = 42) {
item.stuff.makeNoise("hello");
}
function makeTheThing(noisy) {
let empty;
let obj = {
exist: true
};
if (noisy) {
obj.makeNoise = function makeNoise(noise) {
console.log(noise);
}
}
return obj;
}
let thingOne = makeTheThing(true);
let thingTwo = makeTheThing(false);
doTheThing(thingOne);
doTheThing(thingTwo);

@ -0,0 +1,42 @@
AHACAT - Advanced High-Accuracy Code Analysis Toolkit
parsing
scope tracking - tie particular variables to particular identifiable scopess
reassignment tracking - indicate which 'generation' of a variable a particular reference belongs to
in cases where the generation is ambiguous because it eg. depends on the call time (which should probably be a warning!), indicate this as well by specifying the 'generation coverage', ie. the possible generations that it *could* be referring to
deterministic function identifiers, by hashing the function's contents?
recursive hashing of functions, like Nix does?
trait generation based on property assignment codepaths
track property conflicts for objects that have multiple values for the same property
make explicit property checks exclude an object from trait validation? or is this inherently allowed in the required trait signature caused by the property check?
(consider branching, both for trait definitions and usage!)
special cases with scoping/references:
* direct vs indirect eval
* in non-strict mode, eval modifies the scope it's called in
* in non-strict mode, `arguments` is tied to the assigned parameter names in the function definition; changing one changes the other
* composite types (made up of traits)
* traits (made up of sets of properties)
* properties (name + type)
* types (primitive types, composite types, or function types)
* function types (each function is its own type - how to deal with different call signatures?)
how to deal with deduplication of type-identical traits/interfaces that are defined in different places?
how to do this in the context of recursion?
how to approach this in the context of cyclic references between the to-be-deduplicated types?
mark a function as exported when:
* it is made available in the global scope
* it is exported through a module system (`module.exports`, `export`, ...)
* it is exposed as a function object directly from the module, eg.:
* as a return value from another exported function
* as a method of an object that is returned from some other (directly or indirectly) exported function
* accessible, directly or indirectly, through an argument to a callback that is provided externally from the module (eg. hooks)
an exported function can, for example, never be eliminated as 'dead code', unless *all* of the possible call sites (ie. every place where it is directly or indirectly made accessible through an 'import' of some sort) have been analyzed, and determined not to use it
* Dead code detection; distinction between 'in isolation' and 'in context' because the former is useful for error detection whereas both are useful for eg. bundle optimization
* Detect dead code in isolation; functions (and objects of functions?) that are neither exported, nor accessed indirectly through anything that *is* exported, nor called upon module load
* Detect dead code in context; functions that are exported but never called. Possibly other non-accessed data as well?

@ -0,0 +1,20 @@
'use strict';
const path = require("path");
const webpackPresetLoader = require("webpack-preset-loader");
module.exports = webpackPresetLoader([
require("@joepie91/webpack-preset-hot-reload")(),
require("@joepie91/webpack-preset-babel")({
supportReact: true
}),
require("@joepie91/webpack-preset-simple-bundle")({
source: path.join(__dirname, "src/frontend/index.jsx"),
destinationFolder: path.join(__dirname, "public/static/")
})
], {
watch: true,
mode: "development",
devtool: "source-map"
});
Loading…
Cancel
Save