Skip to content

Commit

Permalink
feat: many more fixes, close to being usable on our projects
Browse files Browse the repository at this point in the history
  • Loading branch information
jedwards1211 committed Jan 7, 2021
1 parent b92760a commit 90c16de
Show file tree
Hide file tree
Showing 12 changed files with 403 additions and 31 deletions.
2 changes: 1 addition & 1 deletion .babelrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,5 @@ module.exports = function(api) {
plugins.push('babel-plugin-istanbul')
}

return { plugins, presets }
return { plugins, presets, sourceType: 'module' }
}
12 changes: 9 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,10 @@
"@types/fs-extra": "^9.0.6",
"@types/mocha": "^5.2.7",
"@types/node": "^12.12.6",
"@types/prettier": "^2.1.6",
"@types/resolve": "^1.17.1",
"@types/tmp": "^0.2.0",
"@types/yargs": "^15.0.12",
"babel-eslint": "^10.0.1",
"babel-plugin-istanbul": "^5.1.0",
"chai": "^4.2.0",
Expand All @@ -121,13 +124,11 @@
"eslint": "^5.9.0",
"eslint-config-prettier": "^3.3.0",
"flow-bin": "^0.141.0",
"fs-extra": "^9.0.1",
"husky": "^4.3.0",
"istanbul": "^0.4.5",
"lint-staged": "^10.4.0",
"mocha": "^6.2.1",
"nyc": "^13.1.0",
"prettier": "^1.15.2",
"prettier-eslint": "^8.8.2",
"rimraf": "^2.6.0",
"semantic-release": "^17.1.2",
Expand All @@ -139,6 +140,11 @@
"@babel/runtime": "^7.1.5",
"@babel/template": "^7.12.7",
"@babel/types": "^7.12.12",
"promisify-child-process": "^4.1.1"
"fs-extra": "^9.0.1",
"prettier": "^1.15.2",
"print-diff": "^1.0.0",
"recast": "^0.20.4",
"resolve": "^1.19.0",
"yargs": "^16.2.0"
}
}
9 changes: 8 additions & 1 deletion src/NodeConversionError.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import { NodePath } from '@babel/traverse'

function formatPosition(path: NodePath<any>): string {
const { loc } = path.node
const line = loc?.start?.line || '?'
const col = loc?.start?.column || '?'
return `${line}:${col}`
}

export default class NodeConversionError extends Error {
public readonly path: NodePath<any>
public readonly file: string

constructor(message: string, file: string, path: NodePath<any>) {
super(message)
super(`${message} (${file}, ${formatPosition(path)})`)
this.file = file
this.path = path
this.name = 'NodeConversionError'
Expand Down
77 changes: 77 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import * as fs from 'fs-extra'
import resolve from 'resolve'
import { parse } from '@babel/parser'
import { promisify } from 'util'
import { ConversionContext } from './convert/index'
import printDiff from 'print-diff'
import * as recast from 'recast'
import * as t from '@babel/types'
import * as Path from 'path'
import yargs from 'yargs'
import prettier from 'prettier'

const { _: files } = yargs.argv

async function parseFile(file: string): Promise<t.File> {
return recast.parse(await fs.readFile(file, 'utf8'), {
parser: {
parse: (code: string): any =>
parse(code, {
plugins: [
/\.tsx?$/.test(file) ? 'typescript' : ['flow', { all: true }],
'jsx',
'classProperties',
'exportDefaultFrom',
'asyncGenerators',
'objectRestSpread',
'optionalChaining',
'exportDefaultFrom',
'exportNamespaceFrom',
'dynamicImport',
'nullishCoalescingOperator',
'bigint' as any,
],
tokens: true,
sourceType: 'unambiguous',
}),
},
})
}

async function go() {
const context = new ConversionContext({
parseFile,
resolve: promisify(resolve) as any,
})
try {
for (const file of files) {
if (typeof file !== 'string') continue
// eslint-disable-next-line no-console
console.log(file)
await context.forFile(Path.resolve(file)).processFile()
}
} catch (error) {
// eslint-disable-next-line no-console
console.error(error.stack)
}
for (const [file, ast] of context.fileASTs.entries()) {
const prettierOptions = {
parser: /\.tsx?$/.test(file) ? 'typescript' : 'babel',
}
const printed = prettier.format(recast.print(ast).code, prettierOptions)
const orig = prettier.format(
await fs.readFile(file, 'utf8'),
prettierOptions
)
if (orig === printed) {
// eslint-disable-next-line no-console
console.log(file)
continue
}
// eslint-disable-next-line no-console
console.log(`\n\n${file}\n======================================\n\n`)
printDiff(orig, printed)
}
}

go()
2 changes: 1 addition & 1 deletion src/convert/ObjectTypeAnnotation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export default async function convertObjectTypeAnnotation(
const obj = path.node
const properties = path.get('properties')
const indexers = path.get('indexers') as NodePath<t.ObjectTypeIndexer>[]
const { exact } = obj
const exact = !obj.exact && !obj.inexact ? context.defaultExact : obj.exact
if (properties.length === 0 && indexers?.length === 1) {
const [indexer] = indexers
return templates.record({
Expand Down
29 changes: 29 additions & 0 deletions src/convert/convertUtilityFlowType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import * as t from '@babel/types'
import { NodePath } from '@babel/traverse'
import template from '@babel/template'
import { FileConversionContext } from './index'
import getTypeParams from './getTypeParams'

const templates = {
array: template.expression`T.array(TYPE)`,
}

export default async function convertUtilityFlowType(
context: FileConversionContext,
path: NodePath<t.GenericTypeAnnotation>
): Promise<t.Expression | void> {
const id = path.get('id')
if (!id.isIdentifier()) return
switch (id.node.name) {
case '$ReadOnlyArray':
case 'Array': {
const [elementType] = getTypeParams(context, path, 1)
return templates.array({
T: await context.importT(),
TYPE: await context.convert(elementType),
})
}
case '$ReadOnly':
return await context.convert(getTypeParams(context, path, 1)[0])
}
}
37 changes: 37 additions & 0 deletions src/convert/getTypeParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import * as t from '@babel/types'
import { NodePath } from '@babel/traverse'
import { FileConversionContext } from './index'
import NodeConversionError from '../NodeConversionError'

export default function getTypeParams(
context: FileConversionContext,
path: NodePath<t.GenericTypeAnnotation>,
required: boolean | number = true
): NodePath<t.FlowType>[] {
const typeParameters = path.get('typeParameters')
if (!typeParameters.isTypeParameterInstantiation()) {
if (!required) return []
throw new NodeConversionError(
`Missing required type parameter(s)`,
context.file,
path
)
}
const params = (typeParameters as NodePath<t.TypeParameterInstantiation>).get(
'params'
)
if (
typeof required === 'number'
? params.length !== required
: required
? params.length
: false
) {
throw new NodeConversionError(
`Missing required type parameter(s)`,
context.file,
path
)
}
return params
}
Loading

0 comments on commit 90c16de

Please sign in to comment.