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

defs

Package Overview
Dependencies
Maintainers
1
Versions
18
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

defs - npm Package Compare versions

Comparing version 0.4.3 to 0.5.0

defs-config.json

28

build/es5/defs-cmd.js

@@ -21,5 +21,3 @@ "use strict";

var config = tryor(function() {
return JSON.parse(String(fs.readFileSync("defs-config.json")));
}, {});
var config = findAndReadConfig();

@@ -43,1 +41,25 @@ var ret = defs(src, config);

}
function findAndReadConfig() {
var path = "";
var filename = "defs-config.json";
var filenamePath = null;
while (fs.existsSync(path || ".")) {
filenamePath = path + filename;
if (fs.existsSync(filenamePath)) {
var config = tryor(function() {
return JSON.parse(String(fs.readFileSync(filenamePath)));
}, null);
if (config === null) {
console.error("error: bad JSON in %s", filenamePath);
process.exit(-1);
}
return config;
}
path = "../" + path;
}
return {};
}

264

build/es5/defs-main.js

@@ -11,2 +11,3 @@ "use strict";

var traverse = require("ast-traverse");
var breakable = require("breakable");
var Scope = require("./scope");

@@ -214,3 +215,5 @@ var error = require("./error");

function setupReferences(ast, allIdentifiers) {
function setupReferences(ast, allIdentifiers, opts) {
var analyze = (is.own(opts, "analyze") ? opts.analyze : true);
function visit(node) {

@@ -223,7 +226,7 @@ if (!isReference(node)) {

var scope = node.$scope.lookup(node.name);
if (!scope && options.disallowUnknownReferences) {
if (analyze && !scope && options.disallowUnknownReferences) {
error(getline(node), "reference to unknown global variable {0}", node.name);
}
// check const and let for referenced-before-declaration
if (scope && is.someof(scope.getKind(node.name), ["const", "let"])) {
if (analyze && scope && is.someof(scope.getKind(node.name), ["const", "let"])) {
var allowedFromPos = scope.getFromPos(node.name);

@@ -248,5 +251,3 @@ var referencedAtPos = node.range[0];

function varify(ast, stats, allIdentifiers) {
var changes = [];
function varify(ast, stats, allIdentifiers, changes) {
function unique(name) {

@@ -330,7 +331,23 @@ assert(allIdentifiers.has(name));

changes.push({
start: node.range[0],
end: node.range[1],
str: move.name,
});
if (node.alterop) {
// node has no range because it is the result of another alter operation
var existingOp = null;
for (var i = 0; i < changes.length; i++) {
var op = changes[i];
if (op.node === node) {
existingOp = op;
break;
}
}
assert(existingOp);
// modify op
existingOp.str = move.name;
} else {
changes.push({
start: node.range[0],
end: node.range[1],
str: move.name,
});
}
}

@@ -344,40 +361,101 @@ }

}});
return changes;
}
function detectLoopClosures(node) {
// forbidden pattern:
// <any>* <loop> <non-fn>* <constlet-def> <any>* <fn> <any>* <constlet-ref>
if (isReference(node) && node.$refToScope && isConstLet(node.$refToScope.getKind(node.name))) {
// traverse nodes up towards root from var-def
// if we hit a function (before a loop) - ok!
// if we hit a loop - maybe-ouch
// if we reach root - ok!
for (var n = node.$refToScope.node; ; ) {
if (isFunction(n)) {
// we're ok (function-local)
return;
} else if (isLoop(n)) {
// not ok (between loop and function)
break;
function detectLoopClosures(ast) {
traverse(ast, {pre: visit});
function detectIifyBodyBlockers(body, node) {
return breakable(function(brk) {
traverse(body, {pre: function(n) {
// if we hit an inner function of the loop body, don't traverse further
if (isFunction(n)) {
return false;
}
var err = true; // reset to false in else-statement below
var msg = "loop-variable {0} is captured by a loop-closure that can't be transformed due to use of {1} at line {2}";
if (n.type === "BreakStatement") {
error(getline(node), msg, node.name, "break", getline(n));
} else if (n.type === "ContinueStatement") {
error(getline(node), msg, node.name, "continue", getline(n));
} else if (n.type === "ReturnStatement") {
error(getline(node), msg, node.name, "return", getline(n));
} else if (n.type === "Identifier" && n.name === "arguments") {
error(getline(node), msg, node.name, "arguments", getline(n));
} else if (n.type === "VariableDeclaration" && n.kind === "var") {
error(getline(node), msg, node.name, "var", getline(n));
} else {
err = false;
}
if (err) {
brk(true); // break traversal
}
}});
return false;
});
}
function visit(node) {
// forbidden pattern:
// <any>* <loop> <non-fn>* <constlet-def> <any>* <fn> <any>* <constlet-ref>
var loopNode = null;
if (isReference(node) && node.$refToScope && isConstLet(node.$refToScope.getKind(node.name))) {
// traverse nodes up towards root from constlet-def
// if we hit a function (before a loop) - ok!
// if we hit a loop - maybe-ouch
// if we reach root - ok!
for (var n = node.$refToScope.node; ; ) {
if (isFunction(n)) {
// we're ok (function-local)
return;
} else if (isLoop(n)) {
loopNode = n;
// maybe not ok (between loop and function)
break;
}
n = n.$parent;
if (!n) {
// ok (reached root)
return;
}
}
n = n.$parent;
if (!n) {
// ok (reached root)
return;
}
}
// traverse scopes from reference-scope up towards definition-scope
// if we hit a function, ouch!
var defScope = node.$refToScope;
for (var s = node.$scope; s; s = s.parent) {
if (s === defScope) {
// we're ok
return;
} else if (isFunction(s.node)) {
// not ok (there's a function between the reference and definition)
error(getline(node), "can't transform closure. {0} is defined outside closure, inside loop", node.name);
assert(isLoop(loopNode));
// traverse scopes from reference-scope up towards definition-scope
// if we hit a function, ouch!
var defScope = node.$refToScope;
var generateIIFE = (options.loopClosures === "iife");
for (var s = node.$scope; s; s = s.parent) {
if (s === defScope) {
// we're ok
return;
} else if (isFunction(s.node)) {
// not ok (there's a function between the reference and definition)
// may be transformable via IIFE
if (!generateIIFE) {
var msg = "loop-variable {0} is captured by a loop-closure. Tried \"loopClosures\": \"iife\" in defs-config.json?";
return error(getline(node), msg, node.name);
}
// here be dragons
// for (let x = ..; .. ; ..) { (function(){x})() } is forbidden because of current
// spec and VM status
if (loopNode.type === "ForStatement" && defScope.node === loopNode) {
var declarationNode = defScope.getNode(node.name);
return error(getline(declarationNode), "Not yet specced ES6 feature. {0} is declared in for-loop header and then captured in loop closure", declarationNode.name);
}
// speak now or forever hold your peace
if (detectIifyBodyBlockers(loopNode.body, node)) {
// error already generated
return;
}
// mark loop for IIFE-insertion
loopNode.$iify = true;
}
}

@@ -388,11 +466,78 @@ }

function detectConstAssignment(node) {
if (isLvalue(node)) {
var scope = node.$scope.lookup(node.name);
if (scope && scope.getKind(node.name) === "const") {
error(getline(node), "can't assign to const variable {0}", node.name);
function transformLoopClosures(root, ops) {
function insertOp(pos, str, node) {
var op = {
start: pos,
end: pos,
str: str,
}
if (node) {
op.node = node;
}
ops.push(op);
}
traverse(root, {pre: function(node) {
if (!node.$iify) {
return;
}
var hasBlock = (node.body.type === "BlockStatement");
var insertHead = (hasBlock ?
node.body.range[0] + 1 : // just after body {
node.body.range[0]); // just before existing expression
var insertFoot = (hasBlock ?
node.body.range[1] - 1 : // just before body }
node.body.range[1]); // just after existing expression
var forInName = (node.type === "ForInStatement" && node.left.declarations[0].id.name);;
var iifeHead = fmt("(function({0}){", forInName ? forInName : "");
var iifeTail = fmt("}).call(this{0});", forInName ? ", " + forInName : "");
// modify AST
var iifeFragment = esprima(iifeHead + iifeTail);
var iifeExpressionStatement = iifeFragment.body[0];
var iifeBlockStatement = iifeExpressionStatement.expression.callee.object.body;
if (hasBlock) {
var forBlockStatement = node.body;
var tmp = forBlockStatement.body;
forBlockStatement.body = [iifeExpressionStatement];
iifeBlockStatement.body = tmp;
} else {
var tmp$0 = node.body;
node.body = iifeExpressionStatement;
iifeBlockStatement.body[0] = tmp$0;
}
// create ops
insertOp(insertHead, iifeHead);
if (forInName) {
insertOp(insertFoot, "}).call(this, ");
var args = iifeExpressionStatement.expression.arguments;
var iifeArgumentIdentifier = args[1];
iifeArgumentIdentifier.alterop = true;
insertOp(insertFoot, forInName, iifeArgumentIdentifier);
insertOp(insertFoot, ");");
} else {
insertOp(insertFoot, iifeTail);
}
}});
}
function detectConstAssignment(ast) {
traverse(ast, {pre: function(node) {
if (isLvalue(node)) {
var scope = node.$scope.lookup(node.name);
if (scope && scope.getKind(node.name) === "const") {
error(getline(node), "can't assign to const variable {0}", node.name);
}
}
}});
}
function detectConstantLets(ast) {

@@ -411,3 +556,3 @@ traverse(ast, {pre: function(node) {

function setupScopeAndReferences(root) {
function setupScopeAndReferences(root, opts) {
// setup scopes

@@ -426,3 +571,3 @@ traverse(root, {pre: createScopes});

// also collects all referenced names to allIdentifiers
setupReferences(root, allIdentifiers);
setupReferences(root, allIdentifiers, opts);
return allIdentifiers;

@@ -455,9 +600,12 @@ }

var allIdentifiers = setupScopeAndReferences(ast);
var allIdentifiers = setupScopeAndReferences(ast, {});
// static analysis passes
traverse(ast, {pre: detectLoopClosures});
traverse(ast, {pre: detectConstAssignment});
detectLoopClosures(ast);
detectConstAssignment(ast);
//detectConstantLets(ast);
var changes = [];
transformLoopClosures(ast, changes);
//ast.$scope.print(); process.exit(-1);

@@ -471,2 +619,8 @@

if (changes.length > 0) {
cleanupTree(ast);
allIdentifiers = setupScopeAndReferences(ast, {analyze: false});
}
assert(error.errors.length === 0);
// change constlet declarations to var, renamed if needed

@@ -476,3 +630,3 @@ // varify modifies the scopes and AST accordingly and

var stats = new Stats();
var changes = varify(ast, stats, allIdentifiers);
varify(ast, stats, allIdentifiers, changes);

@@ -479,0 +633,0 @@ if (options.ast) {

var fs = require("fs");
var fmt = require("simple-fmt");
var exec = require("child_process").exec;
var diff = require("diff");

@@ -16,2 +17,10 @@ function slurp(filename) {

function run(test) {
function diffOutput(correct, got, name) {
if (got !== correct) {
var patch = diff.createPatch(name, correct, got);
process.stdout.write(patch);
process.stdout.write("\n\n");
}
}
var noSuffix = test.slice(0, -3);

@@ -24,17 +33,9 @@ exec(fmt("{0} {1} defs-cmd {2}/{3}", NODE, FLAG, pathToTests, test), function(error, stdout, stderr) {

if (stderr !== expectedStderr) {
fail("stderr", stderr, expectedStderr);
}
if (stdout !== expectedStdout) {
fail("stdout", stdout, expectedStdout);
}
var pass = (stderr === expectedStderr && stdout === expectedStdout);
function fail(type, got, expected) {
if (!pass) {
console.log(fmt("FAILED test {0}", test));
console.log(fmt("\nEXPECTED {0}:", type));
process.stdout.write(expected);
console.log(fmt("\nGOT {0}:", type));
process.stdout.write(got);
console.log("---------------------------\n");
}
diffOutput(expectedStdout, stdout, fmt("{0}-out.js", test));
diffOutput(expectedStderr, stderr, fmt("{0}-stderr", test));
});

@@ -41,0 +42,0 @@ }

@@ -131,3 +131,3 @@ "use strict";

Scope.prototype.remove = function(name) {
return this.decls.delete(name);
return this.decls.remove(name);
};

@@ -134,0 +134,0 @@

@@ -0,1 +1,6 @@

## v0.5.0 2013-09-30
* Loop closure IIFE transformation support
* Search for defs-config.json upwards in filesystem
* Improved error messages
## v0.4.3 2013-09-05

@@ -2,0 +7,0 @@ * Improved loop closure detection as to remove false positives

@@ -21,5 +21,3 @@ "use strict";

const config = tryor(function() {
return JSON.parse(String(fs.readFileSync("defs-config.json")));
}, {});
const config = findAndReadConfig();

@@ -43,1 +41,25 @@ const ret = defs(src, config);

}
function findAndReadConfig() {
let path = "";
let filename = "defs-config.json";
let filenamePath = null;
while (fs.existsSync(path || ".")) {
filenamePath = path + filename;
if (fs.existsSync(filenamePath)) {
const config = tryor(function() {
return JSON.parse(String(fs.readFileSync(filenamePath)));
}, null);
if (config === null) {
console.error("error: bad JSON in %s", filenamePath);
process.exit(-1);
}
return config;
}
path = "../" + path;
}
return {};
}

@@ -11,2 +11,3 @@ "use strict";

const traverse = require("ast-traverse");
const breakable = require("breakable");
const Scope = require("./scope");

@@ -214,3 +215,5 @@ const error = require("./error");

function setupReferences(ast, allIdentifiers) {
function setupReferences(ast, allIdentifiers, opts) {
const analyze = (is.own(opts, "analyze") ? opts.analyze : true);
function visit(node) {

@@ -223,7 +226,7 @@ if (!isReference(node)) {

const scope = node.$scope.lookup(node.name);
if (!scope && options.disallowUnknownReferences) {
if (analyze && !scope && options.disallowUnknownReferences) {
error(getline(node), "reference to unknown global variable {0}", node.name);
}
// check const and let for referenced-before-declaration
if (scope && is.someof(scope.getKind(node.name), ["const", "let"])) {
if (analyze && scope && is.someof(scope.getKind(node.name), ["const", "let"])) {
const allowedFromPos = scope.getFromPos(node.name);

@@ -248,5 +251,3 @@ const referencedAtPos = node.range[0];

function varify(ast, stats, allIdentifiers) {
const changes = [];
function varify(ast, stats, allIdentifiers, changes) {
function unique(name) {

@@ -330,7 +331,23 @@ assert(allIdentifiers.has(name));

changes.push({
start: node.range[0],
end: node.range[1],
str: move.name,
});
if (node.alterop) {
// node has no range because it is the result of another alter operation
let existingOp = null;
for (let i = 0; i < changes.length; i++) {
const op = changes[i];
if (op.node === node) {
existingOp = op;
break;
}
}
assert(existingOp);
// modify op
existingOp.str = move.name;
} else {
changes.push({
start: node.range[0],
end: node.range[1],
str: move.name,
});
}
}

@@ -344,40 +361,101 @@ }

}});
return changes;
}
function detectLoopClosures(node) {
// forbidden pattern:
// <any>* <loop> <non-fn>* <constlet-def> <any>* <fn> <any>* <constlet-ref>
if (isReference(node) && node.$refToScope && isConstLet(node.$refToScope.getKind(node.name))) {
// traverse nodes up towards root from var-def
// if we hit a function (before a loop) - ok!
// if we hit a loop - maybe-ouch
// if we reach root - ok!
for (let n = node.$refToScope.node; ; ) {
if (isFunction(n)) {
// we're ok (function-local)
return;
} else if (isLoop(n)) {
// not ok (between loop and function)
break;
function detectLoopClosures(ast) {
traverse(ast, {pre: visit});
function detectIifyBodyBlockers(body, node) {
return breakable(function(brk) {
traverse(body, {pre: function(n) {
// if we hit an inner function of the loop body, don't traverse further
if (isFunction(n)) {
return false;
}
let err = true; // reset to false in else-statement below
const msg = "loop-variable {0} is captured by a loop-closure that can't be transformed due to use of {1} at line {2}";
if (n.type === "BreakStatement") {
error(getline(node), msg, node.name, "break", getline(n));
} else if (n.type === "ContinueStatement") {
error(getline(node), msg, node.name, "continue", getline(n));
} else if (n.type === "ReturnStatement") {
error(getline(node), msg, node.name, "return", getline(n));
} else if (n.type === "Identifier" && n.name === "arguments") {
error(getline(node), msg, node.name, "arguments", getline(n));
} else if (n.type === "VariableDeclaration" && n.kind === "var") {
error(getline(node), msg, node.name, "var", getline(n));
} else {
err = false;
}
if (err) {
brk(true); // break traversal
}
}});
return false;
});
}
function visit(node) {
// forbidden pattern:
// <any>* <loop> <non-fn>* <constlet-def> <any>* <fn> <any>* <constlet-ref>
var loopNode = null;
if (isReference(node) && node.$refToScope && isConstLet(node.$refToScope.getKind(node.name))) {
// traverse nodes up towards root from constlet-def
// if we hit a function (before a loop) - ok!
// if we hit a loop - maybe-ouch
// if we reach root - ok!
for (let n = node.$refToScope.node; ; ) {
if (isFunction(n)) {
// we're ok (function-local)
return;
} else if (isLoop(n)) {
loopNode = n;
// maybe not ok (between loop and function)
break;
}
n = n.$parent;
if (!n) {
// ok (reached root)
return;
}
}
n = n.$parent;
if (!n) {
// ok (reached root)
return;
}
}
// traverse scopes from reference-scope up towards definition-scope
// if we hit a function, ouch!
const defScope = node.$refToScope;
for (let s = node.$scope; s; s = s.parent) {
if (s === defScope) {
// we're ok
return;
} else if (isFunction(s.node)) {
// not ok (there's a function between the reference and definition)
error(getline(node), "can't transform closure. {0} is defined outside closure, inside loop", node.name);
assert(isLoop(loopNode));
// traverse scopes from reference-scope up towards definition-scope
// if we hit a function, ouch!
const defScope = node.$refToScope;
const generateIIFE = (options.loopClosures === "iife");
for (let s = node.$scope; s; s = s.parent) {
if (s === defScope) {
// we're ok
return;
} else if (isFunction(s.node)) {
// not ok (there's a function between the reference and definition)
// may be transformable via IIFE
if (!generateIIFE) {
const msg = "loop-variable {0} is captured by a loop-closure. Tried \"loopClosures\": \"iife\" in defs-config.json?";
return error(getline(node), msg, node.name);
}
// here be dragons
// for (let x = ..; .. ; ..) { (function(){x})() } is forbidden because of current
// spec and VM status
if (loopNode.type === "ForStatement" && defScope.node === loopNode) {
const declarationNode = defScope.getNode(node.name);
return error(getline(declarationNode), "Not yet specced ES6 feature. {0} is declared in for-loop header and then captured in loop closure", declarationNode.name);
}
// speak now or forever hold your peace
if (detectIifyBodyBlockers(loopNode.body, node)) {
// error already generated
return;
}
// mark loop for IIFE-insertion
loopNode.$iify = true;
}
}

@@ -388,11 +466,78 @@ }

function detectConstAssignment(node) {
if (isLvalue(node)) {
const scope = node.$scope.lookup(node.name);
if (scope && scope.getKind(node.name) === "const") {
error(getline(node), "can't assign to const variable {0}", node.name);
function transformLoopClosures(root, ops) {
function insertOp(pos, str, node) {
const op = {
start: pos,
end: pos,
str: str,
}
if (node) {
op.node = node;
}
ops.push(op);
}
traverse(root, {pre: function(node) {
if (!node.$iify) {
return;
}
const hasBlock = (node.body.type === "BlockStatement");
const insertHead = (hasBlock ?
node.body.range[0] + 1 : // just after body {
node.body.range[0]); // just before existing expression
const insertFoot = (hasBlock ?
node.body.range[1] - 1 : // just before body }
node.body.range[1]); // just after existing expression
const forInName = (node.type === "ForInStatement" && node.left.declarations[0].id.name);;
const iifeHead = fmt("(function({0}){", forInName ? forInName : "");
const iifeTail = fmt("}).call(this{0});", forInName ? ", " + forInName : "");
// modify AST
const iifeFragment = esprima(iifeHead + iifeTail);
const iifeExpressionStatement = iifeFragment.body[0];
const iifeBlockStatement = iifeExpressionStatement.expression.callee.object.body;
if (hasBlock) {
const forBlockStatement = node.body;
const tmp = forBlockStatement.body;
forBlockStatement.body = [iifeExpressionStatement];
iifeBlockStatement.body = tmp;
} else {
const tmp = node.body;
node.body = iifeExpressionStatement;
iifeBlockStatement.body[0] = tmp;
}
// create ops
insertOp(insertHead, iifeHead);
if (forInName) {
insertOp(insertFoot, "}).call(this, ");
const args = iifeExpressionStatement.expression.arguments;
const iifeArgumentIdentifier = args[1];
iifeArgumentIdentifier.alterop = true;
insertOp(insertFoot, forInName, iifeArgumentIdentifier);
insertOp(insertFoot, ");");
} else {
insertOp(insertFoot, iifeTail);
}
}});
}
function detectConstAssignment(ast) {
traverse(ast, {pre: function(node) {
if (isLvalue(node)) {
const scope = node.$scope.lookup(node.name);
if (scope && scope.getKind(node.name) === "const") {
error(getline(node), "can't assign to const variable {0}", node.name);
}
}
}});
}
function detectConstantLets(ast) {

@@ -411,3 +556,3 @@ traverse(ast, {pre: function(node) {

function setupScopeAndReferences(root) {
function setupScopeAndReferences(root, opts) {
// setup scopes

@@ -426,3 +571,3 @@ traverse(root, {pre: createScopes});

// also collects all referenced names to allIdentifiers
setupReferences(root, allIdentifiers);
setupReferences(root, allIdentifiers, opts);
return allIdentifiers;

@@ -455,9 +600,12 @@ }

let allIdentifiers = setupScopeAndReferences(ast);
let allIdentifiers = setupScopeAndReferences(ast, {});
// static analysis passes
traverse(ast, {pre: detectLoopClosures});
traverse(ast, {pre: detectConstAssignment});
detectLoopClosures(ast);
detectConstAssignment(ast);
//detectConstantLets(ast);
const changes = [];
transformLoopClosures(ast, changes);
//ast.$scope.print(); process.exit(-1);

@@ -471,2 +619,8 @@

if (changes.length > 0) {
cleanupTree(ast);
allIdentifiers = setupScopeAndReferences(ast, {analyze: false});
}
assert(error.errors.length === 0);
// change constlet declarations to var, renamed if needed

@@ -476,3 +630,3 @@ // varify modifies the scopes and AST accordingly and

const stats = new Stats();
const changes = varify(ast, stats, allIdentifiers);
varify(ast, stats, allIdentifiers, changes);

@@ -479,0 +633,0 @@ if (options.ast) {

{
"name": "defs",
"version": "0.4.3",
"version": "0.5.0",
"description": "Static scope analysis and transpilation of ES6 block scoped const and let variables, to ES3.",

@@ -11,11 +11,15 @@ "main": "build/es5/defs-main.js",

"dependencies": {
"alter": "~0.1.0",
"ast-traverse": "~0.1.0",
"alter": "~0.2.0",
"breakable": "~0.1.0",
"ast-traverse": "~0.1.1",
"simple-fmt": "~0.1.0",
"simple-is": "~0.2.0",
"stringmap": "~0.2.0",
"stringset": "~0.2.0",
"tryor": "~0.1.0",
"stringmap": "~0.2.2",
"stringset": "~0.2.1",
"tryor": "~0.1.2",
"esprima": "~1.0.0"
},
"devDependencies": {
"diff": "~1.0.7"
},
"keywords": [

@@ -22,0 +26,0 @@ "defs",

@@ -40,3 +40,3 @@ # defs.js

`defs` looks for a `defs-config.json` configuration file in your current
directory. It will search for it in parent directories soon as you'd expect.
directory. If not found there, it searches parent directories until it hits `/`.

@@ -52,2 +52,3 @@ Example `defs-config.json`:

},
"loopClosures": "iife",
"disallowVars": false,

@@ -65,2 +66,5 @@ "disallowDuplicated": true,

`loopClosures` (defaults to `false`) can be set to "iife" to enable transformation
of loop-closures via immediately-invoked function expressions.
`disallowVars` (defaults to `false`) can be enabled to make

@@ -136,59 +140,9 @@ usage of `var` an error.

## Compatibility
`defs.js` strives to transpile your program as true to the ES6 block scope semantics as
possible, while being as maximally non-intrusive as possible. The only textual
differences you'll find between your original and transpiled program is that the latter
uses `var` and occasional variable renames.
`defs.js` strives to transpile your program as true to ES6 block scope semantics as
possible while being as maximally non-intrusive as possible.
It can optionally transform loop closures via IIFE's (when possible), if you include
`"loopClosures": "iife"` in your `defs-config.json`. More info in
[loop-closures.md](loop-closures.md).
### Loop closures limitation
`defs.js` won't transpile a closure-that-captures-a-block-scoped-variable-inside-a-loop, such
as the following example:
```javascript
for (let x = 0; x < 10; x++) {
let y = x;
arr.push(function() { return y; });
}
```
With ES6 semantics `y` is bound fresh per loop iteration, so each closure captures a separate
instance of `y`, unlike if `y` would have been a `var`. [Actually, even `x` is bound per
iteration, but v8 (so node) has an
[open bug](https://code.google.com/p/v8/issues/detail?id=2560) for that].
To transpile this example, an IIFE or `try-catch` must be inserted, which isn't maximally
non-intrusive. `defs.js` will detect this case and spit out an error instead, like so:
line 3: can't transform closure. y is defined outside closure, inside loop
You need to manually handle this the way we've always done pre-`ES6`,
for instance like so:
```javascript
for (let x = 0; x < 10; x++) {
(function(y) {
arr.push(function() { return y; });
})(x);
}
```
I'm interested in feedback on this based on real-world usage of `defs.js`.
### Referenced (inside closure) before declaration
`defs.js` detects the vast majority of cases where a variable is referenced prior to
its declaration. The one case it cannot detect is the following:
```javascript
function printx() { console.log(x); }
printx(); // illegal
let x = 1;
printx(); // legal
```
The first call to `printx` is not legal because `x` hasn't been initialized at that point
of *time*, which is impossible to catch reliably with statical analysis.
`v8 --harmony` will detect and error on this via run-time checking. `defs.js` will
happily transpile this example (`let` => `var` and that's it), and the transpiled code
will print `undefined` on the first call to `printx`. This difference should be a very
minor problem in practice.
See [semantic-differences.md](semantic-differences.md) for other minor differences.
const fs = require("fs");
const fmt = require("simple-fmt");
const exec = require("child_process").exec;
const diff = require("diff");

@@ -16,2 +17,10 @@ function slurp(filename) {

function run(test) {
function diffOutput(correct, got, name) {
if (got !== correct) {
const patch = diff.createPatch(name, correct, got);
process.stdout.write(patch);
process.stdout.write("\n\n");
}
}
const noSuffix = test.slice(0, -3);

@@ -24,17 +33,9 @@ exec(fmt("{0} {1} defs-cmd {2}/{3}", NODE, FLAG, pathToTests, test), function(error, stdout, stderr) {

if (stderr !== expectedStderr) {
fail("stderr", stderr, expectedStderr);
}
if (stdout !== expectedStdout) {
fail("stdout", stdout, expectedStdout);
}
const pass = (stderr === expectedStderr && stdout === expectedStdout);
function fail(type, got, expected) {
if (!pass) {
console.log(fmt("FAILED test {0}", test));
console.log(fmt("\nEXPECTED {0}:", type));
process.stdout.write(expected);
console.log(fmt("\nGOT {0}:", type));
process.stdout.write(got);
console.log("---------------------------\n");
}
diffOutput(expectedStdout, stdout, fmt("{0}-out.js", test));
diffOutput(expectedStderr, stderr, fmt("{0}-stderr", test));
});

@@ -41,0 +42,0 @@ }

@@ -131,3 +131,3 @@ "use strict";

Scope.prototype.remove = function(name) {
return this.decls.delete(name);
return this.decls.remove(name);
};

@@ -134,0 +134,0 @@

@@ -9,3 +9,3 @@ "use strict";

// can be transformed
// can be transformed (common manual work-around)
for (var x$0 in [0,1,2]) {

@@ -15,3 +15,3 @@ arr.push((function(x) { return function() { return x; } })(x$0));

// can be transformed
// can be transformed (no extra IIFE will be inserted)
for (var x$1 = 0; x$1 < 3; x$1++) {(function(){

@@ -22,4 +22,79 @@ var y = 1;

// can be transformed (added IIFE)
for (var x$2 = 0; x$2 < 3; x$2++) {(function(){
var y = 1;
arr.push(function() { return y; });
}).call(this);}
// can be transformed (added IIFE)
for (var x$3 = 0; x$3 < 3; x$3++) {(function(){
var y = x$3;
arr.push(function() { return y; });
}).call(this);}
// can be transformed (added IIFE)
for (var x$4 = 0; x$4 < 3; x$4++) {(function(){
var y = x$4, z = arr.push(function() { return y; });
}).call(this);}
// can be transformed (added IIFE)
for (var x$5 = 0; x$5 < 3; x$5++) {(function(){
var x = 1;
arr.push(function() { return x; });
}).call(this);}
// can be transformed (added IIFE)
while (true) {
var f = function() {
for (var x = 0; x < 10; x++) {(function(){
var y = x;
arr.push(function() { return y; });
}).call(this);}
};
f();
}
// it's fine to use break, continue, return and arguments as long as
// it's contained within a function below the loop so that it doesn't
// interfere with the inserted IIFE
(function() {
for (var x = 0; x < 3; x++) {(function(){
var y = x;
(function() {
for(;;) break;
return;
})();
(function() {
for(;;) continue;
arguments
})();
arr.push(function() { return y; });
}).call(this);}
})();
// For-In
for (var x$6 in [0,1,2]) {(function(x){
arr.push(function() { return x; });
}).call(this, x$6);}
// Block-less For-In
for (var x$7 in [0,1,2]) (function(x){arr.push(function() { return x; });}).call(this, x$7);/*with semicolon*/
for (var x$8 in [0,1,2]) (function(x){arr.push(function() { return x; })/*no semicolon*/
}).call(this, x$8);null; // previous semicolon-less for statement's range ends just before 'n' in 'null'
// While
while (true) {(function(){
var x = 1;
arr.push(function() { return x; });
}).call(this);}
// Do-While
do {(function(){
var x = 1;
arr.push(function() { return x; });
}).call(this);} while (true);
arr.forEach(function(f) {
console.log(f());
});

@@ -9,3 +9,3 @@ "use strict";

// can be transformed
// can be transformed (common manual work-around)
for (let x in [0,1,2]) {

@@ -15,10 +15,85 @@ arr.push((function(x) { return function() { return x; } })(x));

// can be transformed
// can be transformed (no extra IIFE will be inserted)
for (let x = 0; x < 3; x++) {(function(){
var y = 1;
let y = 1;
arr.push(function() { return y; });
}).call(this);}
// can be transformed (added IIFE)
for (let x = 0; x < 3; x++) {
let y = 1;
arr.push(function() { return y; });
}
// can be transformed (added IIFE)
for (let x = 0; x < 3; x++) {
let y = x;
arr.push(function() { return y; });
}
// can be transformed (added IIFE)
for (let x = 0; x < 3; x++) {
let y = x, z = arr.push(function() { return y; });
}
// can be transformed (added IIFE)
for (let x = 0; x < 3; x++) {
let x = 1;
arr.push(function() { return x; });
}
// can be transformed (added IIFE)
while (true) {
let f = function() {
for (let x = 0; x < 10; x++) {
let y = x;
arr.push(function() { return y; });
}
};
f();
}
// it's fine to use break, continue, return and arguments as long as
// it's contained within a function below the loop so that it doesn't
// interfere with the inserted IIFE
(function() {
for (let x = 0; x < 3; x++) {
let y = x;
(function() {
for(;;) break;
return;
})();
(function() {
for(;;) continue;
arguments
})();
arr.push(function() { return y; });
}
})();
// For-In
for (let x in [0,1,2]) {
arr.push(function() { return x; });
}
// Block-less For-In
for (let x in [0,1,2]) arr.push(function() { return x; });/*with semicolon*/
for (let x in [0,1,2]) arr.push(function() { return x; })/*no semicolon*/
null; // previous semicolon-less for statement's range ends just before 'n' in 'null'
// While
while (true) {
let x = 1;
arr.push(function() { return x; });
}
// Do-While
do {
let x = 1;
arr.push(function() { return x; });
} while (true);
arr.forEach(function(f) {
console.log(f());
});
"use strict";
var arr = [];
// fresh x per iteration so can't be transformed
// fresh x per iteration but semantics not determined yet
// in ES6 spec draft (transfer in particular). Also inconsistent
// between VM implementations.
// once ES6 nails down the semantics (and VM's catch up) we'll
// revisit
// note v8 bug https://code.google.com/p/v8/issues/detail?id=2560
// also see other/v8-bug.js
for (let x = 0; x < 10; x++) {
for (let x = 0; x < 3; x++) {
arr.push(function() { return x; });
}
for (let z, x = 0; x < 3; x++) {
arr.push(function() { return x; });
}
// fresh y per iteration so can't be transformed
for (var x = 0; x < 10; x++) {
// as a consequence of the above, defs is unable to transform
// the code below (even though it is the output of an earlier
// defs transformation). we should be able to detect this case
// (and pass it through unmodified) but is it worth the effort?
for (let x = 0; x < 3; x++) {(function(){
let y = x;
arr.push(function() { return y; });
}).call(this);}
// return is not allowed inside the loop body because the IIFE would break it
(function() {
for (let x = 0; x < 3; x++) {
let y = x;
return 1;
arr.push(function() { return y; });
}
})();
// break is not allowed inside the loop body because the IIFE would break it
for (let x = 0; x < 3; x++) {
let y = x;
break;
arr.push(function() { return y; });
}
// fresh y per iteration so can't be transformed
while (true) {
var f = function() {
for (var x = 0; x < 10; x++) {
let y = x;
arr.push(function() { return y; });
}
};
f();
// continue is not allowed inside the loop body because the IIFE would break it
for (let x = 0; x < 3; x++) {
let y = x;
continue;
arr.push(function() { return y; });
}
// fresh x per iteration so can't be transformed
for (let x in [0,1,2]) {
arr.push(function() { return x; });
// arguments is not allowed inside the loop body because the IIFE would break it
// (and I don't want to re-apply outer arguments in the inserted IIFE)
for (let x = 0; x < 3; x++) {
let y = x;
arguments[0];
arr.push(function() { return y; });
}
// continue is not allowed inside the loop body because the IIFE would break it
for (let x = 0; x < 3; x++) {
let y = x;
var z = 1;
arr.push(function() { return y; });
}
// TODO block-less loops (is that even applicable?)
arr.forEach(function(f) {
console.log(f());
});

Sorry, the diff of this file is not supported yet

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