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';