Skip to content

Instantly share code, notes, and snippets.

@urugator
Last active March 6, 2020 14:40
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save urugator/ffc92893fafd70d6e52019f38594ece9 to your computer and use it in GitHub Desktop.
Save urugator/ffc92893fafd70d6e52019f38594ece9 to your computer and use it in GitHub Desktop.
// plugin.js
'use strict';
const BabelParser = require("@babel/parser");
// https://github.com/jamiebuilds/babel-handbook/blob/master/translations/en/plugin-handbook.md
// TODO import decorate/extendObservable
module.exports = function ({ types }) {
const visitor = {
/*
Visiting ClassBody is too late
@babel/plugin-proposal-class-properties
transforms properties in ClassDeclaration
*/
ClassDeclaration(path) {
// path.ClassDeclatation.ClassBody.Array
const members = path.node.body.body;
const decoratedMembers = [];
let iter = 0;
let constructorIndex = -1;
for (let node of members) {
if (types.isClassProperty(node)) {
const comments = node.leadingComments;
if (Array.isArray(comments)) {
const closestComment = comments[comments.length - 1];
const matches = closestComment.value.match(/\s*@(.+)/);
decoratedMembers.push({
key: node.key, // "Identifier","StringLiteral", "NumericLiteral"
code: matches[1]
});
}
} else if (types.isClassMethod(node)) {
if (node.kind === 'constructor') {
constructorIndex = iter;
}
// TODO method,getter
}
iter++;
}
// decorate(this, { key: decorator })
const props = decoratedMembers.map(({ key, code }) => {
// TODO clone key node?
// parsing probably slow
const parsedCode = BabelParser.parseExpression(code);
return types.ObjectProperty(key, parsedCode);
});
const callee = types.Identifier('decorate');
const thisExpression = types.ThisExpression();
const object = types.ObjectExpression(props);
const callExpression = types.CallExpression(callee, [
thisExpression,
object,
]);
const expressionStatement = types.ExpressionStatement(callExpression);
if (constructorIndex === -1) {
// Create constructor
// TODO call super(); if extends
// TODO place constructor after fields, before methods
const constructorBody = types.BlockStatement([expressionStatement]);
const constructor = types.ClassMethod(
"constructor",
types.identifier("constructor"),
[],
constructorBody,
);
const classBodyPath = path.get('body');
classBodyPath.pushContainer('body', constructor)
} else {
// Push at the end of constructor
const constructorBodyPath = path.get(`body.body.${constructorIndex}.body`);
constructorBodyPath.pushContainer('body', expressionStatement)
}
},
}
return {
//inherits: require('@babel/plugin-syntax-class-properties'),
visitor,
}
}
// index.js
'use strict';
const Fs = require('fs');
const Babel = require("@babel/core");
const input = Fs.readFileSync(__dirname + '/input.js');
const result = Babel.transform(input, {
plugins: [
//'@babel/plugin-syntax-class-properties',
Babel.createConfigItem(require("./plugin.js")),
'@babel/plugin-proposal-class-properties',
]
});
console.log(result.code);
// input.js
'use strict';
class A {
// @observable
field = "value";
constructor() {
console.log('peice of code');
}
}
class B {
// @observable
field = "value";
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment