add legacy decorators support to strict class fields (#10616)

This PR allows legacy decorators to work with strict class fields, which
are now stage 3 and have shipped in a number of browsers. Allowing this will
allow users of the legacy transform (which is currently recommended by the
champions of the decorator proposal) to use the proper class field semantics
for non-decorated fields, which should help prevent breakage later on.

This change is not a breaking change, since users had to explicitly opt into
loose mode in class fields before. This just gives them the option to remove
that opt-in.
This commit is contained in:
Chris Garrett 2019-11-04 10:05:42 -08:00 committed by Huáng Jùnliàng
parent bea1b0d0af
commit 5c0d8a9de7
13 changed files with 186 additions and 5 deletions

View File

@ -1040,9 +1040,7 @@ helpers.initializerWarningHelper = helper("7.0.0-beta.0")`
export default function _initializerWarningHelper(descriptor, context){ export default function _initializerWarningHelper(descriptor, context){
throw new Error( throw new Error(
'Decorating class property failed. Please ensure that ' + 'Decorating class property failed. Please ensure that ' +
'proposal-class-properties is enabled and set to use loose mode. ' + 'proposal-class-properties is enabled and runs after the decorators transform.'
'To use proposal-class-properties in spec mode with decorators, wait for ' +
'the next major version of decorators in stage 2.'
); );
} }
`; `;

View File

@ -0,0 +1,12 @@
function dec() {}
// Create a local function binding so babel has to change the name of the helper
function _defineProperty() {}
class A {
@dec a;
@dec b = 123;
c = 456;
}

View File

@ -0,0 +1,7 @@
{
"plugins": [
["proposal-decorators", { "legacy": true }],
["proposal-class-properties"],
"transform-classes"
]
}

View File

@ -0,0 +1,40 @@
var _class, _descriptor, _descriptor2, _temp;
function _initializerDefineProperty(target, property, descriptor, context) { if (!descriptor) return; Object.defineProperty(target, property, { enumerable: descriptor.enumerable, configurable: descriptor.configurable, writable: descriptor.writable, value: descriptor.initializer ? descriptor.initializer.call(context) : void 0 }); }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperty2(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; }
function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { var desc = {}; Object.keys(descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; desc.configurable = !!desc.configurable; if ('value' in desc || desc.initializer) { desc.writable = true; } desc = decorators.slice().reverse().reduce(function (desc, decorator) { return decorator(target, property, desc) || desc; }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; desc.initializer = undefined; } if (desc.initializer === void 0) { Object.defineProperty(target, property, desc); desc = null; } return desc; }
function _initializerWarningHelper(descriptor, context) { throw new Error('Decorating class property failed. Please ensure that ' + 'proposal-class-properties is enabled and runs after the decorators transform.'); }
function dec() {} // Create a local function binding so babel has to change the name of the helper
function _defineProperty() {}
let A = (_class = (_temp = function A() {
"use strict";
_classCallCheck(this, A);
_initializerDefineProperty(this, "a", _descriptor, this);
_initializerDefineProperty(this, "b", _descriptor2, this);
_defineProperty2(this, "c", 456);
}, _temp), (_descriptor = _applyDecoratedDescriptor(_class.prototype, "a", [dec], {
configurable: true,
enumerable: true,
writable: true,
initializer: null
}), _descriptor2 = _applyDecoratedDescriptor(_class.prototype, "b", [dec], {
configurable: true,
enumerable: true,
writable: true,
initializer: function () {
return 123;
}
})), _class);

View File

@ -0,0 +1,9 @@
function dec() {}
class A {
@dec a;
@dec b = 123;
c = 456;
}

View File

@ -0,0 +1,7 @@
{
"plugins": [
["proposal-decorators", { "legacy": true }],
["proposal-class-properties", { "loose": true }],
"transform-classes"
]
}

View File

@ -0,0 +1,35 @@
var _class, _descriptor, _descriptor2, _temp;
function _initializerDefineProperty(target, property, descriptor, context) { if (!descriptor) return; Object.defineProperty(target, property, { enumerable: descriptor.enumerable, configurable: descriptor.configurable, writable: descriptor.writable, value: descriptor.initializer ? descriptor.initializer.call(context) : void 0 }); }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { var desc = {}; Object.keys(descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; desc.configurable = !!desc.configurable; if ('value' in desc || desc.initializer) { desc.writable = true; } desc = decorators.slice().reverse().reduce(function (desc, decorator) { return decorator(target, property, desc) || desc; }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; desc.initializer = undefined; } if (desc.initializer === void 0) { Object.defineProperty(target, property, desc); desc = null; } return desc; }
function _initializerWarningHelper(descriptor, context) { throw new Error('Decorating class property failed. Please ensure that ' + 'proposal-class-properties is enabled and runs after the decorators transform.'); }
function dec() {}
let A = (_class = (_temp = function A() {
"use strict";
_classCallCheck(this, A);
_initializerDefineProperty(this, "a", _descriptor, this);
_initializerDefineProperty(this, "b", _descriptor2, this);
this.c = 456;
}, _temp), (_descriptor = _applyDecoratedDescriptor(_class.prototype, "a", [dec], {
configurable: true,
enumerable: true,
writable: true,
initializer: null
}), _descriptor2 = _applyDecoratedDescriptor(_class.prototype, "b", [dec], {
configurable: true,
enumerable: true,
writable: true,
initializer: function () {
return 123;
}
})), _class);

View File

@ -0,0 +1,9 @@
function dec() {}
class A {
@dec a;
@dec b = 123;
c = 456;
}

View File

@ -0,0 +1,7 @@
{
"plugins": [
["proposal-decorators", { "legacy": true }],
["proposal-class-properties"],
"transform-classes"
]
}

View File

@ -0,0 +1,37 @@
var _class, _descriptor, _descriptor2, _temp;
function _initializerDefineProperty(target, property, descriptor, context) { if (!descriptor) return; Object.defineProperty(target, property, { enumerable: descriptor.enumerable, configurable: descriptor.configurable, writable: descriptor.writable, value: descriptor.initializer ? descriptor.initializer.call(context) : void 0 }); }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
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; }
function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { var desc = {}; Object.keys(descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; desc.configurable = !!desc.configurable; if ('value' in desc || desc.initializer) { desc.writable = true; } desc = decorators.slice().reverse().reduce(function (desc, decorator) { return decorator(target, property, desc) || desc; }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; desc.initializer = undefined; } if (desc.initializer === void 0) { Object.defineProperty(target, property, desc); desc = null; } return desc; }
function _initializerWarningHelper(descriptor, context) { throw new Error('Decorating class property failed. Please ensure that ' + 'proposal-class-properties is enabled and runs after the decorators transform.'); }
function dec() {}
let A = (_class = (_temp = function A() {
"use strict";
_classCallCheck(this, A);
_initializerDefineProperty(this, "a", _descriptor, this);
_initializerDefineProperty(this, "b", _descriptor2, this);
_defineProperty(this, "c", 456);
}, _temp), (_descriptor = _applyDecoratedDescriptor(_class.prototype, "a", [dec], {
configurable: true,
enumerable: true,
writable: true,
initializer: null
}), _descriptor2 = _applyDecoratedDescriptor(_class.prototype, "b", [dec], {
configurable: true,
enumerable: true,
writable: true,
initializer: function () {
return 123;
}
})), _class);

View File

@ -12,7 +12,7 @@ function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _d
function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { var desc = {}; Object.keys(descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; desc.configurable = !!desc.configurable; if ('value' in desc || desc.initializer) { desc.writable = true; } desc = decorators.slice().reverse().reduce(function (desc, decorator) { return decorator(target, property, desc) || desc; }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; desc.initializer = undefined; } if (desc.initializer === void 0) { Object.defineProperty(target, property, desc); desc = null; } return desc; } function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { var desc = {}; Object.keys(descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; desc.configurable = !!desc.configurable; if ('value' in desc || desc.initializer) { desc.writable = true; } desc = decorators.slice().reverse().reduce(function (desc, decorator) { return decorator(target, property, desc) || desc; }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; desc.initializer = undefined; } if (desc.initializer === void 0) { Object.defineProperty(target, property, desc); desc = null; } return desc; }
function _initializerWarningHelper(descriptor, context) { throw new Error('Decorating class property failed. Please ensure that ' + 'proposal-class-properties is enabled and set to use loose mode. ' + 'To use proposal-class-properties in spec mode with decorators, wait for ' + 'the next major version of decorators in stage 2.'); } function _initializerWarningHelper(descriptor, context) { throw new Error('Decorating class property failed. Please ensure that ' + 'proposal-class-properties is enabled and runs after the decorators transform.'); }
function dec() {} function dec() {}

View File

@ -290,4 +290,24 @@ export default {
]), ]),
); );
}, },
CallExpression(path, state) {
if (path.node.arguments.length !== 3) return;
if (!WARNING_CALLS.has(path.node.arguments[2])) return;
// If the class properties plugin isn't enabled, this line will add an unused helper
// to the code. It's not ideal, but it's ok since the configuration is not valid anyway.
if (path.node.callee.name !== state.addHelper("defineProperty").name) {
return;
}
path.replaceWith(
t.callExpression(state.addHelper("initializerDefineProperty"), [
t.cloneNode(path.get("arguments")[0].node),
t.cloneNode(path.get("arguments")[1].node),
t.cloneNode(path.get("arguments.2.arguments")[0].node),
t.cloneNode(path.get("arguments.2.arguments")[1].node),
]),
);
},
}; };

View File

@ -2,7 +2,7 @@ var _class, _descriptor;
function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { var desc = {}; Object.keys(descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; desc.configurable = !!desc.configurable; if ('value' in desc || desc.initializer) { desc.writable = true; } desc = decorators.slice().reverse().reduce(function (desc, decorator) { return decorator(target, property, desc) || desc; }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; desc.initializer = undefined; } if (desc.initializer === void 0) { Object.defineProperty(target, property, desc); desc = null; } return desc; } function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { var desc = {}; Object.keys(descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; desc.configurable = !!desc.configurable; if ('value' in desc || desc.initializer) { desc.writable = true; } desc = decorators.slice().reverse().reduce(function (desc, decorator) { return decorator(target, property, desc) || desc; }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; desc.initializer = undefined; } if (desc.initializer === void 0) { Object.defineProperty(target, property, desc); desc = null; } return desc; }
function _initializerWarningHelper(descriptor, context) { throw new Error('Decorating class property failed. Please ensure that ' + 'proposal-class-properties is enabled and set to use loose mode. ' + 'To use proposal-class-properties in spec mode with decorators, wait for ' + 'the next major version of decorators in stage 2.'); } function _initializerWarningHelper(descriptor, context) { throw new Error('Decorating class property failed. Please ensure that ' + 'proposal-class-properties is enabled and runs after the decorators transform.'); }
import { observable } from 'mobx'; import { observable } from 'mobx';
let Foo = (_class = class Foo { let Foo = (_class = class Foo {