babel: [Bug]: Class Private Fields/Accessors/Methods must not be re-initialized on instance

šŸ’»

  • Would you like to work on a fix?

How are you using Babel?

Programmatic API (babel.transform, babel.parse)

Input code

(See ā€œPrevent private class members from being added more than onceā€ section in https://github.com/evanw/esbuild/releases/tag/v0.11.20#:~:text=Prevent private class members from being added more than once)

class Base {
  constructor(obj) {
    return obj;
  }
}

let counter = 0;
class Derived extends Base {
  #foo = ++counter;
  static get(obj) {
    return obj.#foo;
  }
}

const foo = {};
new Derived(foo);
assert.equals(Derived.get(foo), 1);

// This should throw an error, private fields must not be re-initialized
// on an object that already contains the field.
assert.throws(() => {
  new Derived(foo);
});
// ^ That should have thrown, but it doesn't.

// Must not re-initialize the field
assert.equals(Derived.get(foo), 1);
// But the counter is incremented (the initializer runs, but the field
// should not re-add)
assert.equals(counter, 2);

REPL

Configuration file name

babel.config.json

Configuration

{
  "presets": [
    [
      "@babel/preset-env",
      {
        "shippedProposals": true,
        "targets": {
          "chrome": "75"
        }
      }
    ]
  ]
}

Current and expected behavior

Currently, this will re-initialize the field #foo, but this is incorrect. It should throw an error when trying to initialize the field a second time.

Environment

  • Babel version: v7.14.1

Possible solution

We should create new initializePrivateFieldSpec, initializePrivateAccessorSpec, and initializePrivateMethodSpec (and static versions) helpers to initialize the field to a descriptor. Inside the helpers, we should check if the WeakMap/WeakSet that represents the transformed field already contains the instance key. If so, throw. Else, initialize.

Or, we could just have a simple checkPrivateFieldInitSpec which does the check, but doesnā€™t initialize. Weā€™d then call the check before initializing a field.

After creating the helpers, we should call them in the appropriate place in https://github.com/babel/babel/blob/main/packages/babel-helper-create-class-features-plugin/src/fields.js (look for any function that ends in ā€œInitSpecā€).

Additional context

This is a medium difficulty issue, so could be tackled by someone with a little experience making changes to Babel.

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 2
  • Comments: 27 (22 by maintainers)

Most upvoted comments

sure i can try it out

It looks good! The new function can be created in packages/babel-helpers/src/helpers.js, and it can be used using the addHelper internal method (you can Ctrl+F for it to see how it works).

Uh, that link is auto-generated and unfortunately I think we donā€™t have docs for that internal package šŸ˜…

Thanks, I will start working right away

Yes @nicolo-ribaudo! Got a bit side-tracked in the past few weeks.

Yes!

If it is the first time that you contribute to Babel, you can follow these steps: (you need to have make and yarn available on your machine)

  1. Fork the repo
  2. Run git clone https://github.com/<YOUR_USERNAME>/babel.git && cd babel
  3. Run yarn && make bootstrap
  4. Wait ā³
  5. Run make watch (or make build/yarn gulp build whenever you change a file). When updating @babel/helpers, Iā€™m not 100% sure that make watch works.
  6. Add a test (only exec.js and/or input.js; output.js will be automatically generated)
  7. Update the code!
  8. yarn jest [name-of-the-package-to-test] to run the tests
    • If some test outputs donā€™t match but the new results are correct, you can delete the bad output.js files and run the tests again
    • If you prefer, you can run OVERWRITE=true yarn jest [name-of-the-package-to-test] and they will be automatically updated.
  9. If it is working, run make test to run all the tests
  10. Run git push and open a PR!

could i help here?