Last active
November 25, 2020 11:29
-
-
Save jwarby/ea30f10a116e1a02fe6f89c0d58a0b3c to your computer and use it in GitHub Desktop.
JSCodeshift codemod for replacing `autobind` decorators with fat arrow functions
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Replaces instances of @autobind decorators with a fat arrow function. | |
* | |
* !! **YMMV - only tested with ES6 class property member functions.** | |
* | |
* Given the following input: | |
* | |
* class MyClass { | |
* @autobind | |
* myAutoboundFunction() { | |
* } | |
* } | |
* | |
* The output will be: | |
* | |
* class MyClass { | |
* myAutoboundFunction = () => { | |
* } | |
* } | |
* | |
* Usage: | |
* | |
* jscodeshift -t autobind-to-arrow.js [FILE]... | |
* | |
* Note: the underlying library to replace the code doesn't support outputting | |
* without semicolons, so if you don't want semicolons you will have to use an | |
* external tool (eg `prettier`) to remove semicolons after running the codemod. | |
*/ | |
const NAME_AUTOBIND = 'autobind' | |
const getName = ({ value }) => value.key.name | |
module.exports = function(file, api) { | |
const j = api.jscodeshift | |
/* Helper functions below borrowed from | |
* https://github.com/reactjs/react-codemod/blob/96b55a0e/transforms/manual-bind-to-arrow.js#L29 | |
*/ | |
const withComments = (to, from) => { | |
to.comments = from.comments | |
return to | |
} | |
const createArrowFunctionExpression = fn => { | |
const arrowFunc = j.arrowFunctionExpression( | |
fn.params, | |
fn.body, | |
false | |
) | |
arrowFunc.returnType = fn.returnType | |
arrowFunc.defaults = fn.defaults | |
arrowFunc.rest = fn.rest | |
arrowFunc.async = fn.async | |
return arrowFunc | |
} | |
const createArrowProperty = prop => ( | |
withComments(j.classProperty( | |
j.identifier(prop.key.name), | |
createArrowFunctionExpression(prop.value), | |
null, | |
false | |
), prop) | |
) | |
const root = j(file.source) | |
return root | |
.find(j.Decorator) | |
.filter(({ value }) => | |
value.expression.name === NAME_AUTOBIND | |
) | |
.forEach(d => { | |
const methods = root | |
.find(j.MethodDefinition) | |
.filter(path => getName(path) === getName(d.parentPath.parentPath)) | |
methods.replaceWith(m => createArrowProperty(m.node)) | |
}) | |
.remove() | |
.toSource() | |
} |
There is a modification for not removing other decorators:
module.exports = function(file, api) {
const j = api.jscodeshift;
/* Helper functions below borrowed from
* https://github.com/reactjs/react-codemod/blob/96b55a0e/transforms/manual-bind-to-arrow.js#L29
*/
const withComments = (to, from) => {
to.comments = from.comments;
return to;
};
const withDecorators = (to, from) => {
to.decorators = from.decorators;
return to;
};
const createArrowFunctionExpression = fn => {
const arrowFunc = j.arrowFunctionExpression(fn.params, fn.body, false);
arrowFunc.returnType = fn.returnType;
arrowFunc.defaults = fn.defaults;
arrowFunc.rest = fn.rest;
arrowFunc.async = fn.async;
return arrowFunc;
};
const createArrowProperty = prop =>
withComments(
withDecorators(
j.classProperty(
j.identifier(prop.key.name),
createArrowFunctionExpression(prop.value),
null,
false,
),
prop,
),
prop,
);
const root = j(file.source);
return root
.find(j.Decorator)
.filter(({ value }) => value.expression.name === NAME_AUTOBIND)
.forEach(d => {
const methods = root
.find(j.MethodDefinition)
.filter(path => getName(path) === getName(d.parentPath.parentPath));
methods.replaceWith(m => {
m.node.decorators = m.node.decorators.filter(
({ expression }) => expression.name !== NAME_AUTOBIND,
);
return createArrowProperty(m.node);
});
})
.remove()
.toSource();
};
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This will also strip any other decorators attached to an
@autobind
method, unfortunately.