Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

plugin-transform-block-scoping renames function declaration #10046

Closed
mischnic opened this issue May 30, 2019 · 10 comments · Fixed by #11801
Closed

plugin-transform-block-scoping renames function declaration #10046

mischnic opened this issue May 30, 2019 · 10 comments · Fixed by #11801
Labels
i: bug outdated A closed issue/PR that is archived due to age. Recommended to make a new issue

Comments

@mischnic
Copy link
Contributor

Bug Report

Current Behavior
@babel/plugin-transform-block-scoping (and maybe some other plugin in preset-env) incorrectly renames a FunctionDeclaration in a nested block even though it should behave like a var declaration.

Input Code
REPL

if(true) {
  function run() {
    return true;
  }
}

function test() {
  return run();
}

Note: turning function run() { into var run = function() { behaves correctly.

Expected behavior/code
Same as Input

Actual output

if (true) {
  function _run() { // <-------
    return true;
  }
}

function test() {
  return run();
}

Babel Configuration (.babelrc, package.json, cli command)

{
  "plugins": ["@babel/plugin-transform-block-scoping"]
}

or

{
  "presets": ["@babel/preset-env"]
}

Environment

  • Babel version(s): 7.4.0
  • Node/npm version: Node 10
  • OS: macOS
  • Monorepo: -
  • How you are using Babel: via babel-core
@babel-bot
Copy link
Collaborator

Hey @mischnic! We really appreciate you taking the time to report an issue. The collaborators
on this project attempt to help as many people as possible, but we're a limited number of volunteers,
so it's possible this won't be addressed swiftly.

If you need any help, or just have general Babel or JavaScript questions, we have a vibrant Slack
community
that typically always has someone willing to help. You can sign-up here
for an invite.

@mischnic
Copy link
Contributor Author

Not sure if this is related: in the example above, the function inside the if block isn't on the program scope

@mischnic
Copy link
Contributor Author

How I would expect it to behave:

diff --git a/packages/babel-plugin-transform-block-scoping/src/index.js b/packages/babel-plugin-transform-block-scoping/src/index.js
index cc63c554..cdf9b863 100644
--- a/packages/babel-plugin-transform-block-scoping/src/index.js
+++ b/packages/babel-plugin-transform-block-scoping/src/index.js
@@ -701,11 +701,7 @@ class BlockScoping {
 
     const addDeclarationsFromChild = (path, node) => {
       node = node || path.node;
-      if (
-        t.isClassDeclaration(node) ||
-        t.isFunctionDeclaration(node) ||
-        isBlockScoped(node)
-      ) {
+      if (t.isClassDeclaration(node) || isBlockScoped(node)) {
         if (isBlockScoped(node)) {
           convertBlockScopedToVar(path, node, block, this.scope);
         }
diff --git a/packages/babel-traverse/src/scope/index.js b/packages/babel-traverse/src/scope/index.js
index 375d5827..2d1e4149 100644
--- a/packages/babel-traverse/src/scope/index.js
+++ b/packages/babel-traverse/src/scope/index.js
@@ -131,6 +131,13 @@ const collectorVisitor = {
     scope.getBlockParent().registerDeclaration(path);
   },
 
+  FunctionDeclaration(path) {
+    let scope =
+      path.parentPath.scope.getFunctionParent() ||
+      path.parentPath.scope.getProgramParent();
+    scope.registerDeclaration(path);
+  },
+
   ClassDeclaration(path) {
     const id = path.node.id;
     if (!id) return;
diff --git a/packages/babel-types/src/validators/isBlockScoped.js b/packages/babel-types/src/validators/isBlockScoped.js
index 51ae6e07..a02e53c9 100644
--- a/packages/babel-types/src/validators/isBlockScoped.js
+++ b/packages/babel-types/src/validators/isBlockScoped.js
@@ -6,5 +6,5 @@ import isLet from "./isLet";
  * Check if the input `node` is block scoped.
  */
 export default function isBlockScoped(node: Object): boolean {
-  return isFunctionDeclaration(node) || isClassDeclaration(node) || isLet(node);
+  return isClassDeclaration(node) || isLet(node);
 }

@mischnic
Copy link
Contributor Author

I would love to make a PR out of this, but without any comment on whether FunctionDeclaration is actually a BlockScoped or not....

@nicolo-ribaudo
Copy link
Member

Block scoped functions are hard to get right, since they behave differently in loose and strict mode.
Does that patch break any of our existing tests?

@mischnic
Copy link
Contributor Author

mischnic commented May 31, 2019

Yes it does 😐 :

scope › duplicate bindings › let and function in sub scope
scope › duplicate bindings › const and function in sub scope

(let foo;{function foo() {}} and const foo;{function foo() {}} seem to throw "Duplicate declaration" now -- the first works in v8 and foo is undefined, the second throws Missing initializer in const declaration)

babel-plugin-transform-block-scoping/general › issue 1051
babel-plugin-transform-block-scoping/general › issue 4363
babel-plugin-transform-block-scoping/general › switch
babel-plugin-transform-parameters/parameters › default iife 4253
babel-plugin-transform-parameters/parameters › default iife self
Details
Summary of all failing tests
 FAIL  packages/babel-traverse/test/scope.js
  ● scope › duplicate bindings › let and function in sub scope

    expect(received).not.toThrow()

    Error name:    "Error"
    Error message: "Duplicate declaration \"foo\""

          37 |   NodePath.get({
          38 |     hub: {
        > 39 |       buildError: (_, msg) => new Error(msg),
             |                               ^
          40 |     },
          41 |     parentPath: null,
          42 |     parent: ast,

          at Object.buildError (packages/babel-traverse/test/scope.js:39:31)
          at Scope.checkBlockScopedCollisions (packages/babel-traverse/lib/scope/index.js:349:22)
          at Scope.registerBinding (packages/babel-traverse/lib/scope/index.js:506:16)
          at Scope.registerDeclaration (packages/babel-traverse/lib/scope/index.js:441:12)
          at Object.FunctionDeclaration (packages/babel-traverse/lib/scope/index.js:192:11)
          at NodePath._call (packages/babel-traverse/lib/path/context.js:53:20)
          at NodePath.call (packages/babel-traverse/lib/path/context.js:40:17)
          at NodePath.visit (packages/babel-traverse/lib/path/context.js:88:12)
          at TraversalContext.visitQueue (packages/babel-traverse/lib/context.js:118:16)
          at TraversalContext.visitMultiple (packages/babel-traverse/lib/context.js:85:17)

      324 |         ];
      325 |
    > 326 |         expect(() => getPath(ast)).not.toThrow();
          |                                        ^
      327 |       });
      328 |     });
      329 |

      at Object.toThrow (packages/babel-traverse/test/scope.js:326:40)

  ● scope › duplicate bindings › const and function in sub scope

    expect(received).not.toThrow()

    Error name:    "Error"
    Error message: "Duplicate declaration \"foo\""

          37 |   NodePath.get({
          38 |     hub: {
        > 39 |       buildError: (_, msg) => new Error(msg),
             |                               ^
          40 |     },
          41 |     parentPath: null,
          42 |     parent: ast,

          at Object.buildError (packages/babel-traverse/test/scope.js:39:31)
          at Scope.checkBlockScopedCollisions (packages/babel-traverse/lib/scope/index.js:349:22)
          at Scope.registerBinding (packages/babel-traverse/lib/scope/index.js:506:16)
          at Scope.registerDeclaration (packages/babel-traverse/lib/scope/index.js:441:12)
          at Object.FunctionDeclaration (packages/babel-traverse/lib/scope/index.js:192:11)
          at NodePath._call (packages/babel-traverse/lib/path/context.js:53:20)
          at NodePath.call (packages/babel-traverse/lib/path/context.js:40:17)
          at NodePath.visit (packages/babel-traverse/lib/path/context.js:88:12)
          at TraversalContext.visitQueue (packages/babel-traverse/lib/context.js:118:16)
          at TraversalContext.visitMultiple (packages/babel-traverse/lib/context.js:85:17)

      324 |         ];
      325 |
    > 326 |         expect(() => getPath(ast)).not.toThrow();
          |                                        ^
      327 |       });
      328 |     });
      329 |

      at Object.toThrow (packages/babel-traverse/test/scope.js:326:40)

 FAIL  packages/babel-plugin-transform-block-scoping/test/index.js (10.67s)
  ● babel-plugin-transform-block-scoping/general › issue 1051

    Expected ..../babel/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-1051/output.js to match transform output.
    To autogenerate a passing version of this file, delete the file and re-run the tests.

    Diff:

    - Expected
    + Received

      foo.func1 = function () {
        if (cond1) {
          for (;;) {
            if (cond2) {
    -         var _ret = function () {
    -           function func2() {}
    +         var func2 = function () {};

    -           function func3() {}
    +         var func3 = function () {};

    -           func4(function () {
    -             func2();
    -           });
    -           return "break";
    -         }();
    -
    -         if (_ret === "break") break;
    +         func4(function () {
    +           func2();
    +         });
    +         break;
            }
          }
        }
      };

      333 |
      334 |       try {
    > 335 |         expect(actualCode).toEqualFile({
          |                            ^
      336 |           filename: expected.loc,
      337 |           code: expectCode
      338 |         });

      at run (packages/babel-helper-transform-fixture-test-runner/lib/index.js:335:28)
      at runTask (packages/babel-helper-transform-fixture-test-runner/lib/index.js:412:13)
      at Object.<anonymous> (packages/babel-helper-transform-fixture-test-runner/lib/index.js:436:15)

  ● babel-plugin-transform-block-scoping/general › issue 4363

    Expected ..../babel/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-4363/output.js to match transform output.
    To autogenerate a passing version of this file, delete the file and re-run the tests.

    Diff:

    - Expected
    + Received

      function WithoutCurlyBraces() {
    -   var _this = this;
    +   if (true) for (var k in kv) {
    +     var foo = function () {
    +       return this;
    +     };

    -   if (true) {
    -     var _loop = function (k) {
    -       function foo() {
    -         return this;
    -       }
    -
    -       function bar() {
    -         return foo.call(this);
    -       }
    -
    -       console.log(_this, k); // => undefined
    +     var bar = function () {
    +       return foo.call(this);
          };

    -     for (var k in kv) {
    -       _loop(k);
    -     }
    +     console.log(this, k); // => undefined
        }
      }

      function WithCurlyBraces() {
    -   var _this2 = this;
    -
        if (true) {
    -     var _loop2 = function (k) {
    -       function foo() {
    +     for (var k in kv) {
    +       var foo = function () {
              return this;
    -       }
    +       };

    -       function bar() {
    +       var bar = function () {
              return foo.call(this);
    -       }
    +       };

    -       console.log(_this2, k); // => 777
    -     };
    -
    -     for (var k in kv) {
    -       _loop2(k);
    +       console.log(this, k); // => 777
          }
        }
      }

      333 |
      334 |       try {
    > 335 |         expect(actualCode).toEqualFile({
          |                            ^
      336 |           filename: expected.loc,
      337 |           code: expectCode
      338 |         });

      at run (packages/babel-helper-transform-fixture-test-runner/lib/index.js:335:28)
      at runTask (packages/babel-helper-transform-fixture-test-runner/lib/index.js:412:13)
      at Object.<anonymous> (packages/babel-helper-transform-fixture-test-runner/lib/index.js:436:15)

  ● babel-plugin-transform-block-scoping/general › switch

    Expected ..../babel/packages/babel-plugin-transform-block-scoping/test/fixtures/general/switch/output.js to match transform output.
    To autogenerate a passing version of this file, delete the file and re-run the tests.

    Diff:

    - Expected
    + Received

    @@ -25,8 +25,8 @@
          var _d = 5;

        case false:
          class _e {}

    -     var _f = function () {};
    +     var f = function () {};

      }

      333 |
      334 |       try {
    > 335 |         expect(actualCode).toEqualFile({
          |                            ^
      336 |           filename: expected.loc,
      337 |           code: expectCode
      338 |         });

      at run (packages/babel-helper-transform-fixture-test-runner/lib/index.js:335:28)
      at runTask (packages/babel-helper-transform-fixture-test-runner/lib/index.js:412:13)
      at Object.<anonymous> (packages/babel-helper-transform-fixture-test-runner/lib/index.js:436:15)

 FAIL  packages/babel-plugin-transform-parameters/test/index.js (14.516s)
  ● babel-plugin-transform-parameters/parameters › default iife 4253

    Expected ..../babel/packages/babel-plugin-transform-parameters/test/fixtures/parameters/default-iife-4253/output.js to match transform output.
    To autogenerate a passing version of this file, delete the file and re-run the tests.

    Diff:

    - Expected
    + Received

    @@ -3,12 +3,14 @@
      function () {
        "use strict";

        function Ref() {
          var id = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ++Ref.nextID;
    -     babelHelpers.classCallCheck(this, Ref);
    -     this.id = id;
    +     return function () {
    +       babelHelpers.classCallCheck(this, Ref);
    +       this.id = id;
    +     }.apply(this);
        }

        return Ref;
      }();


      333 |
      334 |       try {
    > 335 |         expect(actualCode).toEqualFile({
          |                            ^
      336 |           filename: expected.loc,
      337 |           code: expectCode
      338 |         });

      at run (packages/babel-helper-transform-fixture-test-runner/lib/index.js:335:28)
      at Object.<anonymous> (packages/babel-helper-transform-fixture-test-runner/lib/index.js:430:30)

  ● babel-plugin-transform-parameters/parameters › default iife self

    Expected ..../babel/packages/babel-plugin-transform-parameters/test/fixtures/parameters/default-iife-self/output.js to match transform output.
    To autogenerate a passing version of this file, delete the file and re-run the tests.

    Diff:

    - Expected
    + Received

    @@ -3,12 +3,14 @@
      function () {
        "use strict";

        function Ref() {
          var ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : Ref;
    -     babelHelpers.classCallCheck(this, Ref);
    -     this.ref = ref;
    +     return function () {
    +       babelHelpers.classCallCheck(this, Ref);
    +       this.ref = ref;
    +     }.apply(this);
        }

        return Ref;
      }();


      333 |
      334 |       try {
    > 335 |         expect(actualCode).toEqualFile({
          |                            ^
      336 |           filename: expected.loc,
      337 |           code: expectCode
      338 |         });

      at run (packages/babel-helper-transform-fixture-test-runner/lib/index.js:335:28)
      at Object.<anonymous> (packages/babel-helper-transform-fixture-test-runner/lib/index.js:430:30)

@nicolo-ribaudo
Copy link
Member

The second one should be something like const foo = 2; { function foo() {} }. I'm suprised that it worked before 🤔

This seems to be a deeper problem in @babel/traverse (which will probably be harder to fix), or something else is buggy in the transform-block-scoping plugin.
If you are interseted in knowing the exact semantics of block-level functions, you can read tis part of the spec: https://tc39.github.io/ecma262/#sec-block-level-function-declarations-web-legacy-compatibility-semantics

If you want, you could open a WIP pr 😉

@mischnic
Copy link
Contributor Author

If you want, you could open a WIP pr 😉

😄, done: #10050

@ldco2016
Copy link

I believe I have an issue similar to this.

I recently upgraded from React-Native 0.53.3 to 0.59.9 and my project is not creating the main.jsbundle file. I thought perhaps its because its still using index.ios.js so I renamed it to index.js and removed index.android.js then I tried to run this: react-native bundle --entry-file ./index.js --platform ios --bundle-output ios/main.jsbundle --verbose

and I get this error:

Loading dependency graph, done.
transform[stderr]: Trace: The node type SpreadProperty has been renamed to SpreadElement
error App.js: Cannot read property 'bindings' of null
debug TypeError: Cannot read property 'bindings' of null
    at Scope.moveBindingTo (/Users/danale/Projects/engage-application.mobile/node_modules/@babel/traverse/lib/scope/index.js:864:13)
    at BlockScoping.updateScopeInfo (/Users/danale/Projects/engage-application.mobile/node_modules/babel-plugin-transform-es2015-block-scoping/lib/index.js:364:17)
    at BlockScoping.run (/Users/danale/Projects/engage-application.mobile/node_modules/babel-plugin-transform-es2015-block-scoping/lib/index.js:330:12)
    at PluginPass.BlockStatementSwitchStatementProgram (/Users/danale/Projects/engage-application.mobile/node_modules/babel-plugin-transform-es2015-block-scoping/lib/index.js:70:24)

I can't seem to find assistance anywhere, this is my package.json file:

"react": "16.8.3",
    "react-native": "0.59.9",
    "react-native-autoheight-webview": "0.6.1",
    "react-native-calendar-events": "1.6.1",
    "react-native-device-info": "0.21.5",
    "react-native-exception-handler": "2.8.9",
    "react-native-image-progress": "1.0.1",
    "react-native-immediate-phone-call": "1.0.0",
    "react-native-input-scroll-view": "1.6.7",
    "react-native-keyboard-aware-scroll-view": "0.4.4",
    "react-native-keyboard-manager": "4.0.13-12",
    "react-native-material-buttons": "0.5.0",
    "react-native-material-dropdown": "0.5.2",
    "react-native-material-tabs": "3.5.0",
    "react-native-material-textfield": "0.10.0",
    "react-native-onesignal": "3.0.7",
    "react-native-popup-menu": "0.8.3",
    "react-native-sentry": "0.32.0",
    "react-native-size-matters": "0.1.0",
    "react-native-splash-screen": "3.0.6",
    "react-native-svg": "6.3.1",
    "react-native-swipe-view": "https://github.com/jjd314/react-native-swipe-view",
    "react-native-tab-view": "1.3.2",
    "react-native-vector-icons": "6.1.0",
    "react-native-xcode-packager": "0.1.0",
    "react-navigation": "1.5.11",
    "react-redux": "5.0.6",
    "reactotron-react-native": "3.5.0",
    "reactotron-redux": "3.1.0",
    "recompose": "0.26.0",
    "redux": "4.0.1",
    "redux-thunk": "2.2.0",
    "replace-in-file": "3.1.1"
  },
  "devDependencies": {
    "@babel/core": "7.4.5",
    "@babel/plugin-proposal-optional-chaining": "7.2.0",
    "@babel/runtime": "7.4.5",
    "async": "2.6.0",
    "babel-cli": "6.24.1",
    "babel-eslint": "8.0.2",
    "babel-jest": "23.0.0",
    "babel-plugin-module-resolver": "3.0.0",
    "babel-preset-env": "1.4.0",
    "babel-preset-flow": "6.23.0",
    "babel-preset-react-native": "4.0.0",
    "babel-preset-stage-2": "6.24.1",
    "babel-watch": "2.0.6",
    "chalk": "1.1.3",
    "detox": "8.2.3",
    "eslint": "4.12.0",
    "eslint-import-resolver-babel-module": "4.0.0-beta.3",
    "eslint-plugin-import": "2.8.0",
    "eslint-plugin-prettier": "2.3.1",
    "eslint-plugin-react": "7.5.1",
    "flow-bin": "0.46.0",
    "fs-extra": "5.0.0",
    "jest": "23.0.0",
    "metro-react-native-babel-preset": "0.54.1",

Any kind of assistance that is not repeating what others have said is appreciated. I have been at this for a week now.

vitorveiga added a commit to jscrambler/babel that referenced this issue Jul 2, 2020
vitorveiga added a commit to jscrambler/babel that referenced this issue Jul 7, 2020
vitorveiga added a commit to jscrambler/babel that referenced this issue Jul 7, 2020
vitorveiga added a commit to jscrambler/babel that referenced this issue Jul 7, 2020
vitorveiga added a commit to jscrambler/babel that referenced this issue Jul 7, 2020
vitorveiga added a commit to jscrambler/babel that referenced this issue Jul 7, 2020
vitorveiga added a commit to jscrambler/babel that referenced this issue Jul 7, 2020
@vitorveiga
Copy link
Contributor

Hello,

New Pull Request #11801 to fix the reported bug.

It was easier (for me) to just create a new PR instead of refactoring the PR #10050.

vitorveiga added a commit to jscrambler/babel that referenced this issue Nov 9, 2020
@github-actions github-actions bot added the outdated A closed issue/PR that is archived due to age. Recommended to make a new issue label Mar 17, 2021
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Mar 17, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
i: bug outdated A closed issue/PR that is archived due to age. Recommended to make a new issue
Projects
None yet
5 participants