Created
March 21, 2016 10:01
-
-
Save Sinewyk/4ba5fbfc30c04d184de9 to your computer and use it in GitHub Desktop.
from mixin to HoC for i18n
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
const path = require('path'); | |
const printOptions = { | |
quote: 'single', | |
trailingComma: true, | |
}; | |
const I18N_MIXIN = 'i18nMixin'; | |
const I18N_MIXIN_FILE = 'mixins/i18n'; | |
const CONNECT_TO_I18N = 'connectToI18n'; | |
module.exports = function transform(file, api) { | |
const fileName = path.parse(file.path).name; | |
console.log(fileName); | |
const source = file.source; | |
const j = api.jscodeshift; | |
const root = j(source); | |
const ReactUtils = require('./utils/ReactUtils')(j); | |
function shouldProcessFile() { | |
return root.find(j.ImportDeclaration, { | |
source: { value: I18N_MIXIN_FILE}, | |
}).size(); | |
} | |
function isI18nMixin(node) { | |
return node.type === 'Identifier' && | |
node.name === I18N_MIXIN; | |
} | |
function hasI18nMixin(classPath) { | |
const spec = ReactUtils.getReactCreateClassSpec(classPath); | |
const mixin = spec && spec.properties.find(ReactUtils.isMixinProperty); | |
return mixin && mixin.value.elements.some(isI18nMixin); | |
} | |
function removeI18nMixinFromMixinProperty(elements) { | |
return j.property( | |
'init', | |
j.identifier('mixins'), | |
j.arrayExpression( | |
elements.filter(element => !isI18nMixin(element)) | |
) | |
); | |
} | |
function isPropTypesProperty(property) { | |
const key = property.key; | |
const value = property.value; | |
return key.name === 'propTypes' && | |
value.type === 'ObjectExpression' && | |
Array.isArray(value.properties) && | |
value.properties.length; | |
} | |
function removeI18nMixinFromClass(classPath) { | |
const spec = ReactUtils.getReactCreateClassSpec(classPath); | |
const properties = spec.properties | |
.map(property => { | |
if (!ReactUtils.isMixinProperty(property)) { | |
return property; | |
} | |
const elements = property.value.elements; | |
return elements.length !== 1 ? removeI18nMixinFromMixinProperty(elements) : null; | |
}) | |
.filter(property => !!property); | |
ReactUtils.findReactCreateClassCallExpression(classPath).replaceWith( | |
ReactUtils.createCreateReactClassCallExpression(properties) | |
); | |
} | |
function transformToVariableDeclaration(classPath) { | |
const reactCallExpression = classPath.value.declaration; | |
const variableIdentifier = j.identifier(fileName); | |
const variableDeclaration = j.variableDeclaration('const', [ | |
j.variableDeclarator( | |
variableIdentifier, | |
reactCallExpression | |
), | |
]); | |
classPath.replace(variableDeclaration); | |
} | |
function exportContainerWithI18n(classPath) { | |
const exportDeclaration = j.exportDefaultDeclaration( | |
j.callExpression( | |
j.identifier(CONNECT_TO_I18N), [ | |
j.identifier(fileName), | |
] | |
) | |
); | |
classPath.insertAfter(exportDeclaration); | |
} | |
function convertToProps(transMethod) { | |
return j.memberExpression( | |
j.memberExpression( | |
j.identifier('this'), | |
j.identifier('props') | |
), | |
j.identifier(transMethod) | |
); | |
} | |
function convertTransToProps(transMethod) { | |
// Maybe do better than root | |
return root | |
.find(j.MemberExpression, { | |
object: { | |
type: 'ThisExpression', | |
}, | |
property: { | |
name: transMethod, | |
}, | |
}) | |
.replaceWith(convertToProps(transMethod)) | |
.size(); | |
} | |
function convertContextToProps() { | |
return root | |
.find(j.MemberExpression, { | |
object: { | |
type: 'ThisExpression', | |
}, | |
property: { | |
name: 'context', | |
}, | |
}) | |
.replaceWith( | |
j.memberExpression( | |
j.identifier('this'), | |
j.identifier('props') | |
) | |
).size(); | |
} | |
function createProperty(propName, propType) { | |
const propTypesImported = root.find(j.ImportSpecifier) | |
.filter(p => p.value.local.name === 'PropTypes') | |
.size() > 0; | |
let propTypesMember; | |
if (propTypesImported) { | |
propTypesMember = j.identifier('PropTypes'); | |
} else { | |
propTypesMember = j.memberExpression( | |
j.identifier('React'), | |
j.identifier('PropTypes') | |
); | |
} | |
return j.property( | |
'init', | |
j.identifier(propName), | |
j.memberExpression( | |
j.memberExpression( | |
propTypesMember, | |
j.identifier(propType) | |
), | |
j.identifier('isRequired') | |
) | |
); | |
} | |
function usesProp(propName) { | |
const propsExpressionsForPropName = root | |
.find(j.MemberExpression, { | |
object: { | |
type: 'MemberExpression', | |
}, | |
property: { | |
name: propName, | |
}, | |
}) | |
.filter(p => { | |
const memberExpression = p.value.object; | |
return memberExpression.object.name === 'this' | |
&& memberExpression.property.name === 'props'; | |
}); | |
return propsExpressionsForPropName.size() > 0; | |
} | |
function addMethodsToPropTypes(classPath) { | |
const spec = ReactUtils.getReactCreateClassSpec(classPath); | |
const properties = spec.properties | |
.map(property => { | |
if (!isPropTypesProperty(property)) { | |
return property; | |
} | |
const propTypes = property.value.properties; | |
if (usesProp('trans')) { | |
propTypes.push(createProperty('trans', 'func')); | |
} | |
if (usesProp('transChoice')) { | |
propTypes.push(createProperty('transChoice', 'func')); | |
} | |
if (usesProp('i18n')) { | |
propTypes.push(createProperty('i18n', 'string')); | |
} | |
return property; | |
}) | |
.filter(property => !!property); | |
ReactUtils.findReactCreateClassCallExpression(classPath).replaceWith( | |
ReactUtils.createCreateReactClassCallExpression(properties) | |
); | |
} | |
if (!shouldProcessFile()) { | |
return source; | |
} | |
const reactClass = ReactUtils.findReactCreateClassExportDefault(root); | |
const classesWithI18nMixin = reactClass.filter(hasI18nMixin); | |
const classesWithTransUsage = classesWithI18nMixin.filter(() => { | |
const numberOfTransChanges = convertTransToProps('trans'); | |
const numberOfTransChoiceChanges = convertTransToProps('transChoice'); | |
const numberOfContextChanges = convertContextToProps(); | |
return numberOfTransChanges > 0 || | |
numberOfTransChoiceChanges > 0 || | |
numberOfContextChanges > 0; | |
}); | |
classesWithI18nMixin | |
.forEach(removeI18nMixinFromClass); | |
classesWithTransUsage | |
.forEach(addMethodsToPropTypes) | |
.forEach(transformToVariableDeclaration) | |
.forEach(exportContainerWithI18n); | |
// Change imports | |
root.find(j.ImportDeclaration, { | |
source: { value: I18N_MIXIN_FILE}, | |
}).replaceWith(() => { | |
if (classesWithTransUsage.size() === 0) { | |
return null; | |
} | |
return j.importDeclaration( | |
[j.importDefaultSpecifier(j.identifier(CONNECT_TO_I18N))], | |
j.literal('modules/i18n/connectToI18n') | |
); | |
}); | |
return root.toSource(printOptions); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment