transforms/manual-bind-to-arrow.js (117 lines of code) (raw):
/**
* Copyright 2015-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
/**
* class Component extends React.Component {
* constructor() { this.onClick = this.onClick.bind(this); }
* onClick() { }
* }
*
* -->
*
* class Component extends React.Component {
* onClick = () => { }
* }
*/
export default function transformer(file, api, options) {
const j = api.jscodeshift;
const doesNotUseArguments = require('./utils/doesNotUseArguments')(j);
const printOptions = options.printOptions || {};
var root = j(file.source);
// Helper functions to transform a method declaration to an arrow function
// By default recast drops comments and jscodeshift doesn't have a way to
// set the return type in the convenience method. Otherwise we would have
// inlined all those.
function withComments(to, from) {
to.comments = from.comments;
return to;
}
function createArrowFunctionExpression(fn) {
var 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;
}
function createArrowProperty(prop) {
return withComments(
j.classProperty(
j.identifier(prop.key.name),
createArrowFunctionExpression(prop.value),
null,
false
),
prop
);
}
var hasChanged = false;
var transform = root.find(j.AssignmentExpression).forEach(path => {
// Check that the englobing function is constructor
var methodPath = path;
while (
methodPath &&
(methodPath.node.type !== 'MethodDefinition' ||
methodPath.node.kind !== 'constructor')
) {
methodPath = methodPath.parentPath;
}
if (!methodPath) {
return;
}
// Check that it looks like
// this.method = this.method.bind(this);
// or
// (this: any).method = this.method.bind(this);
// or
// self.method = this.method.bind(this);
if (
!(
path.node.left.type === 'MemberExpression' &&
// this
(path.node.left.object.type === 'ThisExpression' ||
// self
(path.node.left.object.type === 'Identifier' &&
path.node.left.object.name === 'self') ||
// (this: any)
(path.node.left.object.type === 'TypeCastExpression' &&
path.node.left.object.expression.type === 'ThisExpression')) &&
path.node.left.property.type === 'Identifier' &&
path.node.right.type === 'CallExpression' &&
path.node.right.callee.type === 'MemberExpression' &&
path.node.right.callee.property.type === 'Identifier' &&
path.node.right.callee.property.name === 'bind' &&
path.node.right.callee.object.type === 'MemberExpression' &&
path.node.right.callee.object.property.type === 'Identifier' &&
path.node.right.callee.object.object.type === 'ThisExpression' &&
path.node.left.property.name ===
path.node.right.callee.object.property.name &&
true
)
) {
return;
}
// Find the method() declaration and replace it with an arrow function
var methodName = path.node.left.property.name;
const componentDecl = methodPath.parentPath;
var methods = j(componentDecl)
.find(j.MethodDefinition)
.filter(
path =>
path.node.key.type === 'Identifier' &&
path.node.key.name === methodName &&
doesNotUseArguments(path, file.path)
);
// Do not remove the binding if there's no corresponding method to turn
// into an arrow function, or if the method uses `arguments` keyword inside
// it.
if (methods.size() === 0) {
return;
}
methods.replaceWith(path => createArrowProperty(path.node));
// Remove the line
// this.method = this.method.bind(this);
j(path.parentPath).remove();
var selfCount = j(methodPath)
.find(j.Identifier, { name: 'self' })
.size();
if (selfCount === 1) {
// Remove the line
// const self: any = this;
// If self is present somewhere else in the method, then it is
// not safe to do.
j(methodPath)
.find(j.VariableDeclaration)
.filter(
path =>
j(path)
.find(j.Identifier, { name: 'self' })
.size() === 1
)
.remove();
}
// If we delete everything from the constructor but the super() call,
// then delete the entire constructor.
var canDeleteConstructor = true;
methodPath.node.value.body.body.forEach(node => {
if (
!node ||
(node.type === 'ExpressionStatement' &&
node.expression.type === 'CallExpression' &&
// babylon parser
(node.expression.callee.type === 'Super' ||
// flow parser
(node.expression.callee.type === 'Identifier' &&
node.expression.callee.name === 'super')))
) {
return;
}
canDeleteConstructor = false;
});
if (canDeleteConstructor) {
j(methodPath).remove();
}
hasChanged++;
});
if (hasChanged) {
return transform.toSource(printOptions);
}
return null;
}
// module.exports.parser = 'flow';