diff --git a/packages/babel-plugin-transform-react-constant-elements/test/fixtures/constant-elements/dont-hoist-before-declaration/actual.js b/packages/babel-plugin-transform-react-constant-elements/test/fixtures/constant-elements/dont-hoist-before-declaration/actual.js new file mode 100644 index 0000000000..d712e9e19a --- /dev/null +++ b/packages/babel-plugin-transform-react-constant-elements/test/fixtures/constant-elements/dont-hoist-before-declaration/actual.js @@ -0,0 +1,11 @@ +function render() { + const bar = "bar", renderFoo = () => ; + + return renderFoo(); +} + +function render() { + const bar = "bar", renderFoo = () => , baz = "baz"; + + return renderFoo(); +} diff --git a/packages/babel-plugin-transform-react-constant-elements/test/fixtures/constant-elements/dont-hoist-before-declaration/expected.js b/packages/babel-plugin-transform-react-constant-elements/test/fixtures/constant-elements/dont-hoist-before-declaration/expected.js new file mode 100644 index 0000000000..d85f9f3244 --- /dev/null +++ b/packages/babel-plugin-transform-react-constant-elements/test/fixtures/constant-elements/dont-hoist-before-declaration/expected.js @@ -0,0 +1,15 @@ +function render() { + const bar = "bar", + _ref = , + renderFoo = () => _ref; + + return renderFoo(); +} + +function render() { + const bar = "bar", + renderFoo = () => , + baz = "baz"; + + return renderFoo(); +} \ No newline at end of file diff --git a/packages/babel-traverse/src/path/lib/hoister.js b/packages/babel-traverse/src/path/lib/hoister.js index f218eaf2ad..3776b2d30e 100644 --- a/packages/babel-traverse/src/path/lib/hoister.js +++ b/packages/babel-traverse/src/path/lib/hoister.js @@ -83,7 +83,7 @@ export default class PathHoister { if (binding.kind === "param") continue; // if this binding appears after our attachment point then don't hoist it - if (binding.path.getStatementParent().key > path.key) return; + if (this.getAttachmentParentForPath(binding.path).key > path.key) return; } } @@ -105,16 +105,25 @@ export default class PathHoister { return scope.path.get("body").get("body")[0]; } else { // doesn't need to be be attached to this scope - return this.getNextScopeStatementParent(); + return this.getNextScopeAttachmentParent(); } } else if (scope.path.isProgram()) { - return this.getNextScopeStatementParent(); + return this.getNextScopeAttachmentParent(); } } - getNextScopeStatementParent() { + getNextScopeAttachmentParent() { let scope = this.scopes.pop(); - if (scope) return scope.path.getStatementParent(); + if (scope) return this.getAttachmentParentForPath(scope.path); + } + + getAttachmentParentForPath(path) { + do { + if (!path.parentPath || + (Array.isArray(path.container) && path.isStatement()) || + (path.isVariableDeclarator() && path.parentPath.node.declarations.length > 1)) + return path; + } while ((path = path.parentPath)); } hasOwnParamBindings(scope) { @@ -144,10 +153,10 @@ export default class PathHoister { // generate declaration and insert it to our point let uid = attachTo.scope.generateUidIdentifier("ref"); + let declarator = t.variableDeclarator(uid, this.path.node); + attachTo.insertBefore([ - t.variableDeclaration("var", [ - t.variableDeclarator(uid, this.path.node) - ]) + attachTo.isVariableDeclarator() ? declarator : t.variableDeclaration("var", [declarator]) ]); let parent = this.path.parentPath;