diff --git a/packages/babel-plugin-transform-for-of/test/fixtures/spec/exported-function-parent/input.mjs b/packages/babel-plugin-transform-for-of/test/fixtures/spec/exported-function-parent/input.mjs new file mode 100644 index 000000000000..ad0bb3453076 --- /dev/null +++ b/packages/babel-plugin-transform-for-of/test/fixtures/spec/exported-function-parent/input.mjs @@ -0,0 +1,10 @@ +const keys = [] +export const foo = () => { + for (const key of keys) {} +} + +export const bar = () => { + const keys = [] + for (const key of keys) {} +} + diff --git a/packages/babel-plugin-transform-for-of/test/fixtures/spec/exported-function-parent/output.mjs b/packages/babel-plugin-transform-for-of/test/fixtures/spec/exported-function-parent/output.mjs new file mode 100644 index 000000000000..00997d4d77c1 --- /dev/null +++ b/packages/babel-plugin-transform-for-of/test/fixtures/spec/exported-function-parent/output.mjs @@ -0,0 +1,22 @@ +const keys = []; +export const foo = () => { + var _iterator = babelHelpers.createForOfIteratorHelper(keys), + _step; + + try { + for (_iterator.s(); !(_step = _iterator.n()).done;) { + const key = _step.value; + } + } catch (err) { + _iterator.e(err); + } finally { + _iterator.f(); + } +}; +export const bar = () => { + const keys = []; + + for (var _i = 0, _keys = keys; _i < _keys.length; _i++) { + const key = _keys[_i]; + } +}; diff --git a/packages/babel-plugin-transform-for-of/test/fixtures/spec/function-parent/input.js b/packages/babel-plugin-transform-for-of/test/fixtures/spec/function-parent/input.js new file mode 100644 index 000000000000..8f79a4beb121 --- /dev/null +++ b/packages/babel-plugin-transform-for-of/test/fixtures/spec/function-parent/input.js @@ -0,0 +1,17 @@ +const keys = [] + +function a() { + for (const key of keys) {} +} + +const b = () => { + for (const key of keys) {} +} + +const c = function () { + for (const key of keys) {} +} + +const { foo } = () => { + for (const key of keys) {} +} diff --git a/packages/babel-plugin-transform-for-of/test/fixtures/spec/function-parent/output.js b/packages/babel-plugin-transform-for-of/test/fixtures/spec/function-parent/output.js new file mode 100644 index 000000000000..c42b5c524243 --- /dev/null +++ b/packages/babel-plugin-transform-for-of/test/fixtures/spec/function-parent/output.js @@ -0,0 +1,36 @@ +const keys = []; + +function a() { + for (var _i = 0, _keys = keys; _i < _keys.length; _i++) { + const key = _keys[_i]; + } +} + +const b = () => { + for (var _i2 = 0, _keys2 = keys; _i2 < _keys2.length; _i2++) { + const key = _keys2[_i2]; + } +}; + +const c = function () { + for (var _i3 = 0, _keys3 = keys; _i3 < _keys3.length; _i3++) { + const key = _keys3[_i3]; + } +}; + +const { + foo +} = () => { + var _iterator = babelHelpers.createForOfIteratorHelper(keys), + _step; + + try { + for (_iterator.s(); !(_step = _iterator.n()).done;) { + const key = _step.value; + } + } catch (err) { + _iterator.e(err); + } finally { + _iterator.f(); + } +}; diff --git a/packages/babel-plugin-transform-for-of/test/fixtures/spec/infer-type/input.js b/packages/babel-plugin-transform-for-of/test/fixtures/spec/infer-type/input.js new file mode 100644 index 000000000000..9d91ad61c691 --- /dev/null +++ b/packages/babel-plugin-transform-for-of/test/fixtures/spec/infer-type/input.js @@ -0,0 +1,3 @@ +const foo = [] + +for (const i of foo) {} diff --git a/packages/babel-plugin-transform-for-of/test/fixtures/spec/infer-type/output.js b/packages/babel-plugin-transform-for-of/test/fixtures/spec/infer-type/output.js new file mode 100644 index 000000000000..5a965d2c1f83 --- /dev/null +++ b/packages/babel-plugin-transform-for-of/test/fixtures/spec/infer-type/output.js @@ -0,0 +1,5 @@ +const foo = []; + +for (var _i = 0, _foo = foo; _i < _foo.length; _i++) { + const i = _foo[_i]; +} diff --git a/packages/babel-plugin-transform-for-of/test/fixtures/spec/nested-function-parent/input.js b/packages/babel-plugin-transform-for-of/test/fixtures/spec/nested-function-parent/input.js new file mode 100644 index 000000000000..eb6d64d0917c --- /dev/null +++ b/packages/babel-plugin-transform-for-of/test/fixtures/spec/nested-function-parent/input.js @@ -0,0 +1,24 @@ +const keys = [] +const d = () => { + const e = () => { + for (const key of keys) {} + } +} + +const a = () => { + const keys = [] + const c = () => { + for (const key of keys) {} + } + return c +} + +var _keys = [] +const b = () => { + const c = () => { + for (const key of _keys) {} + } + return c +} + +_keys = 'foo' diff --git a/packages/babel-plugin-transform-for-of/test/fixtures/spec/nested-function-parent/output.js b/packages/babel-plugin-transform-for-of/test/fixtures/spec/nested-function-parent/output.js new file mode 100644 index 000000000000..7e8bfcd66241 --- /dev/null +++ b/packages/babel-plugin-transform-for-of/test/fixtures/spec/nested-function-parent/output.js @@ -0,0 +1,44 @@ +const keys = []; + +const d = () => { + const e = () => { + for (var _i = 0, _keys2 = keys; _i < _keys2.length; _i++) { + const key = _keys2[_i]; + } + }; +}; + +const a = () => { + const keys = []; + + const c = () => { + for (var _i2 = 0, _keys3 = keys; _i2 < _keys3.length; _i2++) { + const key = _keys3[_i2]; + } + }; + + return c; +}; + +var _keys = []; + +const b = () => { + const c = () => { + var _iterator = babelHelpers.createForOfIteratorHelper(_keys), + _step; + + try { + for (_iterator.s(); !(_step = _iterator.n()).done;) { + const key = _step.value; + } + } catch (err) { + _iterator.e(err); + } finally { + _iterator.f(); + } + }; + + return c; +}; + +_keys = 'foo'; diff --git a/packages/babel-traverse/src/path/introspection.ts b/packages/babel-traverse/src/path/introspection.ts index 0e01e4f20cc9..efd8402a17cf 100644 --- a/packages/babel-traverse/src/path/introspection.ts +++ b/packages/babel-traverse/src/path/introspection.ts @@ -219,7 +219,7 @@ export function willIMaybeExecuteBefore(this: NodePath, target): boolean { return this._guessExecutionStatusRelativeTo(target) !== "after"; } -function getOuterFunction(path) { +function getOuterFunction(path: NodePath) { return (path.scope.getFunctionParent() || path.scope.getProgramParent()).path; } @@ -385,8 +385,12 @@ export function _guessExecutionStatusRelativeToDifferentFunctions( this: NodePath, target: NodePath, ): RelativeExecutionStatus { + const targetIsGenericFunctionExpression = + (target.isArrowFunctionExpression() || target.isFunctionExpression()) && + t.isVariableDeclarator(target.parent) && + t.isIdentifier(target.parent.id); if ( - !target.isFunctionDeclaration() || + (!target.isFunctionDeclaration() && !targetIsGenericFunctionExpression) || target.parentPath.isExportDeclaration() ) { return "unknown"; @@ -396,7 +400,10 @@ export function _guessExecutionStatusRelativeToDifferentFunctions( // 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 = target.scope.getBinding(target.node.id.name); + const bindingName = targetIsGenericFunctionExpression + ? ((target.parent as t.VariableDeclarator).id as t.Identifier).name + : (target.node as t.FunctionDeclaration).id.name; + const binding = target.scope.getBinding(bindingName); // no references! if (!binding.references) return "before"; @@ -412,7 +419,10 @@ export function _guessExecutionStatusRelativeToDifferentFunctions( const childOfFunction = !!path.find(path => path.node === target.node); if (childOfFunction) continue; - if (path.key !== "callee" || !path.parentPath.isCallExpression()) { + if ( + (path.key !== "callee" || !path.parentPath.isCallExpression()) && + !path.parentPath.isReturnStatement() + ) { // This function is passed as a reference, so we don't // know when it will be called. return "unknown"; diff --git a/packages/babel-traverse/test/inference.js b/packages/babel-traverse/test/inference.js index dde8bd22746e..b86c45660991 100644 --- a/packages/babel-traverse/test/inference.js +++ b/packages/babel-traverse/test/inference.js @@ -149,6 +149,36 @@ describe("inference", function () { t.isGenericTypeAnnotation(type) && type.id.name === "Function", ).toBeTruthy(); }); + it("should infer Array type from outside of function declaration", function () { + const path = getPath(` + const arr = []; function func() { const a = arr }`).get( + "body.1.body.body.0.declarations.0", + ); + const type = path.getTypeAnnotation(); + expect( + t.isGenericTypeAnnotation(type) && type.id.name === "Array", + ).toBeTruthy(); + }); + it("should infer Array type from outside of function expression", function () { + const path = getPath(` + const arr = []; const func = function () { const a = arr }`).get( + "body.1.declarations.0.init.body.body.0.declarations.0", + ); + const type = path.getTypeAnnotation(); + expect( + t.isGenericTypeAnnotation(type) && type.id.name === "Array", + ).toBeTruthy(); + }); + it("should infer Array type from outside of arrow function", function () { + const path = getPath(` + const arr = []; const func = () => { const a = arr }`).get( + "body.1.declarations.0.init.body.body.0.declarations.0", + ); + const type = path.getTypeAnnotation(); + expect( + t.isGenericTypeAnnotation(type) && type.id.name === "Array", + ).toBeTruthy(); + }); it("should infer call return type using function", function () { const path = getPath("(function (): string {})()") .get("body")[0]