Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Quick and dirty flow-to-typscript migration codemod
#!/bin/sh
#
# This is a super quick and dirty codemod for migration a codebase from Flow to TypeScript.
#
# Step 1:
# npm i -g jscodeshift
# Step 2: rename all .js files to .ts(x)
# find src -name "*.js" -exec sh -c 'mv "$0" "${0%.js}.ts"' {} \;
# find storybook -name "*.js" -exec sh -c 'mv "$0" "${0%.js}.ts"' {} \;
# find src/**/components -name "*.ts" -exec sh -c 'mv "$0" "${0%.ts}.tsx"' {} \;
# find src/**/screens -name "*.ts" -exec sh -c 'mv "$0" "${0%.ts}.tsx"' {} \;
# Step 3:
# sh flow-to-typescript-codemod.sh src/**/*.ts*
# Step 4:
# 1) fix linting issues
# 2) fix your tests
# 3) check the compiler output (./node_modules/.bin/tsc --noEmit src/App.tsx)
set -e
echo '\nRunning jscodeshift'
echo '==================='
jscodeshift -t flow-to-typescript-codemod.ts --parser=flow --ignore-pattern=node_modules --extensions=ts,tsx $@
echo '\nRunning sed scripts'
echo '==================='
for file in "$@"
do
# basic stuff
sed -i "" "s| +| readonly |g" $file
sed -i "" "s|mixed|void|g" "$file"
sed -i "" "s|\$Shape|Partial|g" "$file"
sed -i "" "s|: \*|: any|g" "$file"
sed -i "" "s/\$Keys<typeof \([A-Z_a-z]*\)>/keyof typeof \1/g" "$file"
# remove exact
sed -i "" "s|{[|]|{|g" "$file"
sed -i "" "s|[|]}|}|g" "$file"
# https://github.com/Microsoft/TypeScript/wiki/Coding-guidelines#null-and-undefined
sed -i "" "s|: ?|: undefined \| |g" "$file"
sed -i "" "s|= ?|= undefined \| |g" "$file"
sed -i "" "s|<?|<undefined \| |g" "$file"
sed -i "" "s|import type|import|g" "$file"
sed -i "" "s|React.Node|(JSX.Element \| JSX.Element[])[]|g" "$file"
sed -i "" "s|ViewPropTypes.style|ViewProps|g" "$file"
# unstable...
# sed -i "" "s/ \[\([A-Za-z]*\)\]: / \[key in \1\]: /g" "$file"
# fixup for codemod missing parentheses in arrow functions
sed -i "" "s/ arg: \([A-Z_a-z]*\)/ (arg: \1)/g" "$file"
# * --> any
done
echo '\nRunning prettier'
echo '==================='
./node_modules/.bin/prettier --write $@
// See flow-to-typescript-codemod.sh for documentation
type FileInfo = { path: string; source: string }
export const parser = 'flow'
function logger(fileInfo: FileInfo, msg, node) {
const lineInfo = node && node.value.loc ? ` line ${node.value.loc.start.line}` : ''
console.warn(`warning: (${fileInfo.path}${lineInfo}) ${msg}`)
}
export default function flowToTypeScript(fileInfo: FileInfo, api: any, options: any) {
const j = api.jscodeshift
const ast = j(fileInfo.source)
const logWarning = (msg: string, node: any) => logger(fileInfo, msg, node)
const transforms = [
function updateFunctionTypeParams() {
ast.find(j.FunctionTypeParam, { name: null }).forEach((p: any) => {
p.value.name = j.identifier('arg')
})
},
function fixDuplicateImports() {
const fileToImportCount = {}
const fileToSpecifiers = {}
ast.find(j.ImportDeclaration).forEach((p: any) => {
const file = p.value.source.value
if (!(file in fileToSpecifiers)) {
fileToSpecifiers[file] = []
}
fileToSpecifiers[file] = [...fileToSpecifiers[file], ...p.value.specifiers]
if (!(file in fileToImportCount)) {
fileToImportCount[file] = 0
} else {
p.prune()
}
fileToImportCount[file] += 1
})
Object.keys(fileToImportCount).map(file => {
if (fileToImportCount[file] > 1) {
ast.find(j.ImportDeclaration, { source: { value: file } }).forEach((p: any) => {
p.value.specifiers = fileToSpecifiers[file]
})
}
})
},
function removeExactTypes() {
ast.find(j.ObjectTypeAnnotation, { exact: true }).forEach((p: any) => {
p.value.exact = false
p.value.inexact = true
})
},
]
transforms.forEach(t => t())
return ast.toSource({
arrowParensAlways: true,
flowObjectCommas: true,
quote: 'single',
})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment