New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Create bugfix plugin for classes in computed keys in Firefox #16390
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
src | ||
test | ||
*.log |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# @babel/plugin-bugfix-firefox-class-in-computed-class-key | ||
|
||
> Wraps classes defined in computed keys of other classes affected by https://bugzilla.mozilla.org/show_bug.cgi?id=1887677 | ||
|
||
See our website [@babel/plugin-bugfix-firefox-class-in-computed-class-key](https://babeljs.io/docs/babel-plugin-bugfix-firefox-class-in-computed-class-key) for more information. | ||
|
||
## Install | ||
|
||
Using npm: | ||
|
||
```sh | ||
npm install --save-dev @babel/plugin-bugfix-firefox-class-in-computed-class-key | ||
``` | ||
|
||
or using yarn: | ||
|
||
```sh | ||
yarn add @babel/plugin-bugfix-firefox-class-in-computed-class-key --dev | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
{ | ||
"name": "@babel/plugin-bugfix-firefox-class-in-computed-class-key", | ||
"version": "7.24.1", | ||
"description": "Wraps classes defined in computed keys of other classes affected by https://bugzilla.mozilla.org/show_bug.cgi?id=1887677", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/babel/babel.git", | ||
"directory": "packages/babel-plugin-bugfix-firefox-class-in-computed-class-key" | ||
}, | ||
"homepage": "https://babel.dev/docs/en/next/babel-plugin-bugfix-firefox-class-in-computed-class-key", | ||
"license": "MIT", | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"main": "./lib/index.js", | ||
"exports": { | ||
".": "./lib/index.js", | ||
"./package.json": "./package.json" | ||
}, | ||
"keywords": [ | ||
"babel-plugin", | ||
"bugfix" | ||
], | ||
"dependencies": { | ||
"@babel/helper-environment-visitor": "workspace:^", | ||
"@babel/helper-plugin-utils": "workspace:^" | ||
}, | ||
"peerDependencies": { | ||
"@babel/core": "^7.0.0" | ||
}, | ||
"devDependencies": { | ||
"@babel/core": "workspace:^", | ||
"@babel/helper-plugin-test-runner": "workspace:^", | ||
"@babel/traverse": "workspace:^" | ||
}, | ||
"engines": { | ||
"node": ">=6.9.0" | ||
}, | ||
"author": "The Babel Team (https://babel.dev/team)", | ||
"conditions": { | ||
"USE_ESM": [ | ||
{ | ||
"type": "module" | ||
}, | ||
null | ||
], | ||
"BABEL_8_BREAKING": [ | ||
{ | ||
"engines": { | ||
"node": "^16.20.0 || ^18.16.0 || >=20.0.0" | ||
} | ||
}, | ||
{} | ||
] | ||
}, | ||
"type": "commonjs" | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
import type { NodePath, Visitor } from "@babel/traverse"; | ||
import type { types as t } from "@babel/core"; | ||
import { declare } from "@babel/helper-plugin-utils"; | ||
import environmentVisitor from "@babel/helper-environment-visitor"; | ||
|
||
export default declare(({ types: t, traverse, assertVersion }) => { | ||
assertVersion(REQUIRED_VERSION(7)); | ||
|
||
const containsClassExpressionVisitor: Visitor<{ found: boolean }> = { | ||
ClassExpression(path, state) { | ||
state.found = true; | ||
path.stop(); | ||
}, | ||
Function(path) { | ||
path.skip(); | ||
}, | ||
}; | ||
|
||
const containsYieldOrAwaitVisitor = traverse.visitors.merge([ | ||
{ | ||
YieldExpression(path, state) { | ||
state.yield = true; | ||
if (state.await) path.stop(); | ||
}, | ||
AwaitExpression(path, state) { | ||
state.await = true; | ||
if (state.yield) path.stop(); | ||
}, | ||
} satisfies Visitor<{ yield: boolean; await: boolean }>, | ||
environmentVisitor, | ||
]); | ||
|
||
function containsClassExpression(path: NodePath<t.Node>) { | ||
if (t.isClassExpression(path.node)) return true; | ||
if (t.isFunction(path.node)) return false; | ||
const state = { found: false }; | ||
path.traverse(containsClassExpressionVisitor, state); | ||
return state.found; | ||
} | ||
|
||
function wrap(path: NodePath<t.Expression>) { | ||
const context = { | ||
yield: t.isYieldExpression(path.node), | ||
await: t.isAwaitExpression(path.node), | ||
}; | ||
path.traverse(containsYieldOrAwaitVisitor, context); | ||
|
||
let replacement; | ||
|
||
if (context.yield) { | ||
const fn = t.functionExpression( | ||
null, | ||
[], | ||
t.blockStatement([t.returnStatement(path.node)]), | ||
/* generator */ true, | ||
/* async */ context.await, | ||
); | ||
|
||
replacement = t.yieldExpression( | ||
t.callExpression(t.memberExpression(fn, t.identifier("call")), [ | ||
t.thisExpression(), | ||
// NOTE: In some context arguments is invalid (it might not be defined | ||
// in the top-level scope, or it's a syntax error in static class blocks). | ||
// However, `yield` is also invalid in those contexts, so we can safely | ||
// inject a reference to arguments. | ||
t.identifier("arguments"), | ||
]), | ||
true, | ||
); | ||
} else { | ||
const fn = t.arrowFunctionExpression([], path.node, context.await); | ||
|
||
if (context.await) { | ||
replacement = t.awaitExpression(t.callExpression(fn, [])); | ||
} else { | ||
replacement = t.callExpression( | ||
// We need to use fn.call() instead of just fn() because | ||
// terser transforms (() => class {})() to class {}, effectively | ||
// undoing the wrapping introduced by this plugin. | ||
// https://github.com/terser/terser/issues/1514 | ||
// TODO(Babel 8): Remove .call if Terser stops inlining this case. | ||
t.memberExpression(fn, t.identifier("call")), | ||
[], | ||
); | ||
} | ||
} | ||
|
||
path.replaceWith(replacement); | ||
} | ||
|
||
return { | ||
name: "bugfix-firefox-class-in-computed-class-key", | ||
|
||
visitor: { | ||
Class(path) { | ||
const hasPrivateElement = path.node.body.body.some(node => | ||
t.isPrivate(node), | ||
); | ||
if (!hasPrivateElement) return; | ||
|
||
for (const elem of path.get("body.body")) { | ||
if ( | ||
"computed" in elem.node && | ||
elem.node.computed && | ||
containsClassExpression(elem.get("key")) | ||
) { | ||
wrap(elem.get("key")); | ||
} | ||
} | ||
}, | ||
}, | ||
}; | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
class A { | ||
#x = 1; | ||
[useIt([1 + class {}])]; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
class A { | ||
#x = 1; | ||
[(() => useIt([1 + class {}])).call()]; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
{ | ||
"plugins": ["bugfix-firefox-class-in-computed-class-key"] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
class A { | ||
#x = 1; | ||
[class {}]; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
class A { | ||
#x = 1; | ||
[(() => class {}).call()]; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
class A { | ||
#x; | ||
[class {}] | ||
[await class {}] | ||
} | ||
|
||
async function* f() { | ||
class A { | ||
#x; | ||
[class {}] | ||
[await class {}] | ||
[yield class {}] | ||
[yield* class {}] | ||
[await (yield class {})] | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
class A { | ||
#x; | ||
[(() => class {}).call()]; | ||
[await (async () => await class {})()]; | ||
} | ||
async function* f() { | ||
class A { | ||
#x; | ||
[(() => class {}).call()]; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need to add There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is fine, because it is possible that more than just terser will do this (or may do so in the future). |
||
[await (async () => await class {})()]; | ||
[yield* function* () { | ||
return yield class {}; | ||
}.call(this, arguments)]; | ||
[yield* function* () { | ||
return yield* class {}; | ||
}.call(this, arguments)]; | ||
[yield* async function* () { | ||
return await (yield class {}); | ||
}.call(this, arguments)]; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
@deco | ||
class A { | ||
#x = 1; | ||
static #y = 2; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
{ | ||
"plugins": [ | ||
"bugfix-firefox-class-in-computed-class-key", | ||
["proposal-decorators", { "version": "2023-11" }] | ||
] | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For tiny browser bugs this is probably easier than having to go through compat-table.