|
"use strict"; |
|
|
|
const isFuncIdentifier = (() => { |
|
const isCallExpression = path => path.parent.node.type === "CallExpression" |
|
const isCallee = path => path.parent.node.callee && |
|
path.parent.node.callee.name === path.node.name; |
|
|
|
return (path) => isCallExpression(path) && isCallee(path); |
|
})(); |
|
|
|
const addReactImport = (() => { |
|
function useImportSyntax(j, root) { |
|
return root |
|
.find(j.ImportDeclaration, { |
|
importKind: 'value' |
|
}) |
|
.length > 0; |
|
} |
|
|
|
function hasReactImport(j, root) { |
|
return root |
|
.find(j.ImportDeclaration, { |
|
source: {value: 'react'} |
|
}) |
|
.length > 0; |
|
} |
|
|
|
function hasReactRequire(j, root) { |
|
return root.find(j.CallExpression, { |
|
callee: {name: 'require'}, |
|
arguments: {0: {value: 'react'}} |
|
}).length > 0; |
|
} |
|
|
|
function findFirstRequire(j, root) { |
|
return root |
|
.find(j.CallExpression, {callee: {name: 'require'}}) |
|
.at(0) |
|
.paths()[0]; |
|
} |
|
|
|
function findFirstImport(j, root) { |
|
return root.find(j.ImportDeclaration).at(0).paths()[0]; |
|
} |
|
|
|
function retainLeadingComment(j, root, newFirstLine) { |
|
const firstNode = root.find(j.Program).get('body', 0).node; |
|
const {comments} = firstNode; |
|
if (comments) { |
|
delete firstNode.comments; |
|
newFirstLine.comments = comments; |
|
} |
|
} |
|
|
|
return (j, root) => { |
|
if (useImportSyntax(j, root)) { |
|
if (hasReactImport(j, root)) { |
|
return; |
|
} |
|
|
|
const path = findFirstImport(j, root); |
|
if (path) { |
|
const importStatement = j.importDeclaration( |
|
[j.importDefaultSpecifier(j.identifier('React'))], |
|
j.literal('react') |
|
); |
|
retainLeadingComment(j, root, newFirstLine); |
|
|
|
j(path).insertBefore(importStatement); |
|
} |
|
} else { |
|
if (hasReactRequire(j, root)) { |
|
return; |
|
} |
|
|
|
const path = findFirstRequire(j, root); |
|
if (path) { |
|
const requireStatement = j.template.statement([ |
|
"const React = require('react');\n" |
|
]); |
|
j(path.parent.parent).insertAfter(requireStatement); |
|
} |
|
} |
|
}; |
|
})(); |
|
|
|
|
|
const deleteReactDomImport = (() => { |
|
return (j, root) => { |
|
const reactDomFactoriesRequire = root.find(j.VariableDeclarator, { |
|
id: { |
|
type: "ObjectPattern" |
|
}, |
|
init: { |
|
callee: {name: 'require'}, |
|
arguments: [ |
|
{ |
|
value: 'react-dom-factories' |
|
} |
|
] |
|
} |
|
}); |
|
if (reactDomFactoriesRequire.length === 0) { |
|
return false; |
|
} |
|
const requireLine = reactDomFactoriesRequire.paths()[0]; |
|
const requiredDomElements = requireLine.node.id.properties.map( |
|
property => property.key.name |
|
); |
|
|
|
reactDomFactoriesRequire.remove(); |
|
return true; |
|
} |
|
})(); |
|
|
|
|
|
module.exports = function(file, api) { |
|
const j = api.jscodeshift; |
|
const root = j(file.source); |
|
|
|
// matches dom elements of form `div(props, children)` etc |
|
const isDomIdentifier = path => isFuncIdentifier(path) && |
|
importedDomElements.includes(path.node.name) |
|
|
|
// Matches factory components. e.g. MyTooltip(props, children); |
|
const isCustomFactoryIdentifier = path => isFuncIdentifier(path) && |
|
path.node.name[0] === path.node.name[0].toUpperCase(); |
|
|
|
const isFactoryIdentifier = path => isDomIdentifier(path) || |
|
isCustomFactoryIdentifier(path) |
|
|
|
function replaceNonDomFactory(path) { |
|
const args = path.parent.node.arguments; |
|
const factoryPath = path.parent.node; |
|
const factoryName = factoryPath.callee.name; |
|
changedFactoryNames.push(factoryName); |
|
|
|
if (isDomIdentifier(path)) { |
|
// Replace div(props, children) => React.createElement('div', props, children) |
|
args.unshift(j.literal(factoryName)); |
|
} else { |
|
// Replace MyTooltip(props, children) => React.createElement(MyTooltip, props, children) |
|
args.unshift(factoryName); |
|
} |
|
path.node.name = "React.createElement"; |
|
} |
|
|
|
function replaceFactoriesWithCreateElement(j, root) { |
|
const factoryMethods = root.find(j.Identifier).filter(isFactoryIdentifier) |
|
factoryMethods.forEach(replaceNonDomFactory); |
|
return factoryMethods.length > 0; |
|
} |
|
|
|
function changeFromFactoryImports(j, root) { |
|
[...new Set(changedFactoryNames)].map(factoryName => { |
|
const requires = root.find(j.VariableDeclarator, { |
|
id: { |
|
name: factoryName |
|
}, |
|
init: { |
|
callee: {name: 'require'} |
|
} |
|
}).length; |
|
}); |
|
} |
|
|
|
function deleteReactDomImport(j, root) { |
|
const reactDomFactoriesRequire = root.find(j.VariableDeclarator, { |
|
id: { |
|
type: "ObjectPattern" |
|
}, |
|
init: { |
|
callee: {name: 'require'}, |
|
arguments: [ |
|
{ |
|
value: 'react-dom-factories' |
|
} |
|
] |
|
} |
|
}); |
|
if (reactDomFactoriesRequire.length === 0) { |
|
return false; |
|
} |
|
const requireLine = reactDomFactoriesRequire.paths()[0]; |
|
importedDomElements = requireLine.node.id.properties.map( |
|
property => property.key.name |
|
); |
|
|
|
reactDomFactoriesRequire.remove(); |
|
return true; |
|
} |
|
|
|
const changedFactoryNames = [] |
|
let importedDomElements = []; |
|
deleteReactDomImport(j, root); |
|
const hasModifications = replaceFactoriesWithCreateElement(j, root); |
|
|
|
if (hasModifications) { |
|
addReactImport(j, root); |
|
changeFromFactoryImports(j, root); |
|
} |
|
return hasModifications ? root.toSource({ quote: "single" }) : null; |
|
}; |