babel: [Bug]: Tagged template with strict private field/method tag has incorrect receiver
đź’»
- Would you like to work on a fix?
How are you using Babel?
Programmatic API (babel.transform
, babel.parse
)
Input code
While scanning the recent esbuild commits, I noticed https://github.com/evanw/esbuild/commit/f66b586923e2e4569155e758b57bae9d473c7d8f. Essentially, Tagged Template Literals are a special form of function invocation, and we need to preserve the this
receiver when invoking:
class Foo {
#tag() {
return this;
}
constructor() {
const receiver = this.#tag`tagged template`;
console.assert(receiver === this);
}
}
new Foo();
Configuration file name
babel.config.json
Configuration
{
"presets": [
[
"@babel/preset-env",
{
"shippedProposals": true,
"targets": {
"chrome": "75"
}
}
]
]
}
Current and expected behavior
Currently, the this.#tag
is transformed into _classPrivateMethodGet(this, _tag, _tag2)
. The return value from _classPrivateMethodGet
(which is _tag2
, the transformed #tag
method) is then invoked via Tagged Template:
const receiver = _classPrivateMethodGet(this, _tag, _tag2)`tagged template`;
In this case, the receiver is undefined
, which fails the assertion.
console.assert(receiver === this); // Failure, receiver is undefined
Environment
Babel v7.14.1
Possible solution
When we’re transforming a private access in the tag
of a Tagged Template Literal, we need to bind the value to the correct receiver:
- const receiver = _classPrivateMethodGet(this, _tag, _tag2)`tagged template`;
+ const receiver = _classPrivateMethodGet(this, _tag, _tag2).bind(this)`tagged template`;
Where this is done is a bit confusing. Transforming is done via transformPrivateNamesUsage
, which calls an abstract memberExpressionToFunctions
transformer. memberExpressionToFunctions
will call several “handler” functions, which are defined in privateNameHandlerSpec.
Currently, the this.#tag`template literal`
is invoking the get
handler. This fails to preserve the this
receiver once transformed. Luckily, there’s a similar handler called boundGet
which will preserve the receiver.
We actually just need to fix the memberExpressionToFunctions
implementation to detect when the get
is happening inside a Tagged Template’s tag
. In this case, we need to call boundGet
instead of get
. Specifically this line: https://github.com/babel/babel/blob/b1f57e5fb58f7e890b3be58965e014f6fd4950e0/packages/babel-helper-member-expression-to-functions/src/index.ts#L465-L466
We’ll also need
Additional context
This also needs to be applied when transforming a private field, not just a private method:
class Foo {
#tag() {
return this;
}
#tag2 = this.#tag;
constructor() {
const receiver = this.#tag`tagged template`;
console.assert(receiver === this);
const receiver2 = this.#tag2`tagged template`;
console.assert(receiver2 === this);
}
}
new Foo();
About this issue
- Original URL
- State: closed
- Created 3 years ago
- Comments: 15 (15 by maintainers)
There are a few needed , which will live in the following directories:
Class fields:
Private method:
Private accessor:
You can look at the other fixtures in these directories to see an example of the
input.js
andoutput.js
.Yup, right