Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@
"build": "tsc",
"build:watch": "tsc -w",
"clean": "rm -rf ./dist",
"lint": "tslint src/**/*.ts",
"lint": "tslint src/**/*.ts && npm run prettier:check",
"prettier": "prettier 'src/**/*.[tj]s'",
"prettier:fix": "yarn prettier --write",
"prettier:check": "yarn prettier -l",
"prepublishOnly": "npm run clean && npm run lint && npm run build -- -d",
"pretest": "npm run build",
"tdd": "concurrently -k 'npm run build:watch' 'npm run test:watch'",
Expand All @@ -38,6 +41,7 @@
"ava": "^0.24.0",
"concurrently": "^3.5.1",
"flow-bin": "^0.59.0",
"prettier": "^1.15.3",
"tslint": "^5.8.0",
"typescript": "^2.6.2"
},
Expand Down
6 changes: 6 additions & 0 deletions prettier.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
printWidth: 80,
semi: false,
singleQuote: true,
trailingComma: 'none',
}
20 changes: 10 additions & 10 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,17 @@ import { resolve } from 'path'
import stdin = require('stdin')
import { compile } from './index'

main(minimist(process.argv.slice(2), {
alias: {
help: ['h'],
input: ['i'],
output: ['o']
}
}))
main(
minimist(process.argv.slice(2), {
alias: {
help: ['h'],
input: ['i'],
output: ['o']
}
})
)

async function main(argv: minimist.ParsedArgs) {

if (argv.help) {
printHelp()
process.exit(0)
Expand All @@ -32,7 +33,6 @@ async function main(argv: minimist.ParsedArgs) {
process.stderr.write(e.message)
process.exit(1)
}

}

function readInput(argIn?: string) {
Expand All @@ -58,7 +58,7 @@ function printHelp() {
const pkg = require('../../package.json')

process.stdout.write(
`
`
${pkg.name} ${pkg.version}
Usage: flow2ts [--input, -i] [IN_FILE] [--output, -o] [OUT_FILE]

Expand Down
176 changes: 123 additions & 53 deletions src/convert.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,48 @@
import { booleanLiteral, Flow, FlowType, FunctionTypeAnnotation, identifier, Identifier, isSpreadProperty, isTSTypeParameter, isTypeParameter, Node, numericLiteral, stringLiteral, tsAnyKeyword, tsArrayType, tsAsExpression, tsBooleanKeyword, tsFunctionType, TSFunctionType, tsIntersectionType, tsLiteralType, tsNullKeyword, tsNumberKeyword, tsPropertySignature, tsStringKeyword, tsThisType, tsTupleType, TSType, tsTypeAnnotation, tsTypeLiteral, tsTypeParameter, tsTypeParameterDeclaration, tsTypeQuery, tsTypeReference, tsUndefinedKeyword, tsUnionType, tsVoidKeyword, TypeAnnotation, TypeParameter } from '@babel/types'
import {
booleanLiteral,
Flow,
FlowType,
FunctionTypeAnnotation,
identifier,
Identifier,
isSpreadProperty,
isTSTypeParameter,
isTypeParameter,
Node,
numericLiteral,
stringLiteral,
tsAnyKeyword,
tsArrayType,
tsAsExpression,
tsBooleanKeyword,
tsFunctionType,
TSFunctionType,
tsIntersectionType,
tsLiteralType,
tsNullKeyword,
tsNumberKeyword,
tsPropertySignature,
tsStringKeyword,
tsThisType,
tsTupleType,
TSType,
tsTypeAnnotation,
tsTypeLiteral,
tsTypeParameter,
tsTypeParameterDeclaration,
tsTypeQuery,
tsTypeReference,
tsUndefinedKeyword,
tsUnionType,
tsVoidKeyword,
TypeAnnotation,
TypeParameter
} from '@babel/types'
import { generateFreeIdentifier } from './utils'

// TODO: Add overloads
export function toTs(node: Flow | TSType): TSType {
switch (node.type) {

// TS types
// TODO: Why does tsTs get called with TSTypes? It should only get called with Flow types.
case 'TSAnyKeyword':
Expand Down Expand Up @@ -75,7 +113,7 @@ export function toTs(node: Flow | TSType): TSType {

case 'TypeParameterDeclaration':
let params = node.params.map(_ => {
let d = (_ as any as TypeParameter).default
let d = ((_ as any) as TypeParameter).default
let p = tsTypeParameter(
hasBound(_) ? toTsType(_.bound.typeAnnotation) : undefined,
d ? toTs(d) : undefined
Expand Down Expand Up @@ -109,76 +147,108 @@ export function toTs(node: Flow | TSType): TSType {

export function toTsType(node: FlowType): TSType {
switch (node.type) {
case 'AnyTypeAnnotation': return tsAnyKeyword()
case 'ArrayTypeAnnotation': return tsArrayType(toTsType(node.elementType))
case 'BooleanTypeAnnotation': return tsBooleanKeyword()
case 'BooleanLiteralTypeAnnotation': return tsLiteralType(booleanLiteral(node.value!))
case 'FunctionTypeAnnotation': return functionToTsType(node)
case 'GenericTypeAnnotation': return tsTypeReference(node.id)
case 'IntersectionTypeAnnotation': return tsIntersectionType(node.types.map(toTsType))
case 'MixedTypeAnnotation': return tsAnyKeyword()
case 'NullLiteralTypeAnnotation': return tsNullKeyword()
case 'NullableTypeAnnotation': return tsUnionType([toTsType(node.typeAnnotation), tsNullKeyword(), tsUndefinedKeyword()])
case 'NumberLiteralTypeAnnotation': return tsLiteralType(numericLiteral(node.value!))
case 'NumberTypeAnnotation': return tsNumberKeyword()
case 'StringLiteralTypeAnnotation': return tsLiteralType(stringLiteral(node.value!))
case 'StringTypeAnnotation': return tsStringKeyword()
case 'ThisTypeAnnotation': return tsThisType()
case 'TupleTypeAnnotation': return tsTupleType(node.types.map(toTsType))
case 'TypeofTypeAnnotation': return tsTypeQuery(getId(node.argument))
case 'ObjectTypeAnnotation': return tsTypeLiteral([
...node.properties.map(_ => {
if (isSpreadProperty(_)) {
return _
}
let s = tsPropertySignature(_.key, tsTypeAnnotation(toTsType(_.value)))
s.optional = _.optional
return s
// TODO: anonymous indexers
// TODO: named indexers
// TODO: call properties
// TODO: variance
})
// ...node.indexers.map(_ => tSIndexSignature())
])
case 'UnionTypeAnnotation': return tsUnionType(node.types.map(toTsType))
case 'VoidTypeAnnotation': return tsVoidKeyword()
case 'AnyTypeAnnotation':
return tsAnyKeyword()
case 'ArrayTypeAnnotation':
return tsArrayType(toTsType(node.elementType))
case 'BooleanTypeAnnotation':
return tsBooleanKeyword()
case 'BooleanLiteralTypeAnnotation':
return tsLiteralType(booleanLiteral(node.value!))
case 'FunctionTypeAnnotation':
return functionToTsType(node)
case 'GenericTypeAnnotation':
return tsTypeReference(node.id)
case 'IntersectionTypeAnnotation':
return tsIntersectionType(node.types.map(toTsType))
case 'MixedTypeAnnotation':
return tsAnyKeyword()
case 'NullLiteralTypeAnnotation':
return tsNullKeyword()
case 'NullableTypeAnnotation':
return tsUnionType([
toTsType(node.typeAnnotation),
tsNullKeyword(),
tsUndefinedKeyword()
])
case 'NumberLiteralTypeAnnotation':
return tsLiteralType(numericLiteral(node.value!))
case 'NumberTypeAnnotation':
return tsNumberKeyword()
case 'StringLiteralTypeAnnotation':
return tsLiteralType(stringLiteral(node.value!))
case 'StringTypeAnnotation':
return tsStringKeyword()
case 'ThisTypeAnnotation':
return tsThisType()
case 'TupleTypeAnnotation':
return tsTupleType(node.types.map(toTsType))
case 'TypeofTypeAnnotation':
return tsTypeQuery(getId(node.argument))
case 'ObjectTypeAnnotation':
return tsTypeLiteral([
...node.properties.map(_ => {
if (isSpreadProperty(_)) {
return _
}
let s = tsPropertySignature(
_.key,
tsTypeAnnotation(toTsType(_.value))
)
s.optional = _.optional
return s
// TODO: anonymous indexers
// TODO: named indexers
// TODO: call properties
// TODO: variance
})
// ...node.indexers.map(_ => tSIndexSignature())
])
case 'UnionTypeAnnotation':
return tsUnionType(node.types.map(toTsType))
case 'VoidTypeAnnotation':
return tsVoidKeyword()
}
}

function getId(node: FlowType): Identifier {
switch (node.type) {
case 'GenericTypeAnnotation': return node.id
default: throw ReferenceError('typeof query must reference a node that has an id')
case 'GenericTypeAnnotation':
return node.id
default:
throw ReferenceError('typeof query must reference a node that has an id')
}
}

function functionToTsType(node: FunctionTypeAnnotation): TSFunctionType {

let typeParams = undefined

if (node.typeParameters) {
typeParams = tsTypeParameterDeclaration(node.typeParameters.params.map(_ => {

// TODO: How is this possible?
if (isTSTypeParameter(_)) {
return _
}
typeParams = tsTypeParameterDeclaration(
node.typeParameters.params.map(_ => {
// TODO: How is this possible?
if (isTSTypeParameter(_)) {
return _
}

let constraint = _.bound ? toTs(_.bound) : undefined
let default_ = _.default ? toTs(_.default) : undefined
let param = tsTypeParameter(constraint, default_)
param.name = _.name
return param
}))
let constraint = _.bound ? toTs(_.bound) : undefined
let default_ = _.default ? toTs(_.default) : undefined
let param = tsTypeParameter(constraint, default_)
param.name = _.name
return param
})
)
}

let f = tsFunctionType(typeParams)

// Params
if (node.params) {
// TODO: Rest params
let paramNames = node.params.map(_ => _.name).filter(_ => _ !== null).map(_ => (_ as Identifier).name)
let paramNames = node.params
.map(_ => _.name)
.filter(_ => _ !== null)
.map(_ => (_ as Identifier).name)
f.parameters = node.params.map(_ => {
let name = _.name && _.name.name

Expand Down
26 changes: 17 additions & 9 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,30 +20,38 @@ export function addRule(ruleName: string, rule: Rule) {
}

export async function compile(code: string, filename: string) {

let [warnings, ast] = await convert(
parse(code, { plugins: ['classProperties', 'flow', 'objectRestSpread'], sourceType: 'module' })
parse(code, {
plugins: ['classProperties', 'flow', 'objectRestSpread'],
sourceType: 'module'
})
)

warnings.forEach(([message, issueURL, line, column]) => {
console.log(`Warning: ${message} (at ${relative(__dirname, filename)}: line ${line}, column ${column}). See ${issueURL}`)
console.log(
`Warning: ${message} (at ${relative(
__dirname,
filename
)}: line ${line}, column ${column}). See ${issueURL}`
)
})

return addTrailingSpace(trimLeadingNewlines(generate(stripAtFlowAnnotation(ast)).code))
return addTrailingSpace(
trimLeadingNewlines(generate(stripAtFlowAnnotation(ast)).code)
)
}

/**
* @internal
*/
export async function convert<T extends Node>(ast: T): Promise<[Warning[], T]> {

// load rules directory
await Promise.all(sync(resolve(__dirname, './rules/*.js')).map(_ => import(_)))
await Promise.all(
sync(resolve(__dirname, './rules/*.js')).map(_ => import(_))
)

let warnings: Warning[] = []
rules.forEach(visitor =>
traverse(ast, visitor(warnings))
)
rules.forEach(visitor => traverse(ast, visitor(warnings)))

return [warnings, ast]
}
Expand Down
1 change: 0 additions & 1 deletion src/rules/$Exact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { addRule } from '../'

addRule('$Exact', warnings => ({
GenericTypeAnnotation(path) {

if (path.node.id.name !== '$Exact') {
return
}
Expand Down
8 changes: 6 additions & 2 deletions src/rules/$Keys.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { GenericTypeAnnotation, tsTypeOperator, tsTypeReference } from '@babel/types'
import {
GenericTypeAnnotation,
tsTypeOperator,
tsTypeReference
} from '@babel/types'
import { addRule } from '../'

addRule('$Keys', () => ({
GenericTypeAnnotation(path) {
if (path.node.id.name !== '$Keys') {
return
}
let { id } = (path.node.typeParameters.params[0] as GenericTypeAnnotation)
let { id } = path.node.typeParameters.params[0] as GenericTypeAnnotation
let op = tsTypeOperator(tsTypeReference(id))
path.replaceWith(op)
}
Expand Down
8 changes: 3 additions & 5 deletions src/rules/$ReadOnly.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,12 @@ import { addRule } from '../'

addRule('$ReadOnly', () => ({
GenericTypeAnnotation(path) {

if (path.node.id.name !== '$ReadOnly') {
return
}

path.replaceWith(genericTypeAnnotation(
identifier('Readonly'),
path.node.typeParameters
))
path.replaceWith(
genericTypeAnnotation(identifier('Readonly'), path.node.typeParameters)
)
}
}))
8 changes: 6 additions & 2 deletions src/rules/Bounds.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { isTypeParameter, Node, TypeAnnotation, TypeParameter } from '@babel/types'
import {
isTypeParameter,
Node,
TypeAnnotation,
TypeParameter
} from '@babel/types'
import { addRule } from '../'
import { toTs } from '../convert'

addRule('Bounds', () => ({
TypeParameterDeclaration(path) {

if (path.node.params.every(_ => !hasBound(_))) {
return
}
Expand Down
Loading