Issue 1:
💻

Would you like to work on a fix?

How are you using Babel?

babel-loader (webpack)

Input code

When using the assumption privateFieldsAsProperties: true in combination with not using the Babel transform-runtime plugin (so that babel-helpers are inlined), it becomes possible to override #-private fields, which is incorrect semantics.

The issue can only be reproduced when using at least two different source files.

Bar.js:

export default class Bar {
#hidden = "Bar's hidden";
getHidden() {
return this.#hidden;
}
}

index.js:

import Bar from "./Bar";

class Foo extends Bar {
// it should not be possible to override a #private field of a superclass:
#hidden = "Foo's hidden";
}

console.log(new Foo().getHidden()); // actual: "Foo's hidden", expected: "Bar's hidden"

Since Babel's own REPL only supports one input file, here is the full example including a .babelrc in codesandbox:

codesandbox.io reproducer

Configuration file name

.babelrc

Configuration

{
"presets": [
"env"
],
"assumptions": {
"privateFieldsAsProperties": true
}
}

Current and expected behavior

When running the codesandbox example given above, in the output you see "Foo's hidden", so class Foo managed to override the #-private field #hidden of class Bar, which must not be possible.

When you switch off privateFieldsAsProperties and run the example again, the correct result "Foo's hidden" is shown.

Also when you use the Babel plugin transform-runtime, the behavior is correct.

Environment

See codesandbox example:

Babel 7.2.0

Node 16

Possible solution

The reason that both #-private fields share the same name __private_0_hidden is that the helper function classPrivateFieldLooseKey tries to define and initialize a global counter variable id that is increased for each generated property name, to ensure unique private property names. However, when helper functions are inlined (no transform-runtime plugin), each bundled file comes with a copy of this helper code, each defining its own var id = 0;. Thus, in both bundled files, the private member counter starts with 0, ending up in the same private field slot name in both the superclass and the subclass, which must not happen.

One approach to solve this could be to use a Symbol instead of a generated string slot name, but I don't know if Babel helper code is allowed to use language features that may not be available in older target environments. I tested this approach and it works perfectly (given that Symbol is supported).

I guess helper code is not compiled, so using Symbol would rule out IE, unless a Symbol polyfill is applied. If this is a show-stopper, a new assumption privateFieldsAsSymbolProperties could be introduced that fixes the issue, but requires a target that supports Symbol (at least through a polyfill).

Additional context

No response

Issue 2:
Input Code

In the following snippet, calling path.ensureBlock() does not properly unset the "expression" flag on the ArrowFunctionExpression. This causes problems for other plugins, which may rely on the accuracy of the flag (notably, istanbul-lib-instrument).

export default function (babel) {
const { types: t } = babel;

return {
name: "ast-transform", // not required
visitor: {
ArrowFunctionExpression(path) {
path.ensureBlock();
if (path.node.expression) {
throw Error("Should no longer be an expression");
}
},
}
};
}

https://astexplorer.net/#/gist/0bd0506d529ed4c59a4ab10ffc2bd105/c23cb1d08aed3b721a53142b4d3bc6fb14d2af7b

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

See ASTExplorer link.

Expected Behavior

The "expression" flag should be set to false.

Possible Solution

It sounds like it would be enough to add a path.set("expression", false) in https://github.com/babel/babel/blob/master/packages/babel-traverse/src/path/conversion.js#L26 if the node type is an ArrowFunctionExpression? I'm not sure if there are other cases like this in the AST spec.