From 7c4701716c2b228e372a3b8cee7d64322242efc9 Mon Sep 17 00:00:00 2001 From: Sebastian McKenzie Date: Sun, 18 Jan 2015 21:26:02 +1100 Subject: [PATCH] implement block scoping TDZ --- .../transformers/es6-let-scoping.js | 49 +++++++++++++++++-- .../temporal-dead-zone/actual.js | 2 + .../temporal-dead-zone/options.json | 3 ++ 3 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 test/fixtures/transformation/es6-let-scoping/temporal-dead-zone/actual.js create mode 100644 test/fixtures/transformation/es6-let-scoping/temporal-dead-zone/options.json diff --git a/lib/6to5/transformation/transformers/es6-let-scoping.js b/lib/6to5/transformation/transformers/es6-let-scoping.js index 14849d6c7c..d48e3fcf74 100644 --- a/lib/6to5/transformation/transformers/es6-let-scoping.js +++ b/lib/6to5/transformation/transformers/es6-let-scoping.js @@ -57,6 +57,7 @@ exports.Loop = function (node, parent, scope, context, file) { } }; +exports.Program = exports.BlockStatement = function (block, parent, scope, context, file) { if (!t.isLoop(parent)) { var letScoping = new LetScoping(false, block, parent, scope, file); @@ -95,10 +96,12 @@ LetScoping.prototype.run = function () { if (block._letDone) return; block._letDone = true; - // this is a block within a `Function` so we can safely leave it be - if (t.isFunction(this.parent)) return; - var needsClosure = this.getLetReferences(); + this.checkTDZ(); + + // this is a block within a `Function` so we can safely leave it be + if (t.isFunction(this.parent) || t.isProgram(this.block)) return; + if (needsClosure) { this.needsClosure(); } else { @@ -106,6 +109,46 @@ LetScoping.prototype.run = function () { } }; +/** + * Description + */ + +LetScoping.prototype.checkTDZ = function () { + var state = { + letRefs: this.letReferences, + file: this.file + }; + + traverse(this.block, { + enter: function (node, parent, scope, context, state) { + if (!t.isIdentifier(node)) return; + if (!t.isReferenced(node, parent)) return; + + var declared = state.letRefs[node.name]; + if (!declared) return; + + // declared node is different in this scope + if (scope.get(node.name, true) !== declared) return; + + var declaredLoc = declared.loc.start; + var referenceLoc = node.loc.start; + + // does this reference appear on a line before the declaration? + var before = referenceLoc.line < declaredLoc.line; + + if (referenceLoc.line === declaredLoc.line) { + // this reference appears on the same line + // check it appears before the declaration + before = referenceLoc.col < declaredLoc.col; + } + + if (before) { + throw state.file.errorWithNode(node, "Temporal dead zone - accessing a variable before it's initialized"); + } + } + }, this.scope, state); +}; + /** * Description */ diff --git a/test/fixtures/transformation/es6-let-scoping/temporal-dead-zone/actual.js b/test/fixtures/transformation/es6-let-scoping/temporal-dead-zone/actual.js new file mode 100644 index 0000000000..34a3ccd844 --- /dev/null +++ b/test/fixtures/transformation/es6-let-scoping/temporal-dead-zone/actual.js @@ -0,0 +1,2 @@ +qux; +let qux = 456; diff --git a/test/fixtures/transformation/es6-let-scoping/temporal-dead-zone/options.json b/test/fixtures/transformation/es6-let-scoping/temporal-dead-zone/options.json new file mode 100644 index 0000000000..faba38c879 --- /dev/null +++ b/test/fixtures/transformation/es6-let-scoping/temporal-dead-zone/options.json @@ -0,0 +1,3 @@ +{ + "throws": "Temporal dead zone - accessing a variable before it's initialized" +}