New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

incinerator

Package Overview
Dependencies
Maintainers
1
Versions
4
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

incinerator - npm Package Compare versions

Comparing version 0.1.1 to 0.2.0

4

example/package.json

@@ -7,4 +7,4 @@ {

"test": "echo no test",
"build": "webpack",
"dev": "webpack --watch"
"build": "webpack --mode production src/index.js",
"dev": "webpack --watch --mode production src/index.js"
},

@@ -11,0 +11,0 @@ "license": "ISC",

let { resolve } = require("path");
let {
readFileSync: readFile,
writeFileSync: writeFile,
readdirSync: readdir,
statSync: stat
} = require("fs");
let { readFile, writeFile, readdir, stat } = require("fs-extra");
let parser = require("@babel/parser");

@@ -13,2 +8,3 @@ let traverse = require("@babel/traverse").default;

let ws = require("ws");
let chalkAnimation = require("chalk-animation");

@@ -18,29 +14,14 @@ let rootDir = resolve(process.argv[2] || "");

function iterateSourceFiles(dir, callback) {
readdir(dir)
.map(file => resolve(dir, file))
.forEach(file => {
if (stat(file).isDirectory()) {
iterateSourceFiles(file, callback);
} else {
callback(file);
}
});
async function iterateSourceFiles(root, callback) {
let fileStat = await stat(root);
if (fileStat.isDirectory()) {
let files = await readdir(root);
return Promise.all(
files.map(file => iterateSourceFiles(resolve(root, file), callback))
);
} else {
await callback(root);
}
}
let jsFiles = [];
iterateSourceFiles(rootDir, file => {
let source = readFile(file, "utf-8");
let ast = null;
try {
ast = parser.parse(source);
} catch (err) {
if (err.name !== "SyntaxError") throw err;
}
if (ast) {
jsFiles.push({ file, ast });
}
});
function toAST(code) {

@@ -90,35 +71,39 @@ let ast = parser.parse(code);

function writeAST(path, ast) {
writeFile(path, generate(ast).code);
async function writeAST(path, ast) {
await writeFile(path, generate(ast).code + "\n");
}
let functionId = 0;
let functionPathMap = new Map();
async function confirmIncineration() {
let stdin;
await new Promise(resolve => {
stdin = process.openStdin();
stdin.addListener("data", data => {
let str = data.toString();
if (str.trim().toLowerCase() === "incinerate!") {
resolve();
} else if (str.includes("!")) {
console.log("Well, anyway I'll incinerate!");
resolve();
} else {
process.stdout.write("> ");
}
});
let wss = new ws.Server({ port });
wss.on("connection", ws => {
ws.on("message", msg => {
functionPathMap.delete(parseInt(msg, 10));
process.stdout.write("Waiting for 'incinerate!'\n> ");
});
});
stdin.end();
}
jsFiles.forEach(({ file, ast }) => {
removeTaggings(ast);
function removeUnusedVars(ast) {
// referencePaths are cached, so create new ast
ast = parser.parse(generate(ast).code);
// add new taggings
let topLevelDecls = [];
traverse(ast, {
Function(path) {
VariableDeclarator(path) {
if (
path.node.id &&
path.node.id.name.startsWith(incineratorFunctionPrefix)
t.isIdentifier(path.get("id")) &&
t.isProgram(path.parentPath.parentPath)
) {
// skip
} else {
let id = functionId++;
functionPathMap.set(id, path);
let body = path.get("body");
if (t.isBlock(body)) {
body.unshiftContainer("body", tagging(id));
}
topLevelDecls.push(path);
}

@@ -128,21 +113,87 @@ }

writeAST(file, ast);
});
let pathsToRemove = [];
topLevelDecls.forEach(path => {
let name = path.node.id.name;
let scope = path.findParent(t.isProgram).scope;
if (scope.bindings[name].referencePaths.length === 0) {
pathsToRemove.push(path);
}
});
let stdin = process.openStdin();
stdin.addListener("data", data => {
let str = data.toString();
if (str.includes("!")) {
incinerate();
// do until there's no unused vars
if (pathsToRemove.length) {
pathsToRemove.forEach(path => path.remove());
return removeUnusedVars(ast);
} else {
return ast;
}
});
}
console.log("Waiting for 'incinerate!'...");
async function main() {
let jsFiles = [];
await iterateSourceFiles(rootDir, async file => {
let source = await readFile(file, "utf-8");
let ast = null;
try {
ast = parser.parse(source);
} catch (err) {
if (err.name !== "SyntaxError") throw err;
}
function incinerate() {
if (ast) {
jsFiles.push({ file, ast });
}
});
let functionId = 0;
let functionPathMap = new Map();
let wss = new ws.Server({ port });
wss.on("connection", ws => {
ws.on("message", msg => {
functionPathMap.delete(parseInt(msg, 10));
});
});
await Promise.all(
jsFiles.map(async ({ file, ast }) => {
removeTaggings(ast);
// add new taggings
traverse(ast, {
Function(path) {
if (
path.node.id &&
path.node.id.name.startsWith(incineratorFunctionPrefix)
) {
// skip
} else {
let id = functionId++;
functionPathMap.set(id, path);
let body = path.get("body");
if (t.isBlock(body)) {
body.unshiftContainer("body", tagging(id));
}
}
}
});
await writeAST(file, ast);
})
);
await confirmIncineration();
let text = chalkAnimation.rainbow("\nIncinerating!");
// left paths are unused, let's incinerate them!
for (let path of functionPathMap.values()) {
if (t.isFunctionDeclaration(path)) {
path.remove();
// If it's a declaration, replace with empty declaration
path.replaceWith(
t.variableDeclaration("var", [t.variableDeclarator(path.node.id)])
);
} else {
// for the others, just empty its params and body
path.node.params = [];

@@ -153,9 +204,15 @@ path.node.body = t.blockStatement([]);

jsFiles.forEach(({ file, ast }) => {
removeTaggings(ast);
writeAST(file, ast);
});
await Promise.all(
jsFiles.map(async ({ file, ast }) => {
removeTaggings(ast);
await writeAST(file, removeUnusedVars(ast));
})
);
wss.close();
stdin.end();
// show text one more sec because it's rainbow
setTimeout(() => text.stop(), 1000);
}
main();
{
"name": "incinerator",
"version": "0.1.1",
"version": "0.2.0",
"description": "Incinerate unused code!",

@@ -28,2 +28,4 @@ "homepage": "https://github.com/utatti/incinerator",

"@babel/types": "^7.0.0-beta.51",
"chalk-animation": "^1.6.0",
"fs-extra": "^6.0.1",
"ws": "^5.2.1"

@@ -30,0 +32,0 @@ },

@@ -11,3 +11,3 @@ # Incinerator

means Incinerator checks if a part of code is actually used in *runtime* and
remove unless it is. For the time being, only function blocks are checked.
removes it if unused. For the time being, only function blocks are checked.

@@ -34,12 +34,12 @@ ## Install

[![Youtube: Incinerator Demo](https://user-images.githubusercontent.com/1013641/42218553-ff288950-7f03-11e8-9a1b-999a682c36de.png)](https://youtu.be/tj1S0QQOuAM)
[![Youtube: Incinerator Demo](https://user-images.githubusercontent.com/1013641/42240220-8d0011b6-7f41-11e8-94b4-c2532bc192e4.png)](https://youtu.be/OjHFX_utqBM)
## How Incinerator works
I recommend reading [the code](index.js), as it's only about 150 line long.
I recommend reading [the code](index.js), as it's only ~200 lines long.
1. Tag every function with a unique ID
1. Check if a function is called in runtime via WebSocket
1. When finished, remove uncalled functions
1. Remove unused variables or undefined symbols manually
1. When finished, empty uncalled functions
1. Remove unused top-level variables to remove unused dependencies

@@ -46,0 +46,0 @@ Please also consider playing with an [example](example).

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc