Progress so far
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" ],
|
||||
}
|
||||
};
|
@ -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"]);
|
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…
Reference in New Issue