Skip to content

Instantly share code, notes, and snippets.

@jwarby
Last active November 25, 2020 11:29
Show Gist options
  • Save jwarby/ea30f10a116e1a02fe6f89c0d58a0b3c to your computer and use it in GitHub Desktop.
Save jwarby/ea30f10a116e1a02fe6f89c0d58a0b3c to your computer and use it in GitHub Desktop.
JSCodeshift codemod for replacing `autobind` decorators with fat arrow functions
/**
* 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()
}
@dvdovenko
Copy link

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