question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[Bug]: babel crashes when destructuring, after using `for await` inside of async IIFE

See original GitHub issue

💻

  • Would you like to work on a fix?

How are you using Babel?

Other (Next.js, Gatsby, vue-cli, …)

Input code

Code that reproduces the crash (only happens when targeting older engines like IE 11:

main();

async function main() {
  let is_done = false;
  const async_iterable = {
    [Symbol.asyncIterator]: () => ({
      next: () => {
        const promise = Promise.resolve({ value: "luv u", done: is_done });
        is_done = true;
        return promise;
      },
    }),
  };


  (async () => { // IIFE: required for babel to crash
    for await (const string of async_iterable) { // for await: required for babel to crash
      console.log(string);
    }
  })();

  const [one] = [1]; // array destructuring: required for babel to crash
}

REPL link that also crashes

Configuration file name

babel.config.js

Configuration

Not relevant IMO since it also crashes in REPL. Target has to be set to IE 11.

Current and expected behavior

Current behavior: Property name expected type of string but got null

Expected behavior: Code that prints luv u to the console when ran.

Environment

  • Babel: 7.15.7 (@babel/core 7.15.5)
  • Node v16.9.1
  • Yarn 3.0.2
  • OS: macOS 10.15.7 (19H1323)

Possible solution

Transpile the code correctly instead of crashing

Additional context

I wrote code which was the reproduction code but with many more steps. Wanted to push it to a customer as a quick fix before my next calendar event. Babel crashed. Suck. Deployed only for modern browsers. Bored, offline on a flight I removed code bit for bit for bit for bit until I came up with the reproduction snippet. This is a very weird bug.

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:11 (6 by maintainers)

github_iconTop GitHub Comments

2reactions
JLHwungcommented, Sep 30, 2021

I think I have figured it out what’s going wrong here.

The proposal-async-generator-functions plugin initiates a sub-traversal from the Program node in order to run before async-to-generators. In this traversal context, it does not have access to visitors defined by other plugins. It visits the Function node async function main() {} and add asyncIterators helper for the for-await statements.

The addHelper utility does not respect the sub-traversal context, instead it uses the Program node’s context, which is the top level context with visitors defined by all plugins. When asyncIterators helper is inserted before async function main() {}, the async function’s traversal context is changed to the Program’s context by unshiftContainer.

Now the net effect is: After the async function is visited by traverse.node,

https://github.com/babel/babel/blob/89cab4311ce6dea0090d5819a721a1269765d6ec/packages/babel-traverse/src/path/context.ts#L88-L97

its context is changed to the Program’s context, in which the regenerator-transform registered a Function:exit listener to turn all variable declarations to var-statements. The listener is immediately invoked by this.call("exit") which eventually brings out the error since at this time, the transform-destructring is never run (expected), but neither should regenerator-transform if only addHelper could respect the sub-traversal context.

In my local tests, we have similar issue within this.call("enter") and traverse.node, too. The traversal context could be changed by the "enter" listeners, too and they are in current test suites.

A conservative fix will be to manually restore context after this.call("enter") and traverse.node, but it surely impacts performance because most listeners do not change traversal context: except calling unshiftContainer / pushContainer on ancestry path in a sub-traversal. And it may break some plugins if they intend to change the path context in the visitor.

I will see if it can be fixed in addHelper utility, otherwise we may have to trade off performance for correctness here.

2reactions
mischniccommented, Sep 28, 2021

It fails for me with

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

and

{
  "dependencies": {
    "@babel/cli": "7.15.7",
    "@babel/core": "7.15.5",
    "@babel/preset-env": "7.15.6"
  }
}

when running babel via yarn babel index.js on

main();

async function main() {
  (async () => {
    // IIFE: required for babel to crash
    for await (const string of async_iterable) {
      // for await: required for babel to crash
      console.log(string);
    }
  })();

  const [one] = [1]; // array destructuring: required for babel to crash
}
$ node_modules/.bin/babel main.js
TypeError: main.js: Property name expected type of string but got null
    at validate (node_modules/@babel/types/lib/definitions/utils.js:158:13)
    at Object.validate (node_modules/@babel/types/lib/definitions/utils.js:227:7)
    at validateField (node_modules/@babel/types/lib/validators/validate.js:24:9)
    at validate (node_modules/@babel/types/lib/validators/validate.js:17:3)
    at builder (node_modules/@babel/types/lib/builders/builder.js:38:27)
    at Object.identifier (node_modules/@babel/types/lib/builders/generated/index.js:344:31)
    at node_modules/regenerator-transform/lib/hoist.js:32:29
    at Array.forEach (<anonymous>)
    at varDeclToExpr (node_modules/regenerator-transform/lib/hoist.js:29:23)
    at exit (node_modules/regenerator-transform/lib/hoist.js:51:20) {
  code: 'BABEL_TRANSFORM_ERROR'
}
Read more comments on GitHub >

github_iconTop Results From Across the Web

@babel/plugin-bugfix-safari-id-destructuring-collision ... - Yarn
Fast, reliable, and secure dependency management.
Read more >
Destructuring data from async function result or fallback value ...
It seems that you just want await f().catch(() => FALLBACK) . If f() rejects, then you'll substitute the FALLBACK object as the result....
Read more >
API - ESBuild
This API call is used by the command-line interface if no input files are provided and the --bundle flag is not present. In...
Read more >
Understanding JavaScript's async await - Ponyfoo
Given that await suspends your async function and the await Promise. all expression ultimately resolves into a results array, we can use ......
Read more >
CoffeeScript
coffee script into a .js JavaScript file of the same name. -t, --transpile, Pipe the CoffeeScript compiler's output through Babel before saving ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found