💻
How are you using Babel?
Programmatic API (babel.transform, babel.parse)
Input code
While scanning the recent esbuild commits, I noticed evanw/esbuild@f66b586. Essentially, Tagged Template Literals are a special form of function invocation, and we need to preserve the this receiver when invoking:
REPL
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:
|
// MEMBER -> _get(MEMBER) |
|
member.replaceWith(this.get(member)); |
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();
💻
How are you using Babel?
Programmatic API (
babel.transform,babel.parse)Input code
While scanning the recent esbuild commits, I noticed evanw/esbuild@f66b586. Essentially, Tagged Template Literals are a special form of function invocation, and we need to preserve the
thisreceiver when invoking:REPL
Configuration file name
babel.config.json
Configuration
Current and expected behavior
Currently, the
this.#tagis transformed into_classPrivateMethodGet(this, _tag, _tag2). The return value from_classPrivateMethodGet(which is_tag2, the transformed#tagmethod) is then invoked via Tagged Template:In this case, the receiver is
undefined, which fails the assertion.Environment
Babel v7.14.1
Possible solution
When we're transforming a private access in the
tagof a Tagged Template Literal, we need to bind the value to the correct receiver:Where this is done is a bit confusing. Transforming is done via
transformPrivateNamesUsage, which calls an abstractmemberExpressionToFunctionstransformer.memberExpressionToFunctionswill call several "handler" functions, which are defined in privateNameHandlerSpec.Currently, the
this.#tag`template literal`is invoking thegethandler. This fails to preserve thethisreceiver once transformed. Luckily, there's a similar handler calledboundGetwhich will preserve the receiver.We actually just need to fix the
memberExpressionToFunctionsimplementation to detect when thegetis happening inside a Tagged Template'stag. In this case, we need to callboundGetinstead ofget. Specifically this line:babel/packages/babel-helper-member-expression-to-functions/src/index.ts
Lines 465 to 466 in b1f57e5
We'll also need
Additional context
This also needs to be applied when transforming a private field, not just a private method: