<h3 dir="auto"><g-emoji class="g-emoji" alias="computer" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/1f4bb.png">💻</g-emoji></h3>
<ul class="contains-task-list">
<li class="task-list-item"><input type="checkbox" id="" disabled="" class="task-list-item-checkbox" checked=""> Would you like to work on a fix?</li>
</ul>
<h3 dir="auto">How are you using Babel?</h3>
<p dir="auto">babel-loader (webpack)</p>
<h3 dir="auto">Input code</h3>
<p dir="auto">When using the assumption <code class="notranslate">privateFieldsAsProperties: true</code> in combination with not using the Babel <code class="notranslate">transform-runtime</code> plugin (so that babel-helpers are inlined), it becomes possible to override #-private fields, which is incorrect semantics.</p>
<p dir="auto">The issue can only be reproduced when using at least two different source files.</p>
<p dir="auto">Bar.js:</p>
<div class="highlight highlight-source-js notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="export default class Bar {
  #hidden = &quot;Bar's hidden&quot;;
  getHidden() {
    return this.#hidden;
  }
}"><pre class="notranslate"><span class="pl-k">export</span> <span class="pl-k">default</span> <span class="pl-k">class</span> <span class="pl-v">Bar</span> <span class="pl-kos">{</span>
  #<span class="pl-c1">hidden</span> <span class="pl-c1">=</span> <span class="pl-s">"Bar's hidden"</span><span class="pl-kos">;</span>
  <span class="pl-en">getHidden</span><span class="pl-kos">(</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
    <span class="pl-k">return</span> <span class="pl-smi">this</span><span class="pl-kos">.</span>#<span class="pl-c1">hidden</span><span class="pl-kos">;</span>
  <span class="pl-kos">}</span>
<span class="pl-kos">}</span></pre></div>
<p dir="auto">index.js:</p>
<div class="highlight highlight-source-js notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="import Bar from &quot;./Bar&quot;;

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

console.log(new Foo().getHidden()); // actual: &quot;Foo's hidden&quot;, expected: &quot;Bar's hidden&quot;"><pre class="notranslate"><span class="pl-k">import</span> <span class="pl-v">Bar</span> <span class="pl-k">from</span> <span class="pl-s">"./Bar"</span><span class="pl-kos">;</span>

<span class="pl-k">class</span> <span class="pl-v">Foo</span> <span class="pl-k">extends</span> <span class="pl-v">Bar</span> <span class="pl-kos">{</span>
  <span class="pl-c">// it should not be possible to override a #private field of a superclass:</span>
  #<span class="pl-c1">hidden</span> <span class="pl-c1">=</span> <span class="pl-s">"Foo's hidden"</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span>

<span class="pl-smi">console</span><span class="pl-kos">.</span><span class="pl-en">log</span><span class="pl-kos">(</span><span class="pl-k">new</span> <span class="pl-v">Foo</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">.</span><span class="pl-en">getHidden</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">)</span><span class="pl-kos">;</span> <span class="pl-c">// actual: "Foo's hidden", expected: "Bar's hidden"</span></pre></div>
<p dir="auto">Since Babel's own REPL only supports one input file, here is the full example including a <code class="notranslate">.babelrc</code> in  codesandbox:<br>
<a href="https://codesandbox.io/s/babel-privatefieldsasproperties-bug-gqvfsz?file=/.babelrc:84-88" rel="nofollow">codesandbox.io reproducer</a></p>
<h3 dir="auto">Configuration file name</h3>
<p dir="auto">.babelrc</p>
<h3 dir="auto">Configuration</h3>
<div class="highlight highlight-source-json notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="{
  &quot;presets&quot;: [
    &quot;env&quot;
  ],
  &quot;assumptions&quot;: {
    &quot;privateFieldsAsProperties&quot;: true
  }
}"><pre class="notranslate">{
  <span class="pl-ent">"presets"</span>: [
    <span class="pl-s"><span class="pl-pds">"</span>env<span class="pl-pds">"</span></span>
  ],
  <span class="pl-ent">"assumptions"</span>: {
    <span class="pl-ent">"privateFieldsAsProperties"</span>: <span class="pl-c1">true</span>
  }
}</pre></div>
<h3 dir="auto">Current and expected behavior</h3>
<p dir="auto">When running the codesandbox example given above, in the output you see <code class="notranslate">"Foo's hidden"</code>, so class <code class="notranslate">Foo</code> managed to override the #-private field <code class="notranslate">#hidden</code> of class <code class="notranslate">Bar</code>, which must not be possible.<br>
When you switch off <code class="notranslate">privateFieldsAsProperties</code> and run the example again, the correct result <code class="notranslate">"Foo's hidden"</code> is shown.<br>
Also when you use the Babel plugin <code class="notranslate">transform-runtime</code>, the behavior is correct.</p>
<h3 dir="auto">Environment</h3>
<p dir="auto">See <a href="https://codesandbox.io/s/babel-privatefieldsasproperties-bug-gqvfsz?file=/package.json" rel="nofollow">codesandbox example</a>:</p>
<ul dir="auto">
<li>Babel 7.2.0</li>
<li>Node 16</li>
</ul>
<h3 dir="auto">Possible solution</h3>
<p dir="auto">The reason that both #-private fields share the same name <code class="notranslate">__private_0_hidden</code> is that the helper function <a href="https://github.com/babel/babel/blob/main/packages/babel-helpers/src/helpers.ts#L997-L1002"><code class="notranslate">classPrivateFieldLooseKey</code></a> tries to define and initialize a global counter variable <code class="notranslate">id</code> that is increased for each generated property name, to ensure unique private property names. However, when helper functions are inlined (no <code class="notranslate">transform-runtime</code> plugin), each bundled file comes with a <em>copy</em> of this helper code, each defining its own <code class="notranslate">var id = 0;</code>. Thus, in both bundled files, the private member counter starts with <code class="notranslate">0</code>, ending up in the same private field slot name in both the superclass and the subclass, which must not happen.</p>
<p dir="auto">One approach to solve this could be to use a <code class="notranslate">Symbol</code> 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 <code class="notranslate">Symbol</code> is supported).<br>
I guess helper code is not compiled, so <a href="https://caniuse.com/?search=Symbol" rel="nofollow">using <code class="notranslate">Symbol</code> would rule out IE</a>, unless a <a href="https://github.com/medikoo/es6-symbol"><code class="notranslate">Symbol</code> polyfill</a> is applied. If this is a show-stopper, a new assumption <code class="notranslate">privateFieldsAsSymbolProperties</code> could be introduced that fixes the issue, but requires a target that supports <code class="notranslate">Symbol</code> (at least through a polyfill).</p>
<h3 dir="auto">Additional context</h3>
<p dir="auto"><em>No response</em></p>
<h3 dir="auto">Input Code</h3>
<p dir="auto">In the following snippet, calling <code class="notranslate">path.ensureBlock()</code> 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).</p>
<div class="highlight highlight-source-js notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="export default function (babel) {
  const { types: t } = babel;
  
  return {
    name: &quot;ast-transform&quot;, // not required
    visitor: {
      ArrowFunctionExpression(path) {
        path.ensureBlock();
        if (path.node.expression) {
          throw Error(&quot;Should no longer be an expression&quot;);
        }
      },
    }
  };
}"><pre class="notranslate"><span class="pl-k">export</span> <span class="pl-k">default</span> <span class="pl-k">function</span> <span class="pl-kos">(</span><span class="pl-s1">babel</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
  <span class="pl-k">const</span> <span class="pl-kos">{</span> <span class="pl-c1">types</span>: <span class="pl-s1">t</span> <span class="pl-kos">}</span> <span class="pl-c1">=</span> <span class="pl-s1">babel</span><span class="pl-kos">;</span>
  
  <span class="pl-k">return</span> <span class="pl-kos">{</span>
    <span class="pl-c1">name</span>: <span class="pl-s">"ast-transform"</span><span class="pl-kos">,</span> <span class="pl-c">// not required</span>
    <span class="pl-c1">visitor</span>: <span class="pl-kos">{</span>
      <span class="pl-en">ArrowFunctionExpression</span><span class="pl-kos">(</span><span class="pl-s1">path</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
        <span class="pl-s1">path</span><span class="pl-kos">.</span><span class="pl-en">ensureBlock</span><span class="pl-kos">(</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
        <span class="pl-k">if</span> <span class="pl-kos">(</span><span class="pl-s1">path</span><span class="pl-kos">.</span><span class="pl-c1">node</span><span class="pl-kos">.</span><span class="pl-c1">expression</span><span class="pl-kos">)</span> <span class="pl-kos">{</span>
          <span class="pl-k">throw</span> <span class="pl-v">Error</span><span class="pl-kos">(</span><span class="pl-s">"Should no longer be an expression"</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
        <span class="pl-kos">}</span>
      <span class="pl-kos">}</span><span class="pl-kos">,</span>
    <span class="pl-kos">}</span>
  <span class="pl-kos">}</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span></pre></div>
<p dir="auto"><a href="https://astexplorer.net/#/gist/0bd0506d529ed4c59a4ab10ffc2bd105/c23cb1d08aed3b721a53142b4d3bc6fb14d2af7b" rel="nofollow">https://astexplorer.net/#/gist/0bd0506d529ed4c59a4ab10ffc2bd105/c23cb1d08aed3b721a53142b4d3bc6fb14d2af7b</a></p>
<h3 dir="auto">Babel/Babylon Configuration (.babelrc, package.json, cli command)</h3>
<p dir="auto">See ASTExplorer link.</p>
<h3 dir="auto">Expected Behavior</h3>
<p dir="auto">The "expression" flag should be set to false.</p>
<h3 dir="auto">Possible Solution</h3>
<p dir="auto">It sounds like it would be enough to add a <code class="notranslate">path.set("expression", false)</code>  in <a href="https://github.com/babel/babel/blob/master/packages/babel-traverse/src/path/conversion.js#L26">https://github.com/babel/babel/blob/master/packages/babel-traverse/src/path/conversion.js#L26</a> if the node type is an ArrowFunctionExpression? I'm not sure if there are other cases like this in the AST spec.</p>