From fced5cea430cc00e916876b663a8d2a84a5dad1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Ribaudo?= Date: Sun, 21 Jul 2019 06:34:43 +0200 Subject: [PATCH] Fix tdz checks in transform-block-scoping plugin (#9498) * Better tdz tests - Use jest's expect.toThrow/expect.not.toThrow - Add input/output tests * Fix basic tdz (a = 2; let a) Fixes #6848 * Make _guessExecutionStatusRelativeTo more robust * Add tests * Return less "unkown" execution status * "function" execution status does not exist * Fix recursive functions * Update helper version * "finally" blocks are always executed * Typo --- packages/babel-helpers/src/helpers.js | 31 +-- .../src/index.js | 18 +- .../src/tdz.js | 27 +-- .../test/fixtures/exec/scope-bindings.js | 2 + .../tdz/block-ref-function-call/exec.js | 6 +- .../tdz/block-ref-function-call/input.js | 3 + .../tdz/block-ref-function-call/options.json | 3 - .../tdz/block-ref-function-call/output.js | 3 + .../tdz/destructured-self-reference/exec.js | 4 +- .../tdz/destructured-self-reference/input.js | 1 + .../destructured-self-reference/options.json | 3 - .../tdz/destructured-self-reference/output.js | 5 + .../fixtures/tdz/function-call-after/exec.js | 7 + .../exec.js => function-call-after/input.js} | 0 .../tdz/function-call-after/output.js | 6 + .../fixtures/tdz/function-call-before/exec.js | 7 + .../tdz/function-call-before/input.js | 5 + .../tdz/function-call-before/output.js | 6 + .../function-call-maybe-real-after/exec.js | 6 + .../function-call-maybe-real-after/input.js | 4 + .../function-call-maybe-real-after/output.js | 9 + .../function-call-maybe-value-assign/exec.js | 5 + .../function-call-maybe-value-assign/input.js | 5 + .../output.js | 9 + .../fixtures/tdz/function-call-maybe/exec.js | 29 +++ .../fixtures/tdz/function-call-maybe/input.js | 3 + .../tdz/function-call-maybe/output.js | 9 + .../tdz/function-call-nested-function/exec.js | 17 ++ .../function-call-nested-function/input.js | 5 + .../function-call-nested-function/output.js | 11 + .../tdz/function-call-recursive-after/exec.js | 9 + .../function-call-recursive-after/input.js | 7 + .../function-call-recursive-after/output.js | 7 + .../function-call-recursive-before/exec.js | 9 + .../function-call-recursive-before/input.js | 7 + .../function-call-recursive-before/output.js | 7 + .../function-call-recursive-reference/exec.js | 12 ++ .../input.js | 10 + .../output.js | 12 ++ .../test/fixtures/tdz/function-ref/input.js | 3 + .../test/fixtures/tdz/function-ref/output.js | 9 + .../fixtures/tdz/hoisted-function/exec.js | 6 +- .../fixtures/tdz/hoisted-function/input.js | 3 + .../fixtures/tdz/hoisted-function/output.js | 3 + .../test/fixtures/tdz/hoisted-var/exec.js | 6 +- .../test/fixtures/tdz/hoisted-var/input.js | 3 + .../test/fixtures/tdz/hoisted-var/output.js | 2 + .../test/fixtures/tdz/options.json | 5 +- .../test/fixtures/tdz/self-reference/exec.js | 4 +- .../test/fixtures/tdz/self-reference/input.js | 1 + .../fixtures/tdz/self-reference/options.json | 3 - .../fixtures/tdz/self-reference/output.js | 1 + .../fixtures/tdz/shadow-outer-var/exec.js | 11 +- .../fixtures/tdz/shadow-outer-var/input.js | 6 + .../tdz/shadow-outer-var/options.json | 3 - .../fixtures/tdz/shadow-outer-var/output.js | 8 + .../test/fixtures/tdz/simple-assign/exec.js | 4 + .../test/fixtures/tdz/simple-assign/input.js | 2 + .../test/fixtures/tdz/simple-assign/output.js | 3 + .../fixtures/tdz/simple-reference/exec.js | 6 +- .../fixtures/tdz/simple-reference/input.js | 2 + .../tdz/simple-reference/options.json | 3 - .../fixtures/tdz/simple-reference/output.js | 2 + .../src/path/inference/inferer-reference.js | 2 +- .../babel-traverse/src/path/introspection.js | 199 +++++++++++++----- 65 files changed, 507 insertions(+), 122 deletions(-) create mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/block-ref-function-call/input.js delete mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/block-ref-function-call/options.json create mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/block-ref-function-call/output.js create mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/destructured-self-reference/input.js delete mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/destructured-self-reference/options.json create mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/destructured-self-reference/output.js create mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-after/exec.js rename packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/{function-call-no-throw/exec.js => function-call-after/input.js} (100%) create mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-after/output.js create mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-before/exec.js create mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-before/input.js create mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-before/output.js create mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-maybe-real-after/exec.js create mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-maybe-real-after/input.js create mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-maybe-real-after/output.js create mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-maybe-value-assign/exec.js create mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-maybe-value-assign/input.js create mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-maybe-value-assign/output.js create mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-maybe/exec.js create mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-maybe/input.js create mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-maybe/output.js create mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-nested-function/exec.js create mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-nested-function/input.js create mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-nested-function/output.js create mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-recursive-after/exec.js create mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-recursive-after/input.js create mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-recursive-after/output.js create mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-recursive-before/exec.js create mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-recursive-before/input.js create mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-recursive-before/output.js create mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-recursive-reference/exec.js create mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-recursive-reference/input.js create mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-recursive-reference/output.js create mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-ref/input.js create mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-ref/output.js create mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/hoisted-function/input.js create mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/hoisted-function/output.js create mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/hoisted-var/input.js create mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/hoisted-var/output.js create mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/self-reference/input.js delete mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/self-reference/options.json create mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/self-reference/output.js create mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/shadow-outer-var/input.js delete mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/shadow-outer-var/options.json create mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/shadow-outer-var/output.js create mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/simple-assign/exec.js create mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/simple-assign/input.js create mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/simple-assign/output.js create mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/simple-reference/input.js delete mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/simple-reference/options.json create mode 100644 packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/simple-reference/output.js diff --git a/packages/babel-helpers/src/helpers.js b/packages/babel-helpers/src/helpers.js index 8399fb903e45..527b24c330ac 100644 --- a/packages/babel-helpers/src/helpers.js +++ b/packages/babel-helpers/src/helpers.js @@ -817,18 +817,6 @@ helpers.taggedTemplateLiteralLoose = helper("7.0.0-beta.0")` } `; -helpers.temporalRef = helper("7.0.0-beta.0")` - import undef from "temporalUndefined"; - - export default function _temporalRef(val, name) { - if (val === undef) { - throw new ReferenceError(name + " is not defined - temporal dead zone"); - } else { - return val; - } - } -`; - helpers.readOnlyError = helper("7.0.0-beta.0")` export default function _readOnlyError(name) { throw new Error("\\"" + name + "\\" is read-only"); @@ -842,7 +830,24 @@ helpers.classNameTDZError = helper("7.0.0-beta.0")` `; helpers.temporalUndefined = helper("7.0.0-beta.0")` - export default {}; + // This function isn't mean to be called, but to be used as a reference. + // We can't use a normal object because it isn't hoisted. + export default function _temporalUndefined() {} +`; + +helpers.tdz = helper("7.5.5")` + export default function _tdzError(name) { + throw new ReferenceError(name + " is not defined - temporal dead zone"); + } +`; + +helpers.temporalRef = helper("7.0.0-beta.0")` + import undef from "temporalUndefined"; + import err from "tdz"; + + export default function _temporalRef(val, name) { + return val === undef ? err(name) : val; + } `; helpers.slicedToArray = helper("7.0.0-beta.0")` diff --git a/packages/babel-plugin-transform-block-scoping/src/index.js b/packages/babel-plugin-transform-block-scoping/src/index.js index cc63c554ddcd..50c254c848a8 100644 --- a/packages/babel-plugin-transform-block-scoping/src/index.js +++ b/packages/babel-plugin-transform-block-scoping/src/index.js @@ -16,7 +16,7 @@ export default declare((api, opts) => { throw new Error(`.throwIfClosureRequired must be a boolean, or undefined`); } if (typeof tdzEnabled !== "boolean") { - throw new Error(`.throwIfClosureRequired must be a boolean, or undefined`); + throw new Error(`.tdz must be a boolean, or undefined`); } return { @@ -33,11 +33,13 @@ export default declare((api, opts) => { for (let i = 0; i < node.declarations.length; i++) { const decl = node.declarations[i]; - if (decl.init) { - const assign = t.assignmentExpression("=", decl.id, decl.init); - assign._ignoreBlockScopingTDZ = true; - nodes.push(t.expressionStatement(assign)); - } + const assign = t.assignmentExpression( + "=", + decl.id, + decl.init || scope.buildUndefinedNode(), + ); + assign._ignoreBlockScopingTDZ = true; + nodes.push(t.expressionStatement(assign)); decl.init = this.addHelper("temporalUndefined"); } @@ -181,6 +183,8 @@ const letReferenceBlockVisitor = traverse.visitors.merge([ // simply rename the variables. if (state.loopDepth > 0) { path.traverse(letReferenceFunctionVisitor, state); + } else { + path.traverse(tdzVisitor, state); } return path.skip(); }, @@ -756,7 +760,7 @@ class BlockScoping { closurify: false, loopDepth: 0, tdzEnabled: this.tdzEnabled, - addHelper: name => this.addHelper(name), + addHelper: name => this.state.addHelper(name), }; if (isInLoop(this.blockPath)) { diff --git a/packages/babel-plugin-transform-block-scoping/src/tdz.js b/packages/babel-plugin-transform-block-scoping/src/tdz.js index 9612b8deb210..0588e2a276d1 100644 --- a/packages/babel-plugin-transform-block-scoping/src/tdz.js +++ b/packages/babel-plugin-transform-block-scoping/src/tdz.js @@ -1,12 +1,12 @@ -import { types as t } from "@babel/core"; +import { types as t, template } from "@babel/core"; function getTDZStatus(refPath, bindingPath) { const executionStatus = bindingPath._guessExecutionStatusRelativeTo(refPath); if (executionStatus === "before") { - return "inside"; - } else if (executionStatus === "after") { return "outside"; + } else if (executionStatus === "after") { + return "inside"; } else { return "maybe"; } @@ -41,7 +41,7 @@ export const visitor = { if (bindingPath.isFunctionDeclaration()) return; const status = getTDZStatus(path, bindingPath); - if (status === "inside") return; + if (status === "outside") return; if (status === "maybe") { const assert = buildTDZAssert(node, state); @@ -57,19 +57,8 @@ export const visitor = { } else { path.replaceWith(assert); } - } else if (status === "outside") { - path.replaceWith( - t.throwStatement( - t.inherits( - t.newExpression(t.identifier("ReferenceError"), [ - t.stringLiteral( - `${node.name} is not defined - temporal dead zone`, - ), - ]), - node, - ), - ), - ); + } else if (status === "inside") { + path.replaceWith(template.ast`${state.addHelper("tdz")}("${node.name}")`); } }, @@ -87,14 +76,14 @@ export const visitor = { const id = ids[name]; if (isReference(id, path.scope, state)) { - nodes.push(buildTDZAssert(id, state)); + nodes.push(id); } } if (nodes.length) { node._ignoreBlockScopingTDZ = true; nodes.push(node); - path.replaceWithMultiple(nodes.map(t.expressionStatement)); + path.replaceWithMultiple(nodes.map(n => t.expressionStatement(n))); } }, }, diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/exec/scope-bindings.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/exec/scope-bindings.js index 877467f94cfd..3014558a0261 100644 --- a/packages/babel-plugin-transform-block-scoping/test/fixtures/exec/scope-bindings.js +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/exec/scope-bindings.js @@ -5,6 +5,7 @@ if (x) { var innerScope = true; var res = transform(code, { + configFile: false, plugins: opts.plugins.concat([ function (b) { var t = b.types; @@ -34,3 +35,4 @@ if (x) { }`; expect(res.code).toBe(expected); +expect(innerScope).toBe(false); diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/block-ref-function-call/exec.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/block-ref-function-call/exec.js index f62c4127af3c..759123317338 100644 --- a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/block-ref-function-call/exec.js +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/block-ref-function-call/exec.js @@ -1,3 +1,5 @@ -f(); +expect(() => { + f(); -const f = function f() {} + const f = function f() {} +}).toThrow(ReferenceError); diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/block-ref-function-call/input.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/block-ref-function-call/input.js new file mode 100644 index 000000000000..f62c4127af3c --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/block-ref-function-call/input.js @@ -0,0 +1,3 @@ +f(); + +const f = function f() {} diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/block-ref-function-call/options.json b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/block-ref-function-call/options.json deleted file mode 100644 index 7a63bc19c6c6..000000000000 --- a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/block-ref-function-call/options.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "throws": "f is not defined - temporal dead zone" -} diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/block-ref-function-call/output.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/block-ref-function-call/output.js new file mode 100644 index 000000000000..6ef35b6f90bf --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/block-ref-function-call/output.js @@ -0,0 +1,3 @@ +babelHelpers.tdz("f")(); + +var f = function f() {}; diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/destructured-self-reference/exec.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/destructured-self-reference/exec.js index 166d7445f1ff..95ff7d58546f 100644 --- a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/destructured-self-reference/exec.js +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/destructured-self-reference/exec.js @@ -1 +1,3 @@ -let { b: d } = { d } +expect(() => { + let { b: d } = { d } +}).toThrow(ReferenceError); \ No newline at end of file diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/destructured-self-reference/input.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/destructured-self-reference/input.js new file mode 100644 index 000000000000..166d7445f1ff --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/destructured-self-reference/input.js @@ -0,0 +1 @@ +let { b: d } = { d } diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/destructured-self-reference/options.json b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/destructured-self-reference/options.json deleted file mode 100644 index e727ad851d25..000000000000 --- a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/destructured-self-reference/options.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "throws": "d is not defined - temporal dead zone" -} diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/destructured-self-reference/output.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/destructured-self-reference/output.js new file mode 100644 index 000000000000..e80677c113d3 --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/destructured-self-reference/output.js @@ -0,0 +1,5 @@ +var { + b: d +} = { + d: babelHelpers.tdz("d") +}; diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-after/exec.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-after/exec.js new file mode 100644 index 000000000000..57f99f6fd0ae --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-after/exec.js @@ -0,0 +1,7 @@ +expect(() => { + function f() { + x; + } + let x; + f(); +}).not.toThrow(); diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-no-throw/exec.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-after/input.js similarity index 100% rename from packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-no-throw/exec.js rename to packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-after/input.js diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-after/output.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-after/output.js new file mode 100644 index 000000000000..0666189283c9 --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-after/output.js @@ -0,0 +1,6 @@ +function f() { + x; +} + +var x; +f(); diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-before/exec.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-before/exec.js new file mode 100644 index 000000000000..afd1da8d03bd --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-before/exec.js @@ -0,0 +1,7 @@ +expect(() => { + function f() { + x; + } + f(); + let x; +}).toThrow(ReferenceError); diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-before/input.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-before/input.js new file mode 100644 index 000000000000..65d75d718937 --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-before/input.js @@ -0,0 +1,5 @@ +function f() { + x; +} +f(); +let x; diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-before/output.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-before/output.js new file mode 100644 index 000000000000..12dfbdd8c187 --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-before/output.js @@ -0,0 +1,6 @@ +function f() { + babelHelpers.tdz("x"); +} + +f(); +var x; diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-maybe-real-after/exec.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-maybe-real-after/exec.js new file mode 100644 index 000000000000..04e3cc201038 --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-maybe-real-after/exec.js @@ -0,0 +1,6 @@ +expect(() => { + function f() { x } + Math.random() === 2 && f(); + let x; + f(); +}).not.toThrow(); diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-maybe-real-after/input.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-maybe-real-after/input.js new file mode 100644 index 000000000000..9f43c5a414d7 --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-maybe-real-after/input.js @@ -0,0 +1,4 @@ +function f() { x } +Math.random() === 2 && f(); +let x; +f(); diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-maybe-real-after/output.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-maybe-real-after/output.js new file mode 100644 index 000000000000..be10d14b421e --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-maybe-real-after/output.js @@ -0,0 +1,9 @@ +var x = babelHelpers.temporalUndefined; + +function f() { + babelHelpers.temporalRef(x, "x"); +} + +Math.random() === 2 && f(); +x = void 0; +f(); diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-maybe-value-assign/exec.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-maybe-value-assign/exec.js new file mode 100644 index 000000000000..6b24914bd9a9 --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-maybe-value-assign/exec.js @@ -0,0 +1,5 @@ +function f() { x } +Math.random() === 2 && f(); +let x = 3; + +expect(x).toBe(3); diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-maybe-value-assign/input.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-maybe-value-assign/input.js new file mode 100644 index 000000000000..6b24914bd9a9 --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-maybe-value-assign/input.js @@ -0,0 +1,5 @@ +function f() { x } +Math.random() === 2 && f(); +let x = 3; + +expect(x).toBe(3); diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-maybe-value-assign/output.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-maybe-value-assign/output.js new file mode 100644 index 000000000000..65855262f71a --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-maybe-value-assign/output.js @@ -0,0 +1,9 @@ +var x = babelHelpers.temporalUndefined; + +function f() { + babelHelpers.temporalRef(x, "x"); +} + +Math.random() === 2 && f(); +x = 3; +expect(x).toBe(3); diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-maybe/exec.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-maybe/exec.js new file mode 100644 index 000000000000..4b2b4b9e3177 --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-maybe/exec.js @@ -0,0 +1,29 @@ +// "random" :) +let random = (i => { + const vals = [0, 0, 1, 1]; + return () => vals[i++]; +})(0); + +expect(() => { + function f() { x } + random() && f(); + let x; +}).not.toThrow(); + +expect(() => { + function f() { x } + random() || f(); + let x; +}).toThrow(ReferenceError); + +expect(() => { + function f() { x } + random() && f(); + let x; +}).toThrow(ReferenceError); + +expect(() => { + function f() { x } + random() || f(); + let x; +}).not.toThrow(); diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-maybe/input.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-maybe/input.js new file mode 100644 index 000000000000..41390ee1f4a6 --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-maybe/input.js @@ -0,0 +1,3 @@ +function f() { x } +Math.random() && f(); +let x; diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-maybe/output.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-maybe/output.js new file mode 100644 index 000000000000..7d416473c45e --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-maybe/output.js @@ -0,0 +1,9 @@ +var x = babelHelpers.temporalUndefined; + +function f() { + babelHelpers.temporalRef(x, "x"); +} + +Math.random() && f(); +x = void 0; +void 0; diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-nested-function/exec.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-nested-function/exec.js new file mode 100644 index 000000000000..44763e65ec3f --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-nested-function/exec.js @@ -0,0 +1,17 @@ +expect(() => { + function f() { + return function() { x }; + } + let g = f(); + let x; + g(); +}).not.toThrow(); + +expect(() => { + function f() { + return function() { x }; + } + let g = f(); + g(); + let x; +}).toThrow(ReferenceError); diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-nested-function/input.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-nested-function/input.js new file mode 100644 index 000000000000..473220910b97 --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-nested-function/input.js @@ -0,0 +1,5 @@ +function f() { + return function() { x }; +} +f(); +let x; \ No newline at end of file diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-nested-function/output.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-nested-function/output.js new file mode 100644 index 000000000000..262dd53d4306 --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-nested-function/output.js @@ -0,0 +1,11 @@ +var x = babelHelpers.temporalUndefined; + +function f() { + return function () { + babelHelpers.temporalRef(x, "x"); + }; +} + +f(); +x = void 0; +void 0; diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-recursive-after/exec.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-recursive-after/exec.js new file mode 100644 index 000000000000..0e7185778c41 --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-recursive-after/exec.js @@ -0,0 +1,9 @@ +expect(() => { + function f(i) { + if (i) f(i - 1); + x; + } + + let x; + f(3); +}).not.toThrow(); diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-recursive-after/input.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-recursive-after/input.js new file mode 100644 index 000000000000..d50d6a69743e --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-recursive-after/input.js @@ -0,0 +1,7 @@ +function f(i) { + if (i) f(i - 1); + x; +} + +let x; +f(3); diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-recursive-after/output.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-recursive-after/output.js new file mode 100644 index 000000000000..c63e0af30e2d --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-recursive-after/output.js @@ -0,0 +1,7 @@ +function f(i) { + if (i) f(i - 1); + x; +} + +var x; +f(3); diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-recursive-before/exec.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-recursive-before/exec.js new file mode 100644 index 000000000000..eef606be9fe5 --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-recursive-before/exec.js @@ -0,0 +1,9 @@ +expect(() => { + function f(i) { + if (i) f(i - 1); + x; + } + + f(3); + let x; +}).toThrow(ReferenceError); diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-recursive-before/input.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-recursive-before/input.js new file mode 100644 index 000000000000..b07cb1e2235f --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-recursive-before/input.js @@ -0,0 +1,7 @@ +function f(i) { + if (i) f(i - 1); + x; +} + +f(3); +let x; diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-recursive-before/output.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-recursive-before/output.js new file mode 100644 index 000000000000..38da18f3e1ea --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-recursive-before/output.js @@ -0,0 +1,7 @@ +function f(i) { + if (i) f(i - 1); + babelHelpers.tdz("x"); +} + +f(3); +var x; diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-recursive-reference/exec.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-recursive-reference/exec.js new file mode 100644 index 000000000000..7dd31073305c --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-recursive-reference/exec.js @@ -0,0 +1,12 @@ +expect(() => { + function f(i) { + return () => { + x; + f(i - 1); + }; + } + + const g = f(1); + let x; + g(); +}).not.toThrow(); diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-recursive-reference/input.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-recursive-reference/input.js new file mode 100644 index 000000000000..07c586249f11 --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-recursive-reference/input.js @@ -0,0 +1,10 @@ +function f(i) { + return () => { + x; + f(i - 1); + }; +} + +const g = f(1); +let x; +g(); diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-recursive-reference/output.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-recursive-reference/output.js new file mode 100644 index 000000000000..0621da46002f --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-recursive-reference/output.js @@ -0,0 +1,12 @@ +var x = babelHelpers.temporalUndefined; + +function f(i) { + return () => { + babelHelpers.temporalRef(x, "x"); + f(i - 1); + }; +} + +var g = f(1); +x = void 0; +g(); diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-ref/input.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-ref/input.js new file mode 100644 index 000000000000..4955ea61d31a --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-ref/input.js @@ -0,0 +1,3 @@ +function f() { x } +maybeCall(f); +let x; diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-ref/output.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-ref/output.js new file mode 100644 index 000000000000..816fba494e3d --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-ref/output.js @@ -0,0 +1,9 @@ +var x = babelHelpers.temporalUndefined; + +function f() { + babelHelpers.temporalRef(x, "x"); +} + +maybeCall(f); +x = void 0; +void 0; diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/hoisted-function/exec.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/hoisted-function/exec.js index 4f5fd2025711..254ec1eb3200 100644 --- a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/hoisted-function/exec.js +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/hoisted-function/exec.js @@ -1,3 +1,5 @@ -f(); +expect(() => { + f(); -function f() {} + function f() {} +}).not.toThrow(); diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/hoisted-function/input.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/hoisted-function/input.js new file mode 100644 index 000000000000..4f5fd2025711 --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/hoisted-function/input.js @@ -0,0 +1,3 @@ +f(); + +function f() {} diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/hoisted-function/output.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/hoisted-function/output.js new file mode 100644 index 000000000000..4f5fd2025711 --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/hoisted-function/output.js @@ -0,0 +1,3 @@ +f(); + +function f() {} diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/hoisted-var/exec.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/hoisted-var/exec.js index 4c3aa2b2feaa..a63f521aa873 100644 --- a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/hoisted-var/exec.js +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/hoisted-var/exec.js @@ -1,3 +1,5 @@ -x = 3; +expect(() => { + x = 3; -var x; + var x; +}).not.toThrow(); diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/hoisted-var/input.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/hoisted-var/input.js new file mode 100644 index 000000000000..4c3aa2b2feaa --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/hoisted-var/input.js @@ -0,0 +1,3 @@ +x = 3; + +var x; diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/hoisted-var/output.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/hoisted-var/output.js new file mode 100644 index 000000000000..ba478df54193 --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/hoisted-var/output.js @@ -0,0 +1,2 @@ +x = 3; +var x; diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/options.json b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/options.json index 07942e513ebc..e04afdec5da4 100644 --- a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/options.json +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/options.json @@ -1,3 +1,6 @@ { - "plugins": [["transform-block-scoping", { "tdz": true }]] + "plugins": [ + ["transform-block-scoping", { "tdz": true }], + ["external-helpers", { "helperVersion": "7.1000.0" }] + ] } diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/self-reference/exec.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/self-reference/exec.js index 29ade8c5aade..a2fbc3b8e865 100644 --- a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/self-reference/exec.js +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/self-reference/exec.js @@ -1 +1,3 @@ -let x = x; +expect(() => { + let x = x; +}).toThrow(ReferenceError); diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/self-reference/input.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/self-reference/input.js new file mode 100644 index 000000000000..29ade8c5aade --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/self-reference/input.js @@ -0,0 +1 @@ +let x = x; diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/self-reference/options.json b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/self-reference/options.json deleted file mode 100644 index 9bf6222fbc72..000000000000 --- a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/self-reference/options.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "throws": "x is not defined - temporal dead zone" -} diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/self-reference/output.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/self-reference/output.js new file mode 100644 index 000000000000..6b6c616e8b6f --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/self-reference/output.js @@ -0,0 +1 @@ +var x = babelHelpers.tdz("x"); diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/shadow-outer-var/exec.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/shadow-outer-var/exec.js index 88558f3540ef..1adbbf90a20a 100644 --- a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/shadow-outer-var/exec.js +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/shadow-outer-var/exec.js @@ -1,3 +1,8 @@ -var a = 5; -if (a){ console.log(a); let a = 2; } -console.log(a); +expect(() => { + var a = 5; + if (a) { + a; + let a = 2; + } + a; +}).toThrow(ReferenceError); diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/shadow-outer-var/input.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/shadow-outer-var/input.js new file mode 100644 index 000000000000..00b816f4bea8 --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/shadow-outer-var/input.js @@ -0,0 +1,6 @@ +var a = 5; +if (a) { + a; + let a = 2; +} +a; diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/shadow-outer-var/options.json b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/shadow-outer-var/options.json deleted file mode 100644 index c97e0b72d066..000000000000 --- a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/shadow-outer-var/options.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "throws": "a is not defined - temporal dead zone" -} diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/shadow-outer-var/output.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/shadow-outer-var/output.js new file mode 100644 index 000000000000..387cd33a8a8b --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/shadow-outer-var/output.js @@ -0,0 +1,8 @@ +var a = 5; + +if (a) { + babelHelpers.tdz("a"); + var _a = 2; +} + +a; diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/simple-assign/exec.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/simple-assign/exec.js new file mode 100644 index 000000000000..c05e082ae3cf --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/simple-assign/exec.js @@ -0,0 +1,4 @@ +expect(() => { + i = 2; + let i +}).toThrow(ReferenceError); diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/simple-assign/input.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/simple-assign/input.js new file mode 100644 index 000000000000..818e85c9e7a3 --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/simple-assign/input.js @@ -0,0 +1,2 @@ +i = 2; +let i diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/simple-assign/output.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/simple-assign/output.js new file mode 100644 index 000000000000..b6ec817e6248 --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/simple-assign/output.js @@ -0,0 +1,3 @@ +babelHelpers.tdz("i"); +i = 2; +var i; diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/simple-reference/exec.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/simple-reference/exec.js index 99758d996390..37f6df1e98d8 100644 --- a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/simple-reference/exec.js +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/simple-reference/exec.js @@ -1,2 +1,4 @@ -i -let i +expect(() => { + i + let i +}).toThrow(ReferenceError); diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/simple-reference/input.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/simple-reference/input.js new file mode 100644 index 000000000000..99758d996390 --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/simple-reference/input.js @@ -0,0 +1,2 @@ +i +let i diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/simple-reference/options.json b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/simple-reference/options.json deleted file mode 100644 index 7983b3617563..000000000000 --- a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/simple-reference/options.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "throws": "i is not defined - temporal dead zone" -} diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/simple-reference/output.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/simple-reference/output.js new file mode 100644 index 000000000000..b9ae80d157d5 --- /dev/null +++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/simple-reference/output.js @@ -0,0 +1,2 @@ +babelHelpers.tdz("i"); +var i; diff --git a/packages/babel-traverse/src/path/inference/inferer-reference.js b/packages/babel-traverse/src/path/inference/inferer-reference.js index 1cac5e10641b..0af6f1eedb04 100644 --- a/packages/babel-traverse/src/path/inference/inferer-reference.js +++ b/packages/babel-traverse/src/path/inference/inferer-reference.js @@ -102,7 +102,7 @@ function getConstantViolationsBefore(binding, path, functions) { return violations.filter(violation => { violation = violation.resolve(); const status = violation._guessExecutionStatusRelativeTo(path); - if (functions && status === "function") functions.push(violation); + if (functions && status === "unknown") functions.push(violation); return status === "before"; }); } diff --git a/packages/babel-traverse/src/path/introspection.js b/packages/babel-traverse/src/path/introspection.js index 626ca307af8c..5089b0a1d461 100644 --- a/packages/babel-traverse/src/path/introspection.js +++ b/packages/babel-traverse/src/path/introspection.js @@ -206,6 +206,75 @@ export function willIMaybeExecuteBefore(target) { return this._guessExecutionStatusRelativeTo(target) !== "after"; } +function getOuterFunction(path) { + return (path.scope.getFunctionParent() || path.scope.getProgramParent()).path; +} + +function isExecutionUncertain(type, key) { + switch (type) { + // a && FOO + // a || FOO + case "LogicalExpression": + return key === "right"; + + // a ? FOO : FOO + // if (a) FOO; else FOO; + case "ConditionalExpression": + case "IfStatement": + return key === "consequent" || key === "alternate"; + + // while (a) FOO; + case "WhileStatement": + case "DoWhileStatement": + case "ForInStatement": + case "ForOfStatement": + return key === "body"; + + // for (a; b; FOO) FOO; + case "ForStatement": + return key === "body" || key === "update"; + + // switch (a) { FOO } + case "SwitchStatement": + return key === "cases"; + + // try { a } catch FOO finally { b } + case "TryStatement": + return key === "handler"; + + // var [ x = FOO ] + case "AssignmentPattern": + return key === "right"; + + // a?.[FOO] + case "OptionalMemberExpression": + return key === "property"; + + // a?.(FOO) + case "OptionalCallExpression": + return key === "arguments"; + + default: + return false; + } +} + +function isExecutionUncertainInList(paths, maxIndex) { + for (let i = 0; i < maxIndex; i++) { + const path = paths[i]; + if (isExecutionUncertain(path.parent.type, path.parentKey)) { + return true; + } + } + return false; +} + +// TODO (Babel 8) +// This can be { before: boolean, after: boolean, unknown: boolean }. +// This allows transforms like the tdz one to treat cases when the status +// is both before and unknown/after like if it were before. +type RelativeExecutionStatus = "before" | "after" | "unknown"; + /** * Given a `target` check the execution status of it relative to the current path. * @@ -213,108 +282,132 @@ export function willIMaybeExecuteBefore(target) { * before or after the input `target` element. */ -export function _guessExecutionStatusRelativeTo(target) { +export function _guessExecutionStatusRelativeTo( + target: NodePath, +): RelativeExecutionStatus { // check if the two paths are in different functions, we can't track execution of these - const targetFuncParent = - target.scope.getFunctionParent() || target.scope.getProgramParent(); - const selfFuncParent = - this.scope.getFunctionParent() || target.scope.getProgramParent(); + const funcParent = { + this: getOuterFunction(this), + target: getOuterFunction(target), + }; // here we check the `node` equality as sometimes we may have different paths for the // same node due to path thrashing - if (targetFuncParent.node !== selfFuncParent.node) { - const status = this._guessExecutionStatusRelativeToDifferentFunctions( - targetFuncParent, + if (funcParent.target.node !== funcParent.this.node) { + return this._guessExecutionStatusRelativeToDifferentFunctions( + funcParent.target, ); - if (status) { - return status; - } else { - target = targetFuncParent.path; - } } - const targetPaths = target.getAncestry(); - if (targetPaths.indexOf(this) >= 0) return "after"; + const paths = { + target: target.getAncestry(), + this: this.getAncestry(), + }; - const selfPaths = this.getAncestry(); + // If this is an ancestor of the target path, + // e.g. f(g); where this is f and target is g. + if (paths.target.indexOf(this) >= 0) return "after"; + if (paths.this.indexOf(target) >= 0) return "before"; // get ancestor where the branches intersect let commonPath; - let targetIndex; - let selfIndex; - for (selfIndex = 0; selfIndex < selfPaths.length; selfIndex++) { - const selfPath = selfPaths[selfIndex]; - targetIndex = targetPaths.indexOf(selfPath); - if (targetIndex >= 0) { - commonPath = selfPath; - break; + const commonIndex = { target: 0, this: 0 }; + + while (!commonPath && commonIndex.this < paths.this.length) { + const path = paths.this[commonIndex.this]; + commonIndex.target = paths.target.indexOf(path); + if (commonIndex.target >= 0) { + commonPath = path; + } else { + commonIndex.this++; } } + if (!commonPath) { - return "before"; + throw new Error( + "Internal Babel error - The two compared nodes" + + " don't appear to belong to the same program.", + ); } - // get the relationship paths that associate these nodes to their common ancestor - const targetRelationship = targetPaths[targetIndex - 1]; - const selfRelationship = selfPaths[selfIndex - 1]; - if (!targetRelationship || !selfRelationship) { - return "before"; + if ( + isExecutionUncertainInList(paths.this, commonIndex.this - 1) || + isExecutionUncertainInList(paths.target, commonIndex.target - 1) + ) { + return "unknown"; } + const divergence = { + this: paths.this[commonIndex.this - 1], + target: paths.target[commonIndex.target - 1], + }; + // container list so let's see which one is after the other + // e.g. [ THIS, TARGET ] if ( - targetRelationship.listKey && - targetRelationship.container === selfRelationship.container + divergence.target.listKey && + divergence.this.listKey && + divergence.target.container === divergence.this.container ) { - return targetRelationship.key > selfRelationship.key ? "before" : "after"; + return divergence.target.key > divergence.this.key ? "before" : "after"; } // otherwise we're associated by a parent node, check which key comes before the other const keys = t.VISITOR_KEYS[commonPath.type]; - const targetKeyPosition = keys.indexOf(targetRelationship.key); - const selfKeyPosition = keys.indexOf(selfRelationship.key); - return targetKeyPosition > selfKeyPosition ? "before" : "after"; + const keyPosition = { + this: keys.indexOf(divergence.this.parentKey), + target: keys.indexOf(divergence.target.parentKey), + }; + return keyPosition.target > keyPosition.this ? "before" : "after"; } +// Used to avoid infinite recursion in cases like +// function f() { if (false) f(); } +// f(); +// It also works with indirect recursion. +const executionOrderCheckedNodes = new WeakSet(); + export function _guessExecutionStatusRelativeToDifferentFunctions( - targetFuncParent, -) { - const targetFuncPath = targetFuncParent.path; - if (!targetFuncPath.isFunctionDeclaration()) return; + target: NodePath, +): RelativeExecutionStatus { + if (!target.isFunctionDeclaration()) return "unknown"; // so we're in a completely different function, if this is a function declaration // then we can be a bit smarter and handle cases where the function is either // a. not called at all (part of an export) // b. called directly - const binding = targetFuncPath.scope.getBinding(targetFuncPath.node.id.name); + const binding = target.scope.getBinding(target.node.id.name); // no references! if (!binding.references) return "before"; const referencePaths: Array = binding.referencePaths; - // verify that all of the references are calls - for (const path of referencePaths) { - if (path.key !== "callee" || !path.parentPath.isCallExpression()) { - return; - } - } - let allStatus; // verify that all the calls have the same execution status for (const path of referencePaths) { // if a reference is a child of the function we're checking against then we can // safely ignore it - const childOfFunction = !!path.find( - path => path.node === targetFuncPath.node, - ); + const childOfFunction = !!path.find(path => path.node === target.node); if (childOfFunction) continue; + if (path.key !== "callee" || !path.parentPath.isCallExpression()) { + // This function is passed as a reference, so we don't + // know when it will be called. + return "unknown"; + } + + // Prevent infinte loops in recursive functions + if (executionOrderCheckedNodes.has(path.node)) continue; + executionOrderCheckedNodes.add(path.node); + const status = this._guessExecutionStatusRelativeTo(path); - if (allStatus) { - if (allStatus !== status) return; + executionOrderCheckedNodes.delete(path.node); + + if (allStatus && allStatus !== status) { + return "unknown"; } else { allStatus = status; }