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
This commit is contained in:
parent
9bc9571381
commit
fced5cea43
@ -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")`
|
helpers.readOnlyError = helper("7.0.0-beta.0")`
|
||||||
export default function _readOnlyError(name) {
|
export default function _readOnlyError(name) {
|
||||||
throw new Error("\\"" + name + "\\" is read-only");
|
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")`
|
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")`
|
helpers.slicedToArray = helper("7.0.0-beta.0")`
|
||||||
|
|||||||
@ -16,7 +16,7 @@ export default declare((api, opts) => {
|
|||||||
throw new Error(`.throwIfClosureRequired must be a boolean, or undefined`);
|
throw new Error(`.throwIfClosureRequired must be a boolean, or undefined`);
|
||||||
}
|
}
|
||||||
if (typeof tdzEnabled !== "boolean") {
|
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 {
|
return {
|
||||||
@ -33,11 +33,13 @@ export default declare((api, opts) => {
|
|||||||
|
|
||||||
for (let i = 0; i < node.declarations.length; i++) {
|
for (let i = 0; i < node.declarations.length; i++) {
|
||||||
const decl = node.declarations[i];
|
const decl = node.declarations[i];
|
||||||
if (decl.init) {
|
const assign = t.assignmentExpression(
|
||||||
const assign = t.assignmentExpression("=", decl.id, decl.init);
|
"=",
|
||||||
|
decl.id,
|
||||||
|
decl.init || scope.buildUndefinedNode(),
|
||||||
|
);
|
||||||
assign._ignoreBlockScopingTDZ = true;
|
assign._ignoreBlockScopingTDZ = true;
|
||||||
nodes.push(t.expressionStatement(assign));
|
nodes.push(t.expressionStatement(assign));
|
||||||
}
|
|
||||||
decl.init = this.addHelper("temporalUndefined");
|
decl.init = this.addHelper("temporalUndefined");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,6 +183,8 @@ const letReferenceBlockVisitor = traverse.visitors.merge([
|
|||||||
// simply rename the variables.
|
// simply rename the variables.
|
||||||
if (state.loopDepth > 0) {
|
if (state.loopDepth > 0) {
|
||||||
path.traverse(letReferenceFunctionVisitor, state);
|
path.traverse(letReferenceFunctionVisitor, state);
|
||||||
|
} else {
|
||||||
|
path.traverse(tdzVisitor, state);
|
||||||
}
|
}
|
||||||
return path.skip();
|
return path.skip();
|
||||||
},
|
},
|
||||||
@ -756,7 +760,7 @@ class BlockScoping {
|
|||||||
closurify: false,
|
closurify: false,
|
||||||
loopDepth: 0,
|
loopDepth: 0,
|
||||||
tdzEnabled: this.tdzEnabled,
|
tdzEnabled: this.tdzEnabled,
|
||||||
addHelper: name => this.addHelper(name),
|
addHelper: name => this.state.addHelper(name),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isInLoop(this.blockPath)) {
|
if (isInLoop(this.blockPath)) {
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
import { types as t } from "@babel/core";
|
import { types as t, template } from "@babel/core";
|
||||||
|
|
||||||
function getTDZStatus(refPath, bindingPath) {
|
function getTDZStatus(refPath, bindingPath) {
|
||||||
const executionStatus = bindingPath._guessExecutionStatusRelativeTo(refPath);
|
const executionStatus = bindingPath._guessExecutionStatusRelativeTo(refPath);
|
||||||
|
|
||||||
if (executionStatus === "before") {
|
if (executionStatus === "before") {
|
||||||
return "inside";
|
|
||||||
} else if (executionStatus === "after") {
|
|
||||||
return "outside";
|
return "outside";
|
||||||
|
} else if (executionStatus === "after") {
|
||||||
|
return "inside";
|
||||||
} else {
|
} else {
|
||||||
return "maybe";
|
return "maybe";
|
||||||
}
|
}
|
||||||
@ -41,7 +41,7 @@ export const visitor = {
|
|||||||
if (bindingPath.isFunctionDeclaration()) return;
|
if (bindingPath.isFunctionDeclaration()) return;
|
||||||
|
|
||||||
const status = getTDZStatus(path, bindingPath);
|
const status = getTDZStatus(path, bindingPath);
|
||||||
if (status === "inside") return;
|
if (status === "outside") return;
|
||||||
|
|
||||||
if (status === "maybe") {
|
if (status === "maybe") {
|
||||||
const assert = buildTDZAssert(node, state);
|
const assert = buildTDZAssert(node, state);
|
||||||
@ -57,19 +57,8 @@ export const visitor = {
|
|||||||
} else {
|
} else {
|
||||||
path.replaceWith(assert);
|
path.replaceWith(assert);
|
||||||
}
|
}
|
||||||
} else if (status === "outside") {
|
} else if (status === "inside") {
|
||||||
path.replaceWith(
|
path.replaceWith(template.ast`${state.addHelper("tdz")}("${node.name}")`);
|
||||||
t.throwStatement(
|
|
||||||
t.inherits(
|
|
||||||
t.newExpression(t.identifier("ReferenceError"), [
|
|
||||||
t.stringLiteral(
|
|
||||||
`${node.name} is not defined - temporal dead zone`,
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
node,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -87,14 +76,14 @@ export const visitor = {
|
|||||||
const id = ids[name];
|
const id = ids[name];
|
||||||
|
|
||||||
if (isReference(id, path.scope, state)) {
|
if (isReference(id, path.scope, state)) {
|
||||||
nodes.push(buildTDZAssert(id, state));
|
nodes.push(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (nodes.length) {
|
if (nodes.length) {
|
||||||
node._ignoreBlockScopingTDZ = true;
|
node._ignoreBlockScopingTDZ = true;
|
||||||
nodes.push(node);
|
nodes.push(node);
|
||||||
path.replaceWithMultiple(nodes.map(t.expressionStatement));
|
path.replaceWithMultiple(nodes.map(n => t.expressionStatement(n)));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@ -5,6 +5,7 @@ if (x) {
|
|||||||
|
|
||||||
var innerScope = true;
|
var innerScope = true;
|
||||||
var res = transform(code, {
|
var res = transform(code, {
|
||||||
|
configFile: false,
|
||||||
plugins: opts.plugins.concat([
|
plugins: opts.plugins.concat([
|
||||||
function (b) {
|
function (b) {
|
||||||
var t = b.types;
|
var t = b.types;
|
||||||
@ -34,3 +35,4 @@ if (x) {
|
|||||||
}`;
|
}`;
|
||||||
|
|
||||||
expect(res.code).toBe(expected);
|
expect(res.code).toBe(expected);
|
||||||
|
expect(innerScope).toBe(false);
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
f();
|
expect(() => {
|
||||||
|
f();
|
||||||
|
|
||||||
const f = function f() {}
|
const f = function f() {}
|
||||||
|
}).toThrow(ReferenceError);
|
||||||
|
|||||||
@ -0,0 +1,3 @@
|
|||||||
|
f();
|
||||||
|
|
||||||
|
const f = function f() {}
|
||||||
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"throws": "f is not defined - temporal dead zone"
|
|
||||||
}
|
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
babelHelpers.tdz("f")();
|
||||||
|
|
||||||
|
var f = function f() {};
|
||||||
@ -1 +1,3 @@
|
|||||||
let { b: d } = { d }
|
expect(() => {
|
||||||
|
let { b: d } = { d }
|
||||||
|
}).toThrow(ReferenceError);
|
||||||
@ -0,0 +1 @@
|
|||||||
|
let { b: d } = { d }
|
||||||
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"throws": "d is not defined - temporal dead zone"
|
|
||||||
}
|
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
var {
|
||||||
|
b: d
|
||||||
|
} = {
|
||||||
|
d: babelHelpers.tdz("d")
|
||||||
|
};
|
||||||
7
packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-after/exec.js
vendored
Normal file
7
packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-after/exec.js
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
expect(() => {
|
||||||
|
function f() {
|
||||||
|
x;
|
||||||
|
}
|
||||||
|
let x;
|
||||||
|
f();
|
||||||
|
}).not.toThrow();
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
function f() {
|
||||||
|
x;
|
||||||
|
}
|
||||||
|
|
||||||
|
var x;
|
||||||
|
f();
|
||||||
7
packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-before/exec.js
vendored
Normal file
7
packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-before/exec.js
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
expect(() => {
|
||||||
|
function f() {
|
||||||
|
x;
|
||||||
|
}
|
||||||
|
f();
|
||||||
|
let x;
|
||||||
|
}).toThrow(ReferenceError);
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
function f() {
|
||||||
|
x;
|
||||||
|
}
|
||||||
|
f();
|
||||||
|
let x;
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
function f() {
|
||||||
|
babelHelpers.tdz("x");
|
||||||
|
}
|
||||||
|
|
||||||
|
f();
|
||||||
|
var x;
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
expect(() => {
|
||||||
|
function f() { x }
|
||||||
|
Math.random() === 2 && f();
|
||||||
|
let x;
|
||||||
|
f();
|
||||||
|
}).not.toThrow();
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
function f() { x }
|
||||||
|
Math.random() === 2 && f();
|
||||||
|
let x;
|
||||||
|
f();
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
var x = babelHelpers.temporalUndefined;
|
||||||
|
|
||||||
|
function f() {
|
||||||
|
babelHelpers.temporalRef(x, "x");
|
||||||
|
}
|
||||||
|
|
||||||
|
Math.random() === 2 && f();
|
||||||
|
x = void 0;
|
||||||
|
f();
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
function f() { x }
|
||||||
|
Math.random() === 2 && f();
|
||||||
|
let x = 3;
|
||||||
|
|
||||||
|
expect(x).toBe(3);
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
function f() { x }
|
||||||
|
Math.random() === 2 && f();
|
||||||
|
let x = 3;
|
||||||
|
|
||||||
|
expect(x).toBe(3);
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
var x = babelHelpers.temporalUndefined;
|
||||||
|
|
||||||
|
function f() {
|
||||||
|
babelHelpers.temporalRef(x, "x");
|
||||||
|
}
|
||||||
|
|
||||||
|
Math.random() === 2 && f();
|
||||||
|
x = 3;
|
||||||
|
expect(x).toBe(3);
|
||||||
29
packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-maybe/exec.js
vendored
Normal file
29
packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-maybe/exec.js
vendored
Normal file
@ -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();
|
||||||
3
packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-maybe/input.js
vendored
Normal file
3
packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-call-maybe/input.js
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
function f() { x }
|
||||||
|
Math.random() && f();
|
||||||
|
let x;
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
var x = babelHelpers.temporalUndefined;
|
||||||
|
|
||||||
|
function f() {
|
||||||
|
babelHelpers.temporalRef(x, "x");
|
||||||
|
}
|
||||||
|
|
||||||
|
Math.random() && f();
|
||||||
|
x = void 0;
|
||||||
|
void 0;
|
||||||
@ -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);
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
function f() {
|
||||||
|
return function() { x };
|
||||||
|
}
|
||||||
|
f();
|
||||||
|
let x;
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
var x = babelHelpers.temporalUndefined;
|
||||||
|
|
||||||
|
function f() {
|
||||||
|
return function () {
|
||||||
|
babelHelpers.temporalRef(x, "x");
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
f();
|
||||||
|
x = void 0;
|
||||||
|
void 0;
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
expect(() => {
|
||||||
|
function f(i) {
|
||||||
|
if (i) f(i - 1);
|
||||||
|
x;
|
||||||
|
}
|
||||||
|
|
||||||
|
let x;
|
||||||
|
f(3);
|
||||||
|
}).not.toThrow();
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
function f(i) {
|
||||||
|
if (i) f(i - 1);
|
||||||
|
x;
|
||||||
|
}
|
||||||
|
|
||||||
|
let x;
|
||||||
|
f(3);
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
function f(i) {
|
||||||
|
if (i) f(i - 1);
|
||||||
|
x;
|
||||||
|
}
|
||||||
|
|
||||||
|
var x;
|
||||||
|
f(3);
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
expect(() => {
|
||||||
|
function f(i) {
|
||||||
|
if (i) f(i - 1);
|
||||||
|
x;
|
||||||
|
}
|
||||||
|
|
||||||
|
f(3);
|
||||||
|
let x;
|
||||||
|
}).toThrow(ReferenceError);
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
function f(i) {
|
||||||
|
if (i) f(i - 1);
|
||||||
|
x;
|
||||||
|
}
|
||||||
|
|
||||||
|
f(3);
|
||||||
|
let x;
|
||||||
@ -0,0 +1,7 @@
|
|||||||
|
function f(i) {
|
||||||
|
if (i) f(i - 1);
|
||||||
|
babelHelpers.tdz("x");
|
||||||
|
}
|
||||||
|
|
||||||
|
f(3);
|
||||||
|
var x;
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
expect(() => {
|
||||||
|
function f(i) {
|
||||||
|
return () => {
|
||||||
|
x;
|
||||||
|
f(i - 1);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const g = f(1);
|
||||||
|
let x;
|
||||||
|
g();
|
||||||
|
}).not.toThrow();
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
function f(i) {
|
||||||
|
return () => {
|
||||||
|
x;
|
||||||
|
f(i - 1);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const g = f(1);
|
||||||
|
let x;
|
||||||
|
g();
|
||||||
@ -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();
|
||||||
3
packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-ref/input.js
vendored
Normal file
3
packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-ref/input.js
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
function f() { x }
|
||||||
|
maybeCall(f);
|
||||||
|
let x;
|
||||||
9
packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-ref/output.js
vendored
Normal file
9
packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/function-ref/output.js
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
var x = babelHelpers.temporalUndefined;
|
||||||
|
|
||||||
|
function f() {
|
||||||
|
babelHelpers.temporalRef(x, "x");
|
||||||
|
}
|
||||||
|
|
||||||
|
maybeCall(f);
|
||||||
|
x = void 0;
|
||||||
|
void 0;
|
||||||
@ -1,3 +1,5 @@
|
|||||||
f();
|
expect(() => {
|
||||||
|
f();
|
||||||
|
|
||||||
function f() {}
|
function f() {}
|
||||||
|
}).not.toThrow();
|
||||||
|
|||||||
3
packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/hoisted-function/input.js
vendored
Normal file
3
packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/hoisted-function/input.js
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
f();
|
||||||
|
|
||||||
|
function f() {}
|
||||||
3
packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/hoisted-function/output.js
vendored
Normal file
3
packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/hoisted-function/output.js
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
f();
|
||||||
|
|
||||||
|
function f() {}
|
||||||
@ -1,3 +1,5 @@
|
|||||||
x = 3;
|
expect(() => {
|
||||||
|
x = 3;
|
||||||
|
|
||||||
var x;
|
var x;
|
||||||
|
}).not.toThrow();
|
||||||
|
|||||||
3
packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/hoisted-var/input.js
vendored
Normal file
3
packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/hoisted-var/input.js
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
x = 3;
|
||||||
|
|
||||||
|
var x;
|
||||||
2
packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/hoisted-var/output.js
vendored
Normal file
2
packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/hoisted-var/output.js
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
x = 3;
|
||||||
|
var x;
|
||||||
@ -1,3 +1,6 @@
|
|||||||
{
|
{
|
||||||
"plugins": [["transform-block-scoping", { "tdz": true }]]
|
"plugins": [
|
||||||
|
["transform-block-scoping", { "tdz": true }],
|
||||||
|
["external-helpers", { "helperVersion": "7.1000.0" }]
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1 +1,3 @@
|
|||||||
let x = x;
|
expect(() => {
|
||||||
|
let x = x;
|
||||||
|
}).toThrow(ReferenceError);
|
||||||
|
|||||||
1
packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/self-reference/input.js
vendored
Normal file
1
packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/self-reference/input.js
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
let x = x;
|
||||||
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"throws": "x is not defined - temporal dead zone"
|
|
||||||
}
|
|
||||||
1
packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/self-reference/output.js
vendored
Normal file
1
packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/self-reference/output.js
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
var x = babelHelpers.tdz("x");
|
||||||
@ -1,3 +1,8 @@
|
|||||||
var a = 5;
|
expect(() => {
|
||||||
if (a){ console.log(a); let a = 2; }
|
var a = 5;
|
||||||
console.log(a);
|
if (a) {
|
||||||
|
a;
|
||||||
|
let a = 2;
|
||||||
|
}
|
||||||
|
a;
|
||||||
|
}).toThrow(ReferenceError);
|
||||||
|
|||||||
6
packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/shadow-outer-var/input.js
vendored
Normal file
6
packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/shadow-outer-var/input.js
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
var a = 5;
|
||||||
|
if (a) {
|
||||||
|
a;
|
||||||
|
let a = 2;
|
||||||
|
}
|
||||||
|
a;
|
||||||
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"throws": "a is not defined - temporal dead zone"
|
|
||||||
}
|
|
||||||
8
packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/shadow-outer-var/output.js
vendored
Normal file
8
packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/shadow-outer-var/output.js
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
var a = 5;
|
||||||
|
|
||||||
|
if (a) {
|
||||||
|
babelHelpers.tdz("a");
|
||||||
|
var _a = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
a;
|
||||||
4
packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/simple-assign/exec.js
vendored
Normal file
4
packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/simple-assign/exec.js
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
expect(() => {
|
||||||
|
i = 2;
|
||||||
|
let i
|
||||||
|
}).toThrow(ReferenceError);
|
||||||
2
packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/simple-assign/input.js
vendored
Normal file
2
packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/simple-assign/input.js
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
i = 2;
|
||||||
|
let i
|
||||||
3
packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/simple-assign/output.js
vendored
Normal file
3
packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/simple-assign/output.js
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
babelHelpers.tdz("i");
|
||||||
|
i = 2;
|
||||||
|
var i;
|
||||||
@ -1,2 +1,4 @@
|
|||||||
i
|
expect(() => {
|
||||||
let i
|
i
|
||||||
|
let i
|
||||||
|
}).toThrow(ReferenceError);
|
||||||
|
|||||||
2
packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/simple-reference/input.js
vendored
Normal file
2
packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/simple-reference/input.js
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
i
|
||||||
|
let i
|
||||||
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"throws": "i is not defined - temporal dead zone"
|
|
||||||
}
|
|
||||||
2
packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/simple-reference/output.js
vendored
Normal file
2
packages/babel-plugin-transform-block-scoping/test/fixtures/tdz/simple-reference/output.js
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
babelHelpers.tdz("i");
|
||||||
|
var i;
|
||||||
@ -102,7 +102,7 @@ function getConstantViolationsBefore(binding, path, functions) {
|
|||||||
return violations.filter(violation => {
|
return violations.filter(violation => {
|
||||||
violation = violation.resolve();
|
violation = violation.resolve();
|
||||||
const status = violation._guessExecutionStatusRelativeTo(path);
|
const status = violation._guessExecutionStatusRelativeTo(path);
|
||||||
if (functions && status === "function") functions.push(violation);
|
if (functions && status === "unknown") functions.push(violation);
|
||||||
return status === "before";
|
return status === "before";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -206,6 +206,75 @@ export function willIMaybeExecuteBefore(target) {
|
|||||||
return this._guessExecutionStatusRelativeTo(target) !== "after";
|
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.
|
* 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.
|
* 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
|
// check if the two paths are in different functions, we can't track execution of these
|
||||||
const targetFuncParent =
|
const funcParent = {
|
||||||
target.scope.getFunctionParent() || target.scope.getProgramParent();
|
this: getOuterFunction(this),
|
||||||
const selfFuncParent =
|
target: getOuterFunction(target),
|
||||||
this.scope.getFunctionParent() || target.scope.getProgramParent();
|
};
|
||||||
|
|
||||||
// here we check the `node` equality as sometimes we may have different paths for the
|
// here we check the `node` equality as sometimes we may have different paths for the
|
||||||
// same node due to path thrashing
|
// same node due to path thrashing
|
||||||
if (targetFuncParent.node !== selfFuncParent.node) {
|
if (funcParent.target.node !== funcParent.this.node) {
|
||||||
const status = this._guessExecutionStatusRelativeToDifferentFunctions(
|
return this._guessExecutionStatusRelativeToDifferentFunctions(
|
||||||
targetFuncParent,
|
funcParent.target,
|
||||||
);
|
);
|
||||||
if (status) {
|
|
||||||
return status;
|
|
||||||
} else {
|
|
||||||
target = targetFuncParent.path;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const targetPaths = target.getAncestry();
|
const paths = {
|
||||||
if (targetPaths.indexOf(this) >= 0) return "after";
|
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
|
// get ancestor where the branches intersect
|
||||||
let commonPath;
|
let commonPath;
|
||||||
let targetIndex;
|
const commonIndex = { target: 0, this: 0 };
|
||||||
let selfIndex;
|
|
||||||
for (selfIndex = 0; selfIndex < selfPaths.length; selfIndex++) {
|
while (!commonPath && commonIndex.this < paths.this.length) {
|
||||||
const selfPath = selfPaths[selfIndex];
|
const path = paths.this[commonIndex.this];
|
||||||
targetIndex = targetPaths.indexOf(selfPath);
|
commonIndex.target = paths.target.indexOf(path);
|
||||||
if (targetIndex >= 0) {
|
if (commonIndex.target >= 0) {
|
||||||
commonPath = selfPath;
|
commonPath = path;
|
||||||
break;
|
} else {
|
||||||
|
commonIndex.this++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!commonPath) {
|
|
||||||
return "before";
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the relationship paths that associate these nodes to their common ancestor
|
if (!commonPath) {
|
||||||
const targetRelationship = targetPaths[targetIndex - 1];
|
throw new Error(
|
||||||
const selfRelationship = selfPaths[selfIndex - 1];
|
"Internal Babel error - The two compared nodes" +
|
||||||
if (!targetRelationship || !selfRelationship) {
|
" don't appear to belong to the same program.",
|
||||||
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
|
// container list so let's see which one is after the other
|
||||||
|
// e.g. [ THIS, TARGET ]
|
||||||
if (
|
if (
|
||||||
targetRelationship.listKey &&
|
divergence.target.listKey &&
|
||||||
targetRelationship.container === selfRelationship.container
|
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
|
// otherwise we're associated by a parent node, check which key comes before the other
|
||||||
const keys = t.VISITOR_KEYS[commonPath.type];
|
const keys = t.VISITOR_KEYS[commonPath.type];
|
||||||
const targetKeyPosition = keys.indexOf(targetRelationship.key);
|
const keyPosition = {
|
||||||
const selfKeyPosition = keys.indexOf(selfRelationship.key);
|
this: keys.indexOf(divergence.this.parentKey),
|
||||||
return targetKeyPosition > selfKeyPosition ? "before" : "after";
|
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(
|
export function _guessExecutionStatusRelativeToDifferentFunctions(
|
||||||
targetFuncParent,
|
target: NodePath,
|
||||||
) {
|
): RelativeExecutionStatus {
|
||||||
const targetFuncPath = targetFuncParent.path;
|
if (!target.isFunctionDeclaration()) return "unknown";
|
||||||
if (!targetFuncPath.isFunctionDeclaration()) return;
|
|
||||||
|
|
||||||
// so we're in a completely different function, if this is a function declaration
|
// 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
|
// then we can be a bit smarter and handle cases where the function is either
|
||||||
// a. not called at all (part of an export)
|
// a. not called at all (part of an export)
|
||||||
// b. called directly
|
// b. called directly
|
||||||
const binding = targetFuncPath.scope.getBinding(targetFuncPath.node.id.name);
|
const binding = target.scope.getBinding(target.node.id.name);
|
||||||
|
|
||||||
// no references!
|
// no references!
|
||||||
if (!binding.references) return "before";
|
if (!binding.references) return "before";
|
||||||
|
|
||||||
const referencePaths: Array<NodePath> = binding.referencePaths;
|
const referencePaths: Array<NodePath> = 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;
|
let allStatus;
|
||||||
|
|
||||||
// verify that all the calls have the same execution status
|
// verify that all the calls have the same execution status
|
||||||
for (const path of referencePaths) {
|
for (const path of referencePaths) {
|
||||||
// if a reference is a child of the function we're checking against then we can
|
// if a reference is a child of the function we're checking against then we can
|
||||||
// safely ignore it
|
// safely ignore it
|
||||||
const childOfFunction = !!path.find(
|
const childOfFunction = !!path.find(path => path.node === target.node);
|
||||||
path => path.node === targetFuncPath.node,
|
|
||||||
);
|
|
||||||
if (childOfFunction) continue;
|
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);
|
const status = this._guessExecutionStatusRelativeTo(path);
|
||||||
|
|
||||||
if (allStatus) {
|
executionOrderCheckedNodes.delete(path.node);
|
||||||
if (allStatus !== status) return;
|
|
||||||
|
if (allStatus && allStatus !== status) {
|
||||||
|
return "unknown";
|
||||||
} else {
|
} else {
|
||||||
allStatus = status;
|
allStatus = status;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user