Skip to content

Commit

Permalink
Greater spec compliance for class properties (babel#4544)
Browse files Browse the repository at this point in the history
* Desugar class properties to Object.defineProperty per spec
* Define uninitialized static class properties with value=undefined
* Make class-properties increased spec compliance opt-in (spec: true)
  • Loading branch information
motiz88 authored and panagosg7 committed Jan 17, 2017
1 parent 40d1793 commit 7ae8b90
Show file tree
Hide file tree
Showing 39 changed files with 466 additions and 11 deletions.
13 changes: 13 additions & 0 deletions packages/babel-plugin-transform-class-properties/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,29 @@
$ npm install babel-plugin-transform-class-properties
```

### Options: `spec`

- Class properties are compiled to use `Object.defineProperty`
- Static fields are now defined even if they are not initialized

## Usage

### Via `.babelrc` (Recommended)

**.babelrc**

```json
// without options
{
"plugins": ["transform-class-properties"]
}

// with options
{
"plugins": [
["transform-class-properties", { "spec": true }]
]
}
```

### Via CLI
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"dependencies": {
"babel-helper-function-name": "^6.18.0",
"babel-plugin-syntax-class-properties": "^6.8.0",
"babel-runtime": "^6.9.1"
"babel-runtime": "^6.9.1",
"babel-template": "^6.15.0"
},
"devDependencies": {
"babel-helper-plugin-test-runner": "^6.18.0"
Expand Down
39 changes: 29 additions & 10 deletions packages/babel-plugin-transform-class-properties/src/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint max-len: 0 */
// todo: define instead of assign
import nameFunction from "babel-helper-function-name";
import template from "babel-template";

export default function ({ types: t }) {
let findBareSupers = {
Expand All @@ -20,11 +20,31 @@ export default function ({ types: t }) {
}
};

const buildObjectDefineProperty = template(`
Object.defineProperty(REF, KEY, {
// configurable is false by default
enumerable: true,
writable: true,
value: VALUE
});
`);

const buildClassPropertySpec = (ref, {key, value, computed}) => buildObjectDefineProperty({
REF: ref,
KEY: (t.isIdentifier(key) && !computed) ? t.stringLiteral(key.name) : key,
VALUE: value ? value : t.identifier("undefined")
});

const buildClassPropertyNonSpec = (ref, {key, value, computed}) => t.expressionStatement(
t.assignmentExpression("=", t.memberExpression(ref, key, computed || t.isLiteral(key)), value)
);

return {
inherits: require("babel-plugin-syntax-class-properties"),

visitor: {
Class(path) {
Class(path, state) {
const buildClassProperty = state.opts.spec ? buildClassPropertySpec : buildClassPropertyNonSpec;
let isDerived = !!path.node.superClass;
let constructor;
let props = [];
Expand Down Expand Up @@ -55,19 +75,18 @@ export default function ({ types: t }) {
for (let prop of props) {
let propNode = prop.node;
if (propNode.decorators && propNode.decorators.length > 0) continue;
if (!propNode.value) continue;

// In non-spec mode, all properties without values are ignored.
// In spec mode, *static* properties without values are still defined (see below).
if (!state.opts.spec && !propNode.value) continue;

let isStatic = propNode.static;
let isComputed = propNode.computed || t.isLiteral(prop.key);

if (isStatic) {
nodes.push(t.expressionStatement(
t.assignmentExpression("=", t.memberExpression(ref, propNode.key, isComputed), propNode.value)
));
nodes.push(buildClassProperty(ref, propNode));
} else {
instanceBody.push(t.expressionStatement(
t.assignmentExpression("=", t.memberExpression(t.thisExpression(), propNode.key, isComputed), propNode.value)
));
if (!propNode.value) continue; // Ignore instance property with no value in spec mode
instanceBody.push(buildClassProperty(t.thisExpression(), propNode));
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
var foo = "bar";

class Foo {
bar = foo;

constructor() {
var foo = "foo";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
var foo = "bar";

var Foo = function Foo() {
babelHelpers.classCallCheck(this, Foo);

_initialiseProps.call(this);

var foo = "foo";
};

var _initialiseProps = function () {
Object.defineProperty(this, "bar", {
enumerable: true,
writable: true,
value: foo
});
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class Foo extends Bar {
bar = "foo";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
var Foo = function (_Bar) {
babelHelpers.inherits(Foo, _Bar);

function Foo(...args) {
var _temp, _this, _ret;

babelHelpers.classCallCheck(this, Foo);
return _ret = (_temp = (_this = babelHelpers.possibleConstructorReturn(this, (Foo.__proto__ || Object.getPrototypeOf(Foo)).call(this, ...args)), _this), Object.defineProperty(_this, "bar", {
enumerable: true,
writable: true,
value: "foo"
}), _temp), babelHelpers.possibleConstructorReturn(_this, _ret);
}

return Foo;
}(Bar);
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class Child extends Parent {
constructor() {
super();
}

scopedFunctionWithThis = () => {
this.name = {};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"use strict";

var Child = function (_Parent) {
babelHelpers.inherits(Child, _Parent);

function Child() {
babelHelpers.classCallCheck(this, Child);

var _this = babelHelpers.possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).call(this));

Object.defineProperty(_this, "scopedFunctionWithThis", {
enumerable: true,
writable: true,
value: function value() {
_this.name = {};
}
});
return _this;
}

return Child;
}(Parent);
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"plugins": ["external-helpers", ["transform-class-properties", { "spec": true }]],
"presets": ["stage-0", "es2015"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class Foo {
bar;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
var Foo = function Foo() {
babelHelpers.classCallCheck(this, Foo);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class Foo {
bar = "foo";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
var Foo = function Foo() {
babelHelpers.classCallCheck(this, Foo);
Object.defineProperty(this, "bar", {
enumerable: true,
writable: true,
value: "foo"
});
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export default param =>
class App {
static props = {
prop1: 'prop1',
prop2: 'prop2'
}

getParam() {
return param;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
export default (param => {
var _class, _temp;

return _temp = _class = function () {
function App() {
babelHelpers.classCallCheck(this, App);
}

babelHelpers.createClass(App, [{
key: 'getParam',
value: function getParam() {
return param;
}
}]);
return App;
}(), Object.defineProperty(_class, 'props', {
enumerable: true,
writable: true,
value: {
prop1: 'prop1',
prop2: 'prop2'
}
}), _temp;
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"plugins": ["external-helpers", ["transform-class-properties", { "spec": true }], "transform-es2015-classes", "transform-es2015-block-scoping", "syntax-class-properties"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
call(class {
static test = true
});

export default class {
static test = true
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
var _class, _temp;

call((_temp = _class = function _class() {
babelHelpers.classCallCheck(this, _class);
}, Object.defineProperty(_class, "test", {
enumerable: true,
writable: true,
value: true
}), _temp));

var _class2 = function _class2() {
babelHelpers.classCallCheck(this, _class2);
};

Object.defineProperty(_class2, "test", {
enumerable: true,
writable: true,
value: true
});
export default _class2;
;
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
function withContext(ComposedComponent) {
return class WithContext extends Component {

static propTypes = {
context: PropTypes.shape(
{
addCss: PropTypes.func,
setTitle: PropTypes.func,
setMeta: PropTypes.func,
}
),
};
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
function withContext(ComposedComponent) {
var _class, _temp;

return _temp = _class = function (_Component) {
babelHelpers.inherits(WithContext, _Component);

function WithContext() {
babelHelpers.classCallCheck(this, WithContext);
return babelHelpers.possibleConstructorReturn(this, (WithContext.__proto__ || Object.getPrototypeOf(WithContext)).apply(this, arguments));
}

return WithContext;
}(Component), Object.defineProperty(_class, "propTypes", {
enumerable: true,
writable: true,
value: {
context: PropTypes.shape({
addCss: PropTypes.func,
setTitle: PropTypes.func,
setMeta: PropTypes.func
})
}
}), _temp;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
class MyClass {
myAsyncMethod = async () => {
console.log(this);
}
}

(class MyClass2 {
myAsyncMethod = async () => {
console.log(this);
}
})

export default class MyClass3 {
myAsyncMethod = async () => {
console.log(this);
}
}
Loading

0 comments on commit 7ae8b90

Please sign in to comment.