Support NamedEvaluation for decorated anonymous class expression#15122
Support NamedEvaluation for decorated anonymous class expression#15122JLHwung wants to merge 11 commits intobabel:mainfrom
Conversation
| path.parentPath.node.type === "VariableDeclarator" && | ||
| path.parentPath.node.id.type === "Identifier" | ||
| ) { | ||
| className = path.parentPath.node.id.name; |
There was a problem hiding this comment.
Here is how we previously handle the namedEvaluation in the case var c = @dec class {}.
| } | ||
|
|
||
| // Todo: unify name references logic with helper-function-name | ||
| function NamedEvaluationVisitoryFactory( |
There was a problem hiding this comment.
At first I tried to extract logic from helper-function-name but it turns out that helper is out-of-dated. Since any exports in helpers must await a new minor release, I end up implementing a new namedEvaluationVisitor.
There was a problem hiding this comment.
If we were to put this in helper-function-name waiting for a minor, would we need to keep both implementations or can we just update the old one?
|
Build successful! You can test your changes in the REPL here: https://babeljs.io/repl/build/53477/ |
| }, | ||
| // We listen on ObjectExpression so that we don't have to visit | ||
| // the object properties under object patterns | ||
| ObjectExpression(path, state) { |
There was a problem hiding this comment.
Guess I have to handle RecordExpression here as well.
There was a problem hiding this comment.
A record expression can not contain function as value anyway, so we can ignore that.
There was a problem hiding this comment.
@nicolo-ribaudo The current record spec requires NamedEvaluation for the initializer. https://tc39.es/proposal-record-tuple/#sec-record-initializer-runtime-semantics-recordpropertydefinitionevaluation Is that observable now that function / class can not be a record value?
| `; | ||
|
|
||
| helpers.setFunctionName = helper("7.20.1")` | ||
| export default function _setFunctionName(f, n) { |
There was a problem hiding this comment.
To mirror https://tc39.es/ecma262/#sec-setfunctionname, the helper should support prefix used by getters / setters, though it is currently not used by this plugin.
| var key = toPrimitive(arg, "string"); | ||
| return typeof key === "symbol" ? key : String(key); |
There was a problem hiding this comment.
Further assumption idea: _toPropertyKey can be simplified to typeof key === "symbol" ? key : "".concat(key) (aka TS's __propKey) if [Symbol.toPrimitive] method does not return a symbol.
|
We could consider adding |
While engines like V8 retains such information in their AST |
7ce9211 to
699b31f
Compare
699b31f to
da24744
Compare
| return babelHelpers.classPrivateFieldGet(this, _C); | ||
| } | ||
| set ['c'](v) { | ||
| set [_computedKey](v) { |
There was a problem hiding this comment.
The new output aligns to the decorated variant of this test case.
|
|
||
| // Recrawl the scope to make sure new identifiers are properly synced | ||
| path.scope.crawl(); | ||
| } |
There was a problem hiding this comment.
They are copied from the epilogue of this transform.
Also fixed non-memoised computed key for undecorated accessor
4839e26 to
920073e
Compare
ad89112 to
be9293f
Compare
7e2b9dd to
248fb76
Compare
| const valueNode = value ? t.cloneNode(value) : undefined; | ||
|
|
||
| const newField = generateClassProperty(newId, valueNode, isStatic); | ||
| const newField = generateClassProperty(newId, value, isStatic); |
There was a problem hiding this comment.
Here we avoid cloning the accessor property's initializer because
- cloning is unnecessary since the initializer is moved to the proxy setter body
- If the initializer has been transformed by
propertyVisitor.ClassAccessorProperty(e.g. the case of nested decorator@noop accessor foo = @dec class {}), the cloned node will result to a different NodePath, which bypasses theVISITEDcheck and gets the inner class@dec class {}transformed twice.
| } | ||
| hasElementDecorators = true; | ||
| } else if (element.node.type === "ClassAccessorProperty") { | ||
| // @ts-expect-error todo: propertyVisitor.ClassAccessorProperty should be callable. Improve typings. |
There was a problem hiding this comment.
Help wanted: I don't know how to make the visitor callable.
There was a problem hiding this comment.
I tried this, but I also failed.😅
| return this.#C; | ||
| } | ||
| set ['c'](v) { | ||
| set [_computedKey](v) { |
There was a problem hiding this comment.
This test captured the bug that computed key in a non-decorated accessor is not properly memoised.
| let newKey = key; | ||
| if (computed && !scopeParent.isStatic(key)) { | ||
| newKey = memoiseExpression(key as t.Expression, "computedKey"); | ||
| } | ||
| addProxyAccessorsFor(newPath, newKey, newId, computed); |
There was a problem hiding this comment.
| let newKey = key; | |
| if (computed && !scopeParent.isStatic(key)) { | |
| newKey = memoiseExpression(key as t.Expression, "computedKey"); | |
| } | |
| addProxyAccessorsFor(newPath, newKey, newId, computed); | |
| addProxyAccessorsFor( | |
| newPath, | |
| computed && !scopeParent.isStatic(key) | |
| ? memoiseExpression(key as t.Expression, "computedKey") | |
| : key, | |
| newId, | |
| computed, | |
| ); |
Would this be better?
| const id = path.node.id; | ||
| const className = id.name; |
There was a problem hiding this comment.
| const id = path.node.id; | |
| const className = id.name; | |
| const className = path.node.id.name; |
It seems that id is only used once?
| helpers.setFunctionName = helper("7.20.1")` | ||
| export default function _setFunctionName(f, n) { | ||
| if (typeof n === "symbol") n = n.description ? "[" + n.description + "]" : ""; | ||
| return Object.defineProperty(f, "name", { configurable: true, value: n + "" }); |
There was a problem hiding this comment.
In some older browsers .name was non-configurable, so we should wrap this in try/catch.
| /* @minVersion 7.1.5 */ | ||
|
|
||
| // https://tc39.es/ecma262/#sec-toprimitive | ||
| export default function _toPrimitive( |
There was a problem hiding this comment.
Can we inline this in the toPropertyKey helper?
There was a problem hiding this comment.
This helper has been exposed since 7.1.5, so probably not in the patch release. Here I extracted toPrimitive and toPropertyKey into single files.
| } | ||
|
|
||
| // Todo: unify name references logic with helper-function-name | ||
| function NamedEvaluationVisitoryFactory( |
There was a problem hiding this comment.
If we were to put this in helper-function-name waiting for a minor, would we need to keep both implementations or can we just update the old one?
|
This PR is a rebase nightmare. I will try to extract some commits into separate PRs. |
|
Ok, sorry for not reviewing it earlier 😅 |
|
@nicolo-ribaudo No problem. Maybe the one-year-older me can find new bugs hidden in this PR. 😄 |
context.nameis correct for decorated anonymous class expressionThis PR ensures that the
context.nameis correctly assigned when an anonymous class expression is decorated. Note that current main already works in the following case:This PR supports more cases (See testcases for a complete set):
To support computed key and the default export, a new helper
setFunctionNameis introduced, which is very similar to the TS compiler utility__setFunctionName:https://github.com/microsoft/TypeScript/blob/c837d78d7ee5bab2ab6f2c3c7c24882ca9c31dcf/src/compiler/factory/emitHelpers.ts#L899-L909
This PR also fixes that
context.nameis not a string when decorating a computed numerical key:Limit
This PR currently does not support NamedEvaluation for auto-accessors:Thecontext.nameavailable indecwill be the backed private key storage name. Per spec, it should be the accessor namekey.