Skip to content

Instantly share code, notes, and snippets.

@jsnajdr
Last active May 15, 2017 12:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jsnajdr/a0954a1f45132ce2a4272b308024f77a to your computer and use it in GitHub Desktop.
Save jsnajdr/a0954a1f45132ce2a4272b308024f77a to your computer and use it in GitHub Desktop.
/* 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