Class features loose should have precedence over preset-env (#11634)
* Class features loose should have precedence over preset-env * Comment * Update packages/babel-helper-create-class-features-plugin/src/features.js [skip ci] Co-authored-by: Huáng Jùnliàng <jlhwung@gmail.com> * Future proof * Add warning when loose mode changes automatically * Better message Co-authored-by: Huáng Jùnliàng <jlhwung@gmail.com>
This commit is contained in:
parent
5b24d79875
commit
e6d873e061
@ -8,6 +8,15 @@ export const FEATURES = Object.freeze({
|
|||||||
privateIn: 1 << 4,
|
privateIn: 1 << 4,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const featuresSameLoose = new Map([
|
||||||
|
[FEATURES.fields, "@babel/plugin-proposal-class-properties"],
|
||||||
|
[FEATURES.privateMethods, "@babel/plugin-proposal-private-methods"],
|
||||||
|
[
|
||||||
|
FEATURES.privateIn,
|
||||||
|
"@babel/plugin-proposal-private-private-property-in-object",
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
// We can't use a symbol because this needs to always be the same, even if
|
// We can't use a symbol because this needs to always be the same, even if
|
||||||
// this package isn't deduped by npm. e.g.
|
// this package isn't deduped by npm. e.g.
|
||||||
// - node_modules/
|
// - node_modules/
|
||||||
@ -18,6 +27,17 @@ export const FEATURES = Object.freeze({
|
|||||||
const featuresKey = "@babel/plugin-class-features/featuresKey";
|
const featuresKey = "@babel/plugin-class-features/featuresKey";
|
||||||
const looseKey = "@babel/plugin-class-features/looseKey";
|
const looseKey = "@babel/plugin-class-features/looseKey";
|
||||||
|
|
||||||
|
// See https://github.com/babel/babel/issues/11622.
|
||||||
|
// Since preset-env sets loose for the fields and private methods plugins, it can
|
||||||
|
// cause conflicts with the loose mode set by an explicit plugin in the config.
|
||||||
|
// To solve this problem, we ignore preset-env's loose mode if another plugin
|
||||||
|
// explicitly sets it
|
||||||
|
// The code to handle this logic doesn't check that "low priority loose" is always
|
||||||
|
// the same. However, it is only set by the preset and not directly by users:
|
||||||
|
// unless someone _wants_ to break it, it shouldn't be a problem.
|
||||||
|
const looseLowPriorityKey =
|
||||||
|
"@babel/plugin-class-features/looseLowPriorityKey/#__internal__@babel/preset-env__please-overwrite-loose-instead-of-throwing";
|
||||||
|
|
||||||
export function enableFeature(file, feature, loose) {
|
export function enableFeature(file, feature, loose) {
|
||||||
// We can't blindly enable the feature because, if it was already set,
|
// We can't blindly enable the feature because, if it was already set,
|
||||||
// "loose" can't be changed, so that
|
// "loose" can't be changed, so that
|
||||||
@ -25,42 +45,63 @@ export function enableFeature(file, feature, loose) {
|
|||||||
// @babel/plugin-class-properties { loose: false }
|
// @babel/plugin-class-properties { loose: false }
|
||||||
// is transformed in loose mode.
|
// is transformed in loose mode.
|
||||||
// We only enabled the feature if it was previously disabled.
|
// We only enabled the feature if it was previously disabled.
|
||||||
if (!hasFeature(file, feature)) {
|
if (!hasFeature(file, feature) || canIgnoreLoose(file, feature)) {
|
||||||
file.set(featuresKey, file.get(featuresKey) | feature);
|
file.set(featuresKey, file.get(featuresKey) | feature);
|
||||||
if (loose) file.set(looseKey, file.get(looseKey) | feature);
|
if (
|
||||||
|
loose ===
|
||||||
|
"#__internal__@babel/preset-env__prefer-true-but-false-is-ok-if-it-prevents-an-error"
|
||||||
|
) {
|
||||||
|
setLoose(file, feature, true);
|
||||||
|
file.set(looseLowPriorityKey, file.get(looseLowPriorityKey) | feature);
|
||||||
|
} else if (
|
||||||
|
loose ===
|
||||||
|
"#__internal__@babel/preset-env__prefer-false-but-true-is-ok-if-it-prevents-an-error"
|
||||||
|
) {
|
||||||
|
setLoose(file, feature, false);
|
||||||
|
file.set(looseLowPriorityKey, file.get(looseLowPriorityKey) | feature);
|
||||||
|
} else {
|
||||||
|
setLoose(file, feature, loose);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
let resolvedLoose: void | true | false;
|
||||||
hasFeature(file, FEATURES.fields) &&
|
let higherPriorityPluginName: void | string;
|
||||||
hasFeature(file, FEATURES.privateMethods) &&
|
|
||||||
isLoose(file, FEATURES.fields) !== isLoose(file, FEATURES.privateMethods)
|
for (const [mask, name] of featuresSameLoose) {
|
||||||
) {
|
if (!hasFeature(file, mask)) continue;
|
||||||
throw new Error(
|
|
||||||
"'loose' mode configuration must be the same for both @babel/plugin-proposal-class-properties " +
|
const loose = isLoose(file, mask);
|
||||||
"and @babel/plugin-proposal-private-methods",
|
|
||||||
);
|
if (canIgnoreLoose(file, mask)) {
|
||||||
|
continue;
|
||||||
|
} else if (resolvedLoose === !loose) {
|
||||||
|
throw new Error(
|
||||||
|
"'loose' mode configuration must be the same for @babel/plugin-proposal-class-properties, " +
|
||||||
|
"@babel/plugin-proposal-private-methods and " +
|
||||||
|
"@babel/plugin-proposal-private-property-in-object (when they are enabled).",
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
resolvedLoose = loose;
|
||||||
|
higherPriorityPluginName = name;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (resolvedLoose !== undefined) {
|
||||||
hasFeature(file, FEATURES.fields) &&
|
for (const [mask, name] of featuresSameLoose) {
|
||||||
hasFeature(file, FEATURES.privateIn) &&
|
if (hasFeature(file, mask) && isLoose(file, mask) !== resolvedLoose) {
|
||||||
isLoose(file, FEATURES.fields) !== isLoose(file, FEATURES.privateIn)
|
setLoose(file, mask, resolvedLoose);
|
||||||
) {
|
console.warn(
|
||||||
throw new Error(
|
`Though the "loose" option was set to "${!resolvedLoose}" in your @babel/preset-env ` +
|
||||||
"'loose' mode configuration must be the same for both @babel/plugin-proposal-class-properties " +
|
`config, it will not be used for ${name} since the "loose" mode option was set to ` +
|
||||||
"and @babel/plugin-proposal-private-property-in-object",
|
`"${resolvedLoose}" for ${higherPriorityPluginName}.\nThe "loose" option must be the ` +
|
||||||
);
|
`same for @babel/plugin-proposal-class-properties, @babel/plugin-proposal-private-methods ` +
|
||||||
}
|
`and @babel/plugin-proposal-private-property-in-object (when they are enabled): you can ` +
|
||||||
|
`silence this warning by explicitly adding\n` +
|
||||||
if (
|
`\t["${name}", { "loose": ${resolvedLoose} }]\n` +
|
||||||
hasFeature(file, FEATURES.privateMethods) &&
|
`to the "plugins" section of your Babel config.`,
|
||||||
hasFeature(file, FEATURES.privateIn) &&
|
);
|
||||||
isLoose(file, FEATURES.privateMethods) !== isLoose(file, FEATURES.privateIn)
|
}
|
||||||
) {
|
}
|
||||||
throw new Error(
|
|
||||||
"'loose' mode configuration must be the same for both @babel/plugin-proposal-private-methods " +
|
|
||||||
"and @babel/plugin-proposal-private-property-in-object",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,6 +113,17 @@ export function isLoose(file, feature) {
|
|||||||
return !!(file.get(looseKey) & feature);
|
return !!(file.get(looseKey) & feature);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setLoose(file, feature, loose) {
|
||||||
|
if (loose) file.set(looseKey, file.get(looseKey) | feature);
|
||||||
|
else file.set(looseKey, file.get(looseKey) & ~feature);
|
||||||
|
|
||||||
|
file.set(looseLowPriorityKey, file.get(looseLowPriorityKey) & ~feature);
|
||||||
|
}
|
||||||
|
|
||||||
|
function canIgnoreLoose(file, feature) {
|
||||||
|
return !!(file.get(looseLowPriorityKey) & feature);
|
||||||
|
}
|
||||||
|
|
||||||
export function verifyUsedFeatures(path, file) {
|
export function verifyUsedFeatures(path, file) {
|
||||||
if (hasOwnDecorators(path.node)) {
|
if (hasOwnDecorators(path.node)) {
|
||||||
if (!hasFeature(file, FEATURES.decorators)) {
|
if (!hasFeature(file, FEATURES.decorators)) {
|
||||||
|
|||||||
@ -301,10 +301,28 @@ export default declare((api, opts) => {
|
|||||||
|
|
||||||
const pluginUseBuiltIns = useBuiltIns !== false;
|
const pluginUseBuiltIns = useBuiltIns !== false;
|
||||||
const plugins = Array.from(pluginNames)
|
const plugins = Array.from(pluginNames)
|
||||||
.map(pluginName => [
|
.map(pluginName => {
|
||||||
getPlugin(pluginName),
|
if (
|
||||||
{ spec, loose, useBuiltIns: pluginUseBuiltIns },
|
pluginName === "proposal-class-properties" ||
|
||||||
])
|
pluginName === "proposal-private-methods" ||
|
||||||
|
// This is not included in preset-env yet, but let's keep it here so we
|
||||||
|
// don't forget about it in the future.
|
||||||
|
pluginName === "proposal-private-property-in-object"
|
||||||
|
) {
|
||||||
|
return [
|
||||||
|
getPlugin(pluginName),
|
||||||
|
{
|
||||||
|
loose: loose
|
||||||
|
? "#__internal__@babel/preset-env__prefer-true-but-false-is-ok-if-it-prevents-an-error"
|
||||||
|
: "#__internal__@babel/preset-env__prefer-false-but-true-is-ok-if-it-prevents-an-error",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
|
return [
|
||||||
|
getPlugin(pluginName),
|
||||||
|
{ spec, loose, useBuiltIns: pluginUseBuiltIns },
|
||||||
|
];
|
||||||
|
})
|
||||||
.concat(polyfillPlugins);
|
.concat(polyfillPlugins);
|
||||||
|
|
||||||
if (debug) {
|
if (debug) {
|
||||||
|
|||||||
@ -0,0 +1,5 @@
|
|||||||
|
class A {
|
||||||
|
x = 2;
|
||||||
|
|
||||||
|
#foo() {}
|
||||||
|
}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"validateLogs": true,
|
||||||
|
"presets": [
|
||||||
|
["env", {
|
||||||
|
"targets": { "node": 10 },
|
||||||
|
"shippedProposals": true
|
||||||
|
}]
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
["proposal-private-methods", { "loose": true }]
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
var id = 0;
|
||||||
|
|
||||||
|
function _classPrivateFieldLooseKey(name) { return "__private_" + id++ + "_" + name; }
|
||||||
|
|
||||||
|
class A {
|
||||||
|
constructor() {
|
||||||
|
Object.defineProperty(this, _foo, {
|
||||||
|
value: _foo2
|
||||||
|
});
|
||||||
|
this.x = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var _foo = _classPrivateFieldLooseKey("foo");
|
||||||
|
|
||||||
|
var _foo2 = function _foo2() {};
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
Though the "loose" option was set to "false" in your @babel/preset-env config, it will not be used for @babel/plugin-proposal-class-properties since the "loose" mode option was set to "true" for @babel/plugin-proposal-private-methods.
|
||||||
|
The "loose" option must be the same for @babel/plugin-proposal-class-properties, @babel/plugin-proposal-private-methods and @babel/plugin-proposal-private-property-in-object (when they are enabled): you can silence this warning by explicitly adding
|
||||||
|
["@babel/plugin-proposal-class-properties", { "loose": true }]
|
||||||
|
to the "plugins" section of your Babel config.
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
class A {
|
||||||
|
x = 2;
|
||||||
|
|
||||||
|
#foo() {}
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
{
|
||||||
|
"validateLogs": true,
|
||||||
|
"presets": [
|
||||||
|
["env", {
|
||||||
|
"targets": { "node": 10 },
|
||||||
|
"shippedProposals": true,
|
||||||
|
"loose": true
|
||||||
|
}]
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
var id = 0;
|
||||||
|
|
||||||
|
function _classPrivateFieldLooseKey(name) { return "__private_" + id++ + "_" + name; }
|
||||||
|
|
||||||
|
class A {
|
||||||
|
constructor() {
|
||||||
|
Object.defineProperty(this, _foo, {
|
||||||
|
value: _foo2
|
||||||
|
});
|
||||||
|
this.x = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var _foo = _classPrivateFieldLooseKey("foo");
|
||||||
|
|
||||||
|
var _foo2 = function _foo2() {};
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
class A {
|
||||||
|
x = 2;
|
||||||
|
|
||||||
|
#foo() {}
|
||||||
|
}
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"validateLogs": true,
|
||||||
|
"presets": [
|
||||||
|
["env", {
|
||||||
|
"targets": { "node": 10 },
|
||||||
|
"shippedProposals": true
|
||||||
|
}]
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
||||||
|
|
||||||
|
class A {
|
||||||
|
constructor() {
|
||||||
|
_foo.add(this);
|
||||||
|
|
||||||
|
_defineProperty(this, "x", 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var _foo = new WeakSet();
|
||||||
|
|
||||||
|
var _foo2 = function _foo2() {};
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
class A {
|
||||||
|
x = 2;
|
||||||
|
|
||||||
|
#foo() {}
|
||||||
|
}
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"validateLogs": true,
|
||||||
|
"presets": [
|
||||||
|
["env", {
|
||||||
|
"targets": { "node": 10 },
|
||||||
|
"shippedProposals": true
|
||||||
|
}]
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
["proposal-class-properties", { "loose": true }],
|
||||||
|
["proposal-private-methods", { "loose": true }]
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
var id = 0;
|
||||||
|
|
||||||
|
function _classPrivateFieldLooseKey(name) { return "__private_" + id++ + "_" + name; }
|
||||||
|
|
||||||
|
class A {
|
||||||
|
constructor() {
|
||||||
|
Object.defineProperty(this, _foo, {
|
||||||
|
value: _foo2
|
||||||
|
});
|
||||||
|
this.x = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var _foo = _classPrivateFieldLooseKey("foo");
|
||||||
|
|
||||||
|
var _foo2 = function _foo2() {};
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
class A {
|
||||||
|
x = 2;
|
||||||
|
|
||||||
|
#foo() {}
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"validateLogs": true,
|
||||||
|
"presets": [
|
||||||
|
["env", {
|
||||||
|
"targets": { "node": 10 },
|
||||||
|
"shippedProposals": true,
|
||||||
|
"loose": true
|
||||||
|
}]
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
["proposal-class-properties", { "loose": false }],
|
||||||
|
["proposal-private-methods", { "loose": false }]
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
||||||
|
|
||||||
|
class A {
|
||||||
|
constructor() {
|
||||||
|
_foo.add(this);
|
||||||
|
|
||||||
|
_defineProperty(this, "x", 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var _foo = new WeakSet();
|
||||||
|
|
||||||
|
var _foo2 = function _foo2() {};
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
class A {
|
||||||
|
x = 2;
|
||||||
|
|
||||||
|
#foo() {}
|
||||||
|
}
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"validateLogs": true,
|
||||||
|
"presets": [
|
||||||
|
["env", {
|
||||||
|
"targets": { "node": 10 },
|
||||||
|
"shippedProposals": true,
|
||||||
|
"loose": true
|
||||||
|
}]
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
["proposal-class-properties", { "loose": true }]
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
var id = 0;
|
||||||
|
|
||||||
|
function _classPrivateFieldLooseKey(name) { return "__private_" + id++ + "_" + name; }
|
||||||
|
|
||||||
|
class A {
|
||||||
|
constructor() {
|
||||||
|
Object.defineProperty(this, _foo, {
|
||||||
|
value: _foo2
|
||||||
|
});
|
||||||
|
this.x = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var _foo = _classPrivateFieldLooseKey("foo");
|
||||||
|
|
||||||
|
var _foo2 = function _foo2() {};
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
class A {
|
||||||
|
x = 2;
|
||||||
|
|
||||||
|
#foo() {}
|
||||||
|
}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"validateLogs": true,
|
||||||
|
"presets": [
|
||||||
|
["env", {
|
||||||
|
"targets": { "node": 10 },
|
||||||
|
"shippedProposals": true
|
||||||
|
}]
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
["proposal-class-properties", { "loose": true }]
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
var id = 0;
|
||||||
|
|
||||||
|
function _classPrivateFieldLooseKey(name) { return "__private_" + id++ + "_" + name; }
|
||||||
|
|
||||||
|
class A {
|
||||||
|
constructor() {
|
||||||
|
Object.defineProperty(this, _foo, {
|
||||||
|
value: _foo2
|
||||||
|
});
|
||||||
|
this.x = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var _foo = _classPrivateFieldLooseKey("foo");
|
||||||
|
|
||||||
|
var _foo2 = function _foo2() {};
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
Though the "loose" option was set to "false" in your @babel/preset-env config, it will not be used for @babel/plugin-proposal-private-methods since the "loose" mode option was set to "true" for @babel/plugin-proposal-class-properties.
|
||||||
|
The "loose" option must be the same for @babel/plugin-proposal-class-properties, @babel/plugin-proposal-private-methods and @babel/plugin-proposal-private-property-in-object (when they are enabled): you can silence this warning by explicitly adding
|
||||||
|
["@babel/plugin-proposal-private-methods", { "loose": true }]
|
||||||
|
to the "plugins" section of your Babel config.
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
class A {
|
||||||
|
x = 2;
|
||||||
|
|
||||||
|
#foo() {}
|
||||||
|
}
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"validateLogs": true,
|
||||||
|
"presets": [
|
||||||
|
["env", {
|
||||||
|
"targets": { "node": 10 },
|
||||||
|
"shippedProposals": true,
|
||||||
|
"loose": true
|
||||||
|
}]
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
["proposal-class-properties", { "loose": false }]
|
||||||
|
]
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
|
||||||
|
|
||||||
|
class A {
|
||||||
|
constructor() {
|
||||||
|
_foo.add(this);
|
||||||
|
|
||||||
|
_defineProperty(this, "x", 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var _foo = new WeakSet();
|
||||||
|
|
||||||
|
var _foo2 = function _foo2() {};
|
||||||
@ -0,0 +1,4 @@
|
|||||||
|
Though the "loose" option was set to "true" in your @babel/preset-env config, it will not be used for @babel/plugin-proposal-private-methods since the "loose" mode option was set to "false" for @babel/plugin-proposal-class-properties.
|
||||||
|
The "loose" option must be the same for @babel/plugin-proposal-class-properties, @babel/plugin-proposal-private-methods and @babel/plugin-proposal-private-property-in-object (when they are enabled): you can silence this warning by explicitly adding
|
||||||
|
["@babel/plugin-proposal-private-methods", { "loose": false }]
|
||||||
|
to the "plugins" section of your Babel config.
|
||||||
Loading…
x
Reference in New Issue
Block a user