Skip to content

Instantly share code, notes, and snippets.

@dounan
Last active February 9, 2022 11:59
Show Gist options
  • Save dounan/dba4fef8ff63a4661a13e27f3e3dce32 to your computer and use it in GitHub Desktop.
Save dounan/dba4fef8ff63a4661a13e27f3e3dce32 to your computer and use it in GitHub Desktop.
Codemod to convert all components to react-puritan PureComponent
/**
* 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