Last active
May 15, 2017 12:55
-
-
Save jsnajdr/a0954a1f45132ce2a4272b308024f77a to your computer and use it in GitHub Desktop.
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
/* eslint-disable no-console */ | |
export default function transformer( file, api ) { | |
const j = api.jscodeshift; | |
const ReactUtils = require( 'react-codemod/transforms/utils/ReactUtils' )( j ); | |
const root = j( file.source ); | |
ReactUtils.findReactES6ClassDeclaration( root ).forEach( checkEventHandlers ); | |
function checkEventHandlers( classPath ) { | |
// Find JSX attributes whose value is an expression container | |
const eventHandlerAttributes = j( classPath ).find( j.JSXAttribute ).filter( attrPath => { | |
const name = attrPath.value.name.name; | |
const value = attrPath.value.value; | |
return name && value && value.type === 'JSXExpressionContainer'; | |
} ); | |
// Filter just the JSX attributes that have form `{ this.something }` | |
const thisMethodCalls = eventHandlerAttributes.filter( attrPath => { | |
const expression = attrPath.value.value.expression; | |
return j.match( expression, { | |
type: 'MemberExpression', | |
object: { type: 'ThisExpression' }, | |
property: { type: 'Identifier' } | |
} ); | |
} ); | |
thisMethodCalls.forEach( thisMethodCall => { | |
// Extract the method name in the `{ this.method }` attribute value | |
const method = thisMethodCall.value.value.expression.property.name; | |
// Find class properties that define the `method` | |
const classProps = j( classPath ).find( j.ClassProperty, { | |
key: { type: 'Identifier', name: method } | |
} ); | |
// Find class method definitions that define the `method` | |
const methods = j( classPath ).find( j.MethodDefinition, { | |
key: { type: 'Identifier', name: method } | |
} ); | |
// Are there assignments that assign to the `this.method` property? | |
// It's very likely that these are instance properties created in the constructor. | |
const assignCalls = j( classPath ).find( j.AssignmentExpression, { | |
left: { | |
type: 'MemberExpression', | |
object: { type: 'ThisExpression' }, | |
property: { type: 'Identifier', name: method } | |
} | |
} ); | |
// Are there calls to `this.method.bind`? It's very likely that these are | |
// binding the class methods to the right `this`. | |
const bindCalls = j( classPath ).find( j.CallExpression, { | |
callee: { | |
type: 'MemberExpression', | |
object: { | |
type: 'MemberExpression', | |
object: { type: 'ThisExpression' }, | |
property: { type: 'Identifier', name: method } | |
}, | |
property: { type: 'Identifier', name: 'bind' } | |
} | |
} ); | |
if ( methods.size() > 0 ) { | |
if ( bindCalls.size() === 0 ) { | |
console.log( `ERR: ${ method } defined as method (${ file.path })` ); | |
} | |
} else if ( classProps.size() > 0 ) { | |
classProps.forEach( classProp => { | |
const type = classProp.value.value.type; | |
// We expect the handler properties to be arrow functions | |
if ( type !== 'ArrowFunctionExpression' ) { | |
console.log( `ERR: ${ method } prop is not arrow func but ${ type } (${ file.path })` ); | |
} | |
} ); | |
} else if ( assignCalls.size() === 0 ) { | |
console.log( `ERR: ${ method } not found as instance prop (${ file.path })` ); | |
} | |
} ); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment