Last active
February 9, 2022 11:59
-
-
Save dounan/dba4fef8ff63a4661a13e27f3e3dce32 to your computer and use it in GitHub Desktop.
Codemod to convert all components to react-puritan PureComponent
This file contains 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
/** | |
* jscodeshift -t ./transforms/puritan-pure-component.js --explicit-require=true --extensions=jsx ../flexport/webpack/assets/javascr | |
ipts | |
*/ | |
'use strict'; | |
const { basename, extname, dirname } = require('path'); | |
module.exports = (file, api, options) => { | |
const j = api.jscodeshift; | |
require('./utils/array-polyfills'); | |
const ReactUtils = require('./utils/ReactUtils')(j); | |
const printOptions = | |
options.printOptions || { | |
quote: 'single', | |
trailingComma: true, | |
flowObjectCommas: true, | |
arrowParensAlways: true, | |
arrayBracketSpacing: false, | |
objectCurlySpacing: false, | |
}; | |
const root = j(file.source); | |
const findRequirePathAndBinding = (moduleName) => { | |
let result = null; | |
const requireCall = root.find(j.VariableDeclarator, { | |
id: {type: 'Identifier'}, | |
init: { | |
callee: {name: 'require'}, | |
arguments: [{value: moduleName}], | |
}, | |
}); | |
const importStatement = root.find(j.ImportDeclaration, { | |
source: { | |
value: moduleName, | |
}, | |
}); | |
if (importStatement.size()) { | |
importStatement.forEach(path => { | |
result = { | |
path, | |
binding: path.value.specifiers[0].local.name, | |
type: 'import', | |
}; | |
}); | |
} else if (requireCall.size()) { | |
requireCall.forEach(path => { | |
result = { | |
path, | |
binding: path.value.id.name, | |
type: 'require', | |
}; | |
}); | |
} | |
return result; | |
}; | |
const COMPONENT_REGEX = /^(Pure)?Component$/; | |
const isReactComponent = (name) => COMPONENT_REGEX.test(name); | |
if (ReactUtils.hasReact(root)) { | |
let replaced = false; | |
const update = path => { | |
if (!path.value.superClass) { | |
return; | |
} | |
let superClasName; | |
if (path.value.superClass.type === 'Identifier') { | |
superClasName = path.value.superClass.name; | |
} else if (path.value.superClass.type === 'MemberExpression') { | |
superClasName = path.value.superClass.property.name; | |
} else { | |
return; | |
} | |
if (!isReactComponent(superClasName) || isBaseTypeComponent(path)) { | |
return; | |
} | |
replaced = true; | |
// updateComments(path); | |
const newClassDeclaration = j.classDeclaration( | |
path.value.id, | |
path.value.body, | |
j.identifier('PureComponent'), | |
); | |
newClassDeclaration.comments = path.value.comments; | |
newClassDeclaration.superTypeParameters = path.value.superTypeParameters; | |
j(path).replaceWith(newClassDeclaration); | |
}; | |
// This detects our custom Component class in types/BaseTypes that takes in 4 | |
// generic types and tries to do some precise typing. | |
function isBaseTypeComponent(path) { | |
return path.value.superTypeParameters && path.value.superTypeParameters.params.length === 4; | |
} | |
function updateComments(path) { | |
if (path.parentPath.value.type === 'ExportDefaultDeclaration' || | |
path.parentPath.value.type === 'ExportNamedDeclaration') { | |
path = path.parentPath; | |
} | |
path.value.comments = (path.value.comments || []).concat( | |
[j.commentBlock('* @extends React.Component ', true, false)], | |
); | |
}; | |
function removeComponentImports(path) { | |
path.value.specifiers = path.value.specifiers.filter(s => { | |
return ( | |
s.type !== 'ImportSpecifier' || | |
s.imported.type !== 'Identifier' || | |
!isComponentString(s.imported.name) | |
); | |
}); | |
} | |
function isComponentString(string) { | |
return string === 'Component' || string === 'PureComponent'; | |
} | |
root.find(j.ClassDeclaration).forEach(update); | |
if (replaced) { | |
const reactPathAndBinding = | |
findRequirePathAndBinding('react') || | |
findRequirePathAndBinding('React'); | |
j(reactPathAndBinding.path).insertAfter(j.template.statement([ | |
`import {PureComponent} from 'react-puritan';` | |
])); | |
root.find(j.ImportDeclaration, { | |
source: { | |
type: 'Literal', | |
value: 'react', | |
} | |
}).forEach(removeComponentImports); | |
} | |
} | |
return root.toSource(printOptions); | |
}; | |
module.exports.parser = 'flow'; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment