├── playground ├── .gitignore ├── examples │ ├── imports.js │ ├── basic-types.js │ ├── object-types.js │ ├── generics.js │ ├── utility-types.js │ └── function-types.js ├── images │ ├── GitHub-Mark-Light-32px.png │ └── GitHub-Mark-Light-64px.png ├── src │ ├── overrides.d.ts │ ├── index.tsx │ ├── hash.ts │ ├── options-panel.tsx │ └── app.tsx ├── tsconfig.json ├── templates │ └── index.html ├── babel.configs.js ├── package.json └── webpack.config.js ├── test ├── fixtures │ ├── convert │ │ ├── basic-types │ │ │ ├── any01 │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ ├── null01 │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── void01 │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── array01 │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── empty01 │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ ├── mixed01 │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ ├── number01 │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── string01 │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── array02 │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── boolean01 │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ ├── exist01 │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ └── tuple01 │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ ├── comments │ │ │ ├── flow01 │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── flow03 │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── flow-issue │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── flow-strict │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── flow-strict-local │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── flow02 │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── flow-fix-me-01 │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ ├── flow-fix-me-02 │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ ├── flow-expect-error │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ └── flow-ignore │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ ├── formatting │ │ │ ├── spacing05 │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── spacing04 │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── nullish-coalescing │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── optional-chaining │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── spacing01 │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── spacing02 │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── import_comments │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── object_newlines │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── comment_line02 │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── comment_line01 │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── object_type_newlines │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── prettier_options │ │ │ │ ├── flow.js │ │ │ │ ├── ts.js │ │ │ │ └── options.json │ │ │ ├── trailing_comment_line │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ ├── spacing03 │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── comment_block01 │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── comment_block02 │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── class_properties01 │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── class_properties02 │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ └── switch_cast_newlines │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ ├── typeof │ │ │ └── typeof01 │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ ├── literal-types │ │ │ ├── numeric-literal │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── boolean-literal │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ └── string-literal │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ ├── object-types │ │ │ ├── spread01 │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── spread04 │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── basic01 │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── exact01 │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── indexer01 │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── method01 │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ ├── spread02 │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── variance01 │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ ├── variance02 │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ ├── anonymous-indexer │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── method02 │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── optionality │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ ├── type-reference-indexer │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── writeonly-indexer │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── readonly-indexer │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── union-type-indexer │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── indexer02 │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── spread03 │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ └── callables01 │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ ├── type-aliases │ │ │ ├── alias01 │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ └── alias02 │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ ├── type-casting │ │ │ ├── cast01 │ │ │ │ ├── options.json │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ └── cast02 │ │ │ │ ├── options.json │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ ├── utility-types │ │ │ ├── $Diff │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ ├── $FlowFixMe │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── $Rest │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ ├── $Keys_inline │ │ │ │ ├── ts.js │ │ │ │ ├── flow.js │ │ │ │ └── options.json │ │ │ ├── $Call_import │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ ├── $Keys_import │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ ├── $PropertyType_inline │ │ │ │ ├── ts.js │ │ │ │ ├── flow.js │ │ │ │ └── options.json │ │ │ ├── $Shape_import │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ ├── $Shape_inline │ │ │ │ ├── flow.js │ │ │ │ ├── ts.js │ │ │ │ └── options.json │ │ │ ├── $Values_import │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ ├── $Values_inline │ │ │ │ ├── flow.js │ │ │ │ ├── ts.js │ │ │ │ └── options.json │ │ │ ├── Class_import │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ ├── $ReadOnly_inline │ │ │ │ ├── ts.js │ │ │ │ ├── flow.js │ │ │ │ └── options.json │ │ │ ├── $NonMaybeType_inline │ │ │ │ ├── ts.js │ │ │ │ ├── flow.js │ │ │ │ └── options.json │ │ │ ├── $ReadOnly_import │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ ├── $ElementType_import │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ ├── $NonMaybeType_import │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ ├── $PropertyType_import │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ ├── $ReadOnlyArray_inline │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── multiple_utility_types │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ └── $Exact │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ ├── function-types │ │ │ ├── generic03 │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── no-args01 │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ ├── generic01 │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── predicate02 │ │ │ │ ├── options.json │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── anon-args01 │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ ├── generic02 │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ ├── generic04 │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ ├── named-args01 │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── named-args02 │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── anon-args02 │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ ├── optional-args01 │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── parenthesized03 │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ ├── parenthesized01 │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ ├── parenthesized02 │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── predicate03 │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── rest01 │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ └── predicate01 │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ ├── imports │ │ │ ├── export_type │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ ├── export_star_from │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── export_type_star_from │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── import_node_module │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ ├── import_typeof │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ ├── default_import │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── export_type_from │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── typed_and_non_typed_imports │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── import_type_within_braces │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ ├── named_import_declaration_kind │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── named_import_specifier_kind │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ └── dynamic_import │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ ├── interfaces │ │ │ ├── class01 │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ ├── class02 │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── interface01 │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ └── interface02 │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ ├── intersection-union │ │ │ ├── union01 │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ └── intersection01 │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ ├── nullable-types │ │ │ ├── nullable01 │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ ├── nullable02 │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ ├── nullable03 │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ ├── nullable04 │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ └── nullable05 │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ ├── type-identifiers │ │ │ ├── basic_type_identifiers │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ └── qualified_type_identifiers │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ ├── historical-types │ │ │ ├── object-generic-type-annotation │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ └── function-generic-type-annotation │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ ├── classes │ │ │ ├── variance │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ └── readonly-class-properties │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ ├── declare │ │ │ ├── declare-var │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── declare-function │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── declare-class │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── function-overloading │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── method-overloading │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ └── declare-module │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ ├── react │ │ │ ├── element-config │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ ├── element-config-typeof │ │ │ │ ├── flow.js │ │ │ │ ├── ts.js │ │ │ │ └── options.json │ │ │ ├── basic-types-same │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── events │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ │ ├── basic-types-different │ │ │ │ ├── flow.js │ │ │ │ └── ts.js │ │ │ └── global-types │ │ │ │ ├── ts.js │ │ │ │ └── flow.js │ │ └── decorator │ │ │ ├── decorator-on-class │ │ │ ├── flow.js │ │ │ └── ts.js │ │ │ ├── multi-decorator │ │ │ ├── flow.js │ │ │ └── ts.js │ │ │ ├── decorator-factory │ │ │ ├── flow.js │ │ │ └── ts.js │ │ │ ├── mobx │ │ │ ├── flow.js │ │ │ └── ts.js │ │ │ └── decorator-on-class-with-argument │ │ │ ├── flow.js │ │ │ └── ts.js │ └── type-check │ │ ├── simple-types-fail.js │ │ ├── .flowconfig │ │ └── simple-types-pass.js ├── matchers.ts ├── convert.test.ts ├── type-check.test.ts └── cli.test.ts ├── .gitignore ├── .npmignore ├── .gitmodules ├── bin └── flow-to-ts.js ├── .github ├── dependabot.yml └── workflows │ └── node.yml ├── .prettierignore ├── src ├── detect-jsx.ts ├── util.ts ├── convert.ts ├── transforms │ ├── utility-types.ts │ ├── declare.ts │ ├── react-types.ts │ └── object-type.ts ├── cli.ts └── transform.ts ├── codecov.yml ├── jest.config.js ├── jest.transformer.js ├── babel.configs.js ├── webpack.config.js ├── package.json ├── README.md └── tsconfig.json /playground/.gitignore: -------------------------------------------------------------------------------- 1 | .cache 2 | dist 3 | node_modules 4 | -------------------------------------------------------------------------------- /test/fixtures/convert/basic-types/any01/flow.js: -------------------------------------------------------------------------------- 1 | let a: any; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/basic-types/any01/ts.js: -------------------------------------------------------------------------------- 1 | let a: any; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/basic-types/null01/ts.js: -------------------------------------------------------------------------------- 1 | let a: null; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/basic-types/void01/ts.js: -------------------------------------------------------------------------------- 1 | let a: void; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/comments/flow01/ts.js: -------------------------------------------------------------------------------- 1 | let a: string; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/comments/flow03/ts.js: -------------------------------------------------------------------------------- 1 | let a: string; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/formatting/spacing05/ts.js: -------------------------------------------------------------------------------- 1 | function a() {} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | node_modules 3 | coverage 4 | docs 5 | dist 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .circleci 2 | .vscode 3 | coverage 4 | playground 5 | test -------------------------------------------------------------------------------- /test/fixtures/convert/basic-types/array01/ts.js: -------------------------------------------------------------------------------- 1 | let a: string[]; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/basic-types/empty01/flow.js: -------------------------------------------------------------------------------- 1 | let a: empty; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/basic-types/empty01/ts.js: -------------------------------------------------------------------------------- 1 | let a: never; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/basic-types/mixed01/flow.js: -------------------------------------------------------------------------------- 1 | let a: mixed; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/basic-types/mixed01/ts.js: -------------------------------------------------------------------------------- 1 | let a: unknown; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/basic-types/null01/flow.js: -------------------------------------------------------------------------------- 1 | let a: null; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/basic-types/number01/ts.js: -------------------------------------------------------------------------------- 1 | let a: number; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/basic-types/string01/ts.js: -------------------------------------------------------------------------------- 1 | let a: string; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/basic-types/void01/flow.js: -------------------------------------------------------------------------------- 1 | let a: void; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/comments/flow-issue/ts.js: -------------------------------------------------------------------------------- 1 | let a: string; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/comments/flow-strict/ts.js: -------------------------------------------------------------------------------- 1 | let a: string; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/formatting/spacing04/ts.js: -------------------------------------------------------------------------------- 1 | const a: number = 5; -------------------------------------------------------------------------------- /test/fixtures/convert/formatting/spacing05/flow.js: -------------------------------------------------------------------------------- 1 | function a() {} -------------------------------------------------------------------------------- /test/fixtures/convert/typeof/typeof01/flow.js: -------------------------------------------------------------------------------- 1 | type a = typeof A; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/typeof/typeof01/ts.js: -------------------------------------------------------------------------------- 1 | type a = typeof A; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/basic-types/array01/flow.js: -------------------------------------------------------------------------------- 1 | let a: string[]; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/basic-types/array02/ts.js: -------------------------------------------------------------------------------- 1 | let a: Array; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/basic-types/boolean01/flow.js: -------------------------------------------------------------------------------- 1 | let a: boolean; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/basic-types/boolean01/ts.js: -------------------------------------------------------------------------------- 1 | let a: boolean; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/basic-types/exist01/flow.js: -------------------------------------------------------------------------------- 1 | let a: Array<*>; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/basic-types/exist01/ts.js: -------------------------------------------------------------------------------- 1 | let a: Array; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/basic-types/number01/flow.js: -------------------------------------------------------------------------------- 1 | let a: number; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/basic-types/string01/flow.js: -------------------------------------------------------------------------------- 1 | let a: string; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/comments/flow-strict-local/ts.js: -------------------------------------------------------------------------------- 1 | let a: string; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/literal-types/numeric-literal/ts.js: -------------------------------------------------------------------------------- 1 | type a = 5; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/object-types/spread01/ts.js: -------------------------------------------------------------------------------- 1 | let obj: T & U; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/type-aliases/alias01/flow.js: -------------------------------------------------------------------------------- 1 | type T = string; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/type-aliases/alias01/ts.js: -------------------------------------------------------------------------------- 1 | type T = string; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/type-casting/cast01/options.json: -------------------------------------------------------------------------------- 1 | {"prettier": true} -------------------------------------------------------------------------------- /test/fixtures/convert/type-casting/cast02/options.json: -------------------------------------------------------------------------------- 1 | {"prettier": true} -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/$Diff/flow.js: -------------------------------------------------------------------------------- 1 | type B = $Diff; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/$FlowFixMe/ts.js: -------------------------------------------------------------------------------- 1 | type A = any; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/$Rest/flow.js: -------------------------------------------------------------------------------- 1 | type B = $Rest; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/basic-types/array02/flow.js: -------------------------------------------------------------------------------- 1 | let a: Array; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/basic-types/tuple01/flow.js: -------------------------------------------------------------------------------- 1 | let a: [string, number]; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/basic-types/tuple01/ts.js: -------------------------------------------------------------------------------- 1 | let a: [string, number]; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/comments/flow01/flow.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | let a: string; 3 | -------------------------------------------------------------------------------- /test/fixtures/convert/function-types/generic03/ts.js: -------------------------------------------------------------------------------- 1 | type Item = T; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/function-types/no-args01/flow.js: -------------------------------------------------------------------------------- 1 | let a: () => void; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/function-types/no-args01/ts.js: -------------------------------------------------------------------------------- 1 | let a: () => void; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/imports/export_type/flow.js: -------------------------------------------------------------------------------- 1 | export type A = string; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/imports/export_type/ts.js: -------------------------------------------------------------------------------- 1 | export type A = string; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/interfaces/class01/flow.js: -------------------------------------------------------------------------------- 1 | class A implements B {} 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/interfaces/class01/ts.js: -------------------------------------------------------------------------------- 1 | class A implements B {} 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/interfaces/class02/ts.js: -------------------------------------------------------------------------------- 1 | class A implements B, C {} 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/intersection-union/union01/flow.js: -------------------------------------------------------------------------------- 1 | let obj: T | U; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/intersection-union/union01/ts.js: -------------------------------------------------------------------------------- 1 | let obj: T | U; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/literal-types/boolean-literal/flow.js: -------------------------------------------------------------------------------- 1 | type a = true; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/literal-types/boolean-literal/ts.js: -------------------------------------------------------------------------------- 1 | type a = true; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/literal-types/numeric-literal/flow.js: -------------------------------------------------------------------------------- 1 | type a = 5; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/literal-types/string-literal/flow.js: -------------------------------------------------------------------------------- 1 | type a = "foo"; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/literal-types/string-literal/ts.js: -------------------------------------------------------------------------------- 1 | type a = "foo"; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/nullable-types/nullable01/flow.js: -------------------------------------------------------------------------------- 1 | let a: ?string; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/$Keys_inline/ts.js: -------------------------------------------------------------------------------- 1 | type B = keyof A; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/comments/flow03/flow.js: -------------------------------------------------------------------------------- 1 | /** @flow **/ 2 | let a: string; 3 | -------------------------------------------------------------------------------- /test/fixtures/convert/formatting/spacing04/flow.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | const a: number = 5; -------------------------------------------------------------------------------- /test/fixtures/convert/function-types/generic01/ts.js: -------------------------------------------------------------------------------- 1 | let a: (b: T) => void; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/function-types/generic03/flow.js: -------------------------------------------------------------------------------- 1 | type Item = T; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/function-types/predicate02/options.json: -------------------------------------------------------------------------------- 1 | {"prettier": true} -------------------------------------------------------------------------------- /test/fixtures/convert/imports/export_star_from/ts.js: -------------------------------------------------------------------------------- 1 | export * from "./foo"; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/interfaces/class02/flow.js: -------------------------------------------------------------------------------- 1 | class A implements B, C {} 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/intersection-union/intersection01/flow.js: -------------------------------------------------------------------------------- 1 | let obj: T & U; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/intersection-union/intersection01/ts.js: -------------------------------------------------------------------------------- 1 | let obj: T & U; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/nullable-types/nullable02/flow.js: -------------------------------------------------------------------------------- 1 | let a: ?Array; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/nullable-types/nullable03/flow.js: -------------------------------------------------------------------------------- 1 | let a: Array; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/nullable-types/nullable04/flow.js: -------------------------------------------------------------------------------- 1 | let a: ?Array; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/object-types/spread04/ts.js: -------------------------------------------------------------------------------- 1 | let obj: { x: number } & T; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/type-casting/cast01/flow.js: -------------------------------------------------------------------------------- 1 | const a: number = (5: any); 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/type-casting/cast01/ts.js: -------------------------------------------------------------------------------- 1 | const a: number = 5 as any; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/type-casting/cast02/flow.js: -------------------------------------------------------------------------------- 1 | const a = ((5: any): number); 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/type-casting/cast02/ts.js: -------------------------------------------------------------------------------- 1 | const a = (5 as any) as number; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/type-identifiers/basic_type_identifiers/flow.js: -------------------------------------------------------------------------------- 1 | let a: T; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/type-identifiers/basic_type_identifiers/ts.js: -------------------------------------------------------------------------------- 1 | let a: T; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/$Call_import/flow.js: -------------------------------------------------------------------------------- 1 | type B = $Call; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/$Keys_import/flow.js: -------------------------------------------------------------------------------- 1 | type B = $Keys; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/$Keys_inline/flow.js: -------------------------------------------------------------------------------- 1 | type B = $Keys; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/$PropertyType_inline/ts.js: -------------------------------------------------------------------------------- 1 | type C = A[B]; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/$Shape_import/flow.js: -------------------------------------------------------------------------------- 1 | type B = $Shape; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/$Shape_inline/flow.js: -------------------------------------------------------------------------------- 1 | type B = $Shape; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/$Shape_inline/ts.js: -------------------------------------------------------------------------------- 1 | type B = Partial; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/$Values_import/flow.js: -------------------------------------------------------------------------------- 1 | type B = $Values; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/$Values_inline/flow.js: -------------------------------------------------------------------------------- 1 | type B = $Values; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/$Values_inline/ts.js: -------------------------------------------------------------------------------- 1 | type B = A[keyof A]; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/Class_import/flow.js: -------------------------------------------------------------------------------- 1 | type B = Class; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/comments/flow02/ts.js: -------------------------------------------------------------------------------- 1 | // leading comment 2 | let a: string; 3 | -------------------------------------------------------------------------------- /test/fixtures/convert/formatting/nullish-coalescing/ts.js: -------------------------------------------------------------------------------- 1 | let x = (foo ?? 0) > 100; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/function-types/anon-args01/flow.js: -------------------------------------------------------------------------------- 1 | let a: (string) => void; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/function-types/generic01/flow.js: -------------------------------------------------------------------------------- 1 | let a: (b: T) => void; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/function-types/generic02/flow.js: -------------------------------------------------------------------------------- 1 | type Item = T; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/function-types/generic04/flow.js: -------------------------------------------------------------------------------- 1 | type Item = T; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/function-types/named-args01/ts.js: -------------------------------------------------------------------------------- 1 | let a: (b: string) => void; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/function-types/named-args02/ts.js: -------------------------------------------------------------------------------- 1 | let a: (b?: string) => void; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/imports/export_star_from/flow.js: -------------------------------------------------------------------------------- 1 | export * from "./foo.js"; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/imports/export_type_star_from/ts.js: -------------------------------------------------------------------------------- 1 | export * from "./types"; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/imports/import_node_module/flow.js: -------------------------------------------------------------------------------- 1 | import type A from "dep"; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/imports/import_node_module/ts.js: -------------------------------------------------------------------------------- 1 | import type A from "dep"; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/imports/import_typeof/flow.js: -------------------------------------------------------------------------------- 1 | import typeof A from "./a.js"; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/object-types/basic01/ts.js: -------------------------------------------------------------------------------- 1 | let obj: { 2 | a: string; 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/convert/object-types/exact01/ts.js: -------------------------------------------------------------------------------- 1 | let obj: { 2 | a: string; 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/convert/object-types/indexer01/ts.js: -------------------------------------------------------------------------------- 1 | let obj: Record; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/type-aliases/alias02/flow.js: -------------------------------------------------------------------------------- 1 | type T = { 2 | a: A, 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/convert/type-aliases/alias02/ts.js: -------------------------------------------------------------------------------- 1 | type T = { 2 | a: A; 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/$ReadOnly_inline/ts.js: -------------------------------------------------------------------------------- 1 | let a: Readonly; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/comments/flow-fix-me-01/flow.js: -------------------------------------------------------------------------------- 1 | // $FlowFixMe 2 | let a: string; 3 | -------------------------------------------------------------------------------- /test/fixtures/convert/comments/flow-strict/flow.js: -------------------------------------------------------------------------------- 1 | // @flow strict 2 | let a: string; 3 | -------------------------------------------------------------------------------- /test/fixtures/convert/formatting/nullish-coalescing/flow.js: -------------------------------------------------------------------------------- 1 | let x = (foo ?? 0) > 100; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/function-types/anon-args01/ts.js: -------------------------------------------------------------------------------- 1 | let a: (arg0: string) => void; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/function-types/anon-args02/flow.js: -------------------------------------------------------------------------------- 1 | let a: (string, number) => void; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/function-types/generic02/ts.js: -------------------------------------------------------------------------------- 1 | type Item = T; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/function-types/generic04/ts.js: -------------------------------------------------------------------------------- 1 | type Item = T; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/function-types/named-args01/flow.js: -------------------------------------------------------------------------------- 1 | let a: (b: string) => void; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/function-types/named-args02/flow.js: -------------------------------------------------------------------------------- 1 | let a: (b?: string) => void; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/function-types/optional-args01/ts.js: -------------------------------------------------------------------------------- 1 | let a = (b: string = "") => b; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/function-types/parenthesized03/flow.js: -------------------------------------------------------------------------------- 1 | type array = (() => void)[]; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/function-types/parenthesized03/ts.js: -------------------------------------------------------------------------------- 1 | type array = (() => void)[]; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/imports/import_typeof/ts.js: -------------------------------------------------------------------------------- 1 | type A = typeof import("./a").default; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/interfaces/interface01/flow.js: -------------------------------------------------------------------------------- 1 | interface A { 2 | a: string; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/convert/interfaces/interface01/ts.js: -------------------------------------------------------------------------------- 1 | interface A { 2 | a: string; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/convert/nullable-types/nullable01/ts.js: -------------------------------------------------------------------------------- 1 | let a: string | null | undefined; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/object-types/basic01/flow.js: -------------------------------------------------------------------------------- 1 | let obj: { 2 | a: string, 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/convert/object-types/exact01/flow.js: -------------------------------------------------------------------------------- 1 | let obj: {| 2 | a: string, 3 | |}; 4 | -------------------------------------------------------------------------------- /test/fixtures/convert/object-types/method01/flow.js: -------------------------------------------------------------------------------- 1 | let obj: { 2 | a(): string, 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/convert/object-types/method01/ts.js: -------------------------------------------------------------------------------- 1 | let obj: { 2 | a(): string; 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/convert/object-types/spread02/ts.js: -------------------------------------------------------------------------------- 1 | let obj: T & { 2 | x: number; 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/convert/object-types/variance01/flow.js: -------------------------------------------------------------------------------- 1 | let obj: { 2 | +a: string, 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/convert/object-types/variance02/flow.js: -------------------------------------------------------------------------------- 1 | let obj: { 2 | -a: string, 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/convert/object-types/variance02/ts.js: -------------------------------------------------------------------------------- 1 | let obj: { 2 | a: string; 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/$FlowFixMe/flow.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | type A = $FlowFixMe; 3 | -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/$Keys_inline/options.json: -------------------------------------------------------------------------------- 1 | {"inlineUtilityTypes": true} -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/$NonMaybeType_inline/ts.js: -------------------------------------------------------------------------------- 1 | type B = NonNullable; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/$ReadOnly_import/flow.js: -------------------------------------------------------------------------------- 1 | let a: $ReadOnly; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/$ReadOnly_inline/flow.js: -------------------------------------------------------------------------------- 1 | let a: $ReadOnly; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/$ReadOnly_inline/options.json: -------------------------------------------------------------------------------- 1 | {"inlineUtilityTypes": true} -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/$Shape_inline/options.json: -------------------------------------------------------------------------------- 1 | {"inlineUtilityTypes": true} -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/$Values_inline/options.json: -------------------------------------------------------------------------------- 1 | {"inlineUtilityTypes": true} -------------------------------------------------------------------------------- /test/fixtures/convert/comments/flow-fix-me-01/ts.js: -------------------------------------------------------------------------------- 1 | // @ts-expect-error 2 | let a: string; 3 | -------------------------------------------------------------------------------- /test/fixtures/convert/comments/flow-fix-me-02/flow.js: -------------------------------------------------------------------------------- 1 | // $FlowFixMe: TODO 2 | let a: string; 3 | -------------------------------------------------------------------------------- /test/fixtures/convert/function-types/optional-args01/flow.js: -------------------------------------------------------------------------------- 1 | let a = (b?: string = "") => b; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/function-types/parenthesized01/flow.js: -------------------------------------------------------------------------------- 1 | type union = (() => void) | void; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/function-types/parenthesized01/ts.js: -------------------------------------------------------------------------------- 1 | type union = (() => void) | void; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/imports/export_type_star_from/flow.js: -------------------------------------------------------------------------------- 1 | export type * from "./types.js"; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/nullable-types/nullable02/ts.js: -------------------------------------------------------------------------------- 1 | let a: Array | null | undefined; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/nullable-types/nullable03/ts.js: -------------------------------------------------------------------------------- 1 | let a: Array; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/object-types/anonymous-indexer/ts.js: -------------------------------------------------------------------------------- 1 | let obj: Record; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/object-types/method02/ts.js: -------------------------------------------------------------------------------- 1 | let obj: { 2 | a(b: T): string; 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/convert/object-types/optionality/flow.js: -------------------------------------------------------------------------------- 1 | type Foo = { 2 | x?: number, 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/convert/object-types/optionality/ts.js: -------------------------------------------------------------------------------- 1 | type Foo = { 2 | x?: number; 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/convert/object-types/spread01/flow.js: -------------------------------------------------------------------------------- 1 | let obj: { 2 | ...T, 3 | ...U, 4 | }; 5 | -------------------------------------------------------------------------------- /test/fixtures/convert/object-types/type-reference-indexer/ts.js: -------------------------------------------------------------------------------- 1 | let obj: Record; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/object-types/writeonly-indexer/ts.js: -------------------------------------------------------------------------------- 1 | let obj: Record; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/type-identifiers/qualified_type_identifiers/flow.js: -------------------------------------------------------------------------------- 1 | let foobar: Foo.Bar; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/type-identifiers/qualified_type_identifiers/ts.js: -------------------------------------------------------------------------------- 1 | let foobar: Foo.Bar; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/$ElementType_import/flow.js: -------------------------------------------------------------------------------- 1 | type C = $ElementType; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/$NonMaybeType_import/flow.js: -------------------------------------------------------------------------------- 1 | type B = $NonMaybeType; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/$NonMaybeType_inline/flow.js: -------------------------------------------------------------------------------- 1 | type B = $NonMaybeType; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/$NonMaybeType_inline/options.json: -------------------------------------------------------------------------------- 1 | {"inlineUtilityTypes": true} -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/$PropertyType_import/flow.js: -------------------------------------------------------------------------------- 1 | type C = $PropertyType; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/$PropertyType_inline/flow.js: -------------------------------------------------------------------------------- 1 | type C = $PropertyType; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/$PropertyType_inline/options.json: -------------------------------------------------------------------------------- 1 | {"inlineUtilityTypes": true} -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/$ReadOnlyArray_inline/ts.js: -------------------------------------------------------------------------------- 1 | let a: ReadonlyArray; 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "babel"] 2 | path = babel 3 | url = https://github.com/kevinbarabash/babel 4 | -------------------------------------------------------------------------------- /test/fixtures/convert/comments/flow-fix-me-02/ts.js: -------------------------------------------------------------------------------- 1 | // @ts-expect-error: TODO 2 | let a: string; 3 | -------------------------------------------------------------------------------- /test/fixtures/convert/comments/flow-strict-local/flow.js: -------------------------------------------------------------------------------- 1 | // @flow strict-local 2 | let a: string; 3 | -------------------------------------------------------------------------------- /test/fixtures/convert/comments/flow02/flow.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | // leading comment 3 | let a: string; 4 | -------------------------------------------------------------------------------- /test/fixtures/convert/function-types/anon-args02/ts.js: -------------------------------------------------------------------------------- 1 | let a: (arg0: string, arg1: number) => void; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/function-types/parenthesized02/ts.js: -------------------------------------------------------------------------------- 1 | type intersection = (() => void) & void; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/function-types/predicate03/ts.js: -------------------------------------------------------------------------------- 1 | const isString = y => typeof y === "isString"; -------------------------------------------------------------------------------- /test/fixtures/convert/function-types/rest01/flow.js: -------------------------------------------------------------------------------- 1 | let a: (b: string, ...rest: number[]) => void; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/function-types/rest01/ts.js: -------------------------------------------------------------------------------- 1 | let a: (b: string, ...rest: number[]) => void; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/interfaces/interface02/flow.js: -------------------------------------------------------------------------------- 1 | interface A extends B { 2 | a: string; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/convert/interfaces/interface02/ts.js: -------------------------------------------------------------------------------- 1 | interface A extends B { 2 | a: string; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/convert/object-types/indexer01/flow.js: -------------------------------------------------------------------------------- 1 | let obj: { 2 | [key: number]: string, 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/convert/object-types/method02/flow.js: -------------------------------------------------------------------------------- 1 | let obj: { 2 | a(b: T): string, 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/convert/object-types/spread02/flow.js: -------------------------------------------------------------------------------- 1 | let obj: { 2 | ...T, 3 | x: number, 4 | }; 5 | -------------------------------------------------------------------------------- /test/fixtures/convert/object-types/spread04/flow.js: -------------------------------------------------------------------------------- 1 | let obj: { 2 | x: number, 3 | ...T, 4 | }; 5 | -------------------------------------------------------------------------------- /test/fixtures/convert/object-types/variance01/ts.js: -------------------------------------------------------------------------------- 1 | let obj: { 2 | readonly a: string; 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/$ReadOnlyArray_inline/flow.js: -------------------------------------------------------------------------------- 1 | let a: $ReadOnlyArray; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/formatting/optional-chaining/ts.js: -------------------------------------------------------------------------------- 1 | let x = foo?.bar.baz(); 2 | let y = foo ?? bar(); -------------------------------------------------------------------------------- /test/fixtures/convert/function-types/parenthesized02/flow.js: -------------------------------------------------------------------------------- 1 | type intersection = (() => void) & void; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/historical-types/object-generic-type-annotation/ts.js: -------------------------------------------------------------------------------- 1 | let a: Record; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/object-types/readonly-indexer/ts.js: -------------------------------------------------------------------------------- 1 | let obj: Readonly>; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/formatting/optional-chaining/flow.js: -------------------------------------------------------------------------------- 1 | let x = foo?.bar.baz(); 2 | let y = foo ?? bar(); 3 | -------------------------------------------------------------------------------- /test/fixtures/convert/function-types/predicate03/flow.js: -------------------------------------------------------------------------------- 1 | const isString = (y): %checks => typeof y === "isString"; -------------------------------------------------------------------------------- /test/fixtures/convert/historical-types/function-generic-type-annotation/flow.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | let a: Function; 3 | -------------------------------------------------------------------------------- /test/fixtures/convert/historical-types/object-generic-type-annotation/flow.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | let a: Object; 3 | -------------------------------------------------------------------------------- /test/fixtures/convert/object-types/readonly-indexer/flow.js: -------------------------------------------------------------------------------- 1 | let obj: { 2 | +[key: number]: string, 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/convert/object-types/type-reference-indexer/flow.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | let obj: { [key: A]: string }; 3 | -------------------------------------------------------------------------------- /test/fixtures/convert/object-types/union-type-indexer/ts.js: -------------------------------------------------------------------------------- 1 | let obj: { [key in string | number]?: string }; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/object-types/writeonly-indexer/flow.js: -------------------------------------------------------------------------------- 1 | let obj: { 2 | -[key: number]: string, 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/$Diff/ts.js: -------------------------------------------------------------------------------- 1 | import { $Diff } from "utility-types"; 2 | type B = $Diff; 3 | -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/$Rest/ts.js: -------------------------------------------------------------------------------- 1 | import { $Diff } from "utility-types"; 2 | type B = $Diff; 3 | -------------------------------------------------------------------------------- /test/fixtures/convert/comments/flow-issue/flow.js: -------------------------------------------------------------------------------- 1 | // $FlowIssue: this is a bug in flow itself 2 | let a: string; 3 | -------------------------------------------------------------------------------- /test/fixtures/convert/historical-types/function-generic-type-annotation/ts.js: -------------------------------------------------------------------------------- 1 | let a: (...args: Array) => any; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/nullable-types/nullable04/ts.js: -------------------------------------------------------------------------------- 1 | let a: Array | null | undefined; 2 | -------------------------------------------------------------------------------- /test/fixtures/convert/object-types/indexer02/ts.js: -------------------------------------------------------------------------------- 1 | type Key = "foo" | "bar"; 2 | let obj: Record; 3 | -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/multiple_utility_types/flow.js: -------------------------------------------------------------------------------- 1 | type B = $Keys; 2 | type C = $Values; 3 | -------------------------------------------------------------------------------- /bin/flow-to-ts.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const { cli } = require("../dist/cli.bundle.js"); 3 | 4 | cli(process.argv); 5 | -------------------------------------------------------------------------------- /test/fixtures/convert/classes/variance/ts.js: -------------------------------------------------------------------------------- 1 | export default class FooBar { 2 | foo: number; 3 | #bar: number; 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/convert/comments/flow-expect-error/flow.js: -------------------------------------------------------------------------------- 1 | // $FlowExpectError: string isn't a number 2 | let a: string; 3 | -------------------------------------------------------------------------------- /test/fixtures/convert/comments/flow-expect-error/ts.js: -------------------------------------------------------------------------------- 1 | // @ts-expect-error: string isn't a number 2 | let a: string; 3 | -------------------------------------------------------------------------------- /test/fixtures/convert/formatting/spacing01/ts.js: -------------------------------------------------------------------------------- 1 | let a: number = 5; 2 | let b: string = "foo"; 3 | let c: boolean = true; -------------------------------------------------------------------------------- /test/fixtures/convert/formatting/spacing02/ts.js: -------------------------------------------------------------------------------- 1 | let a: number = 5; 2 | let b: string = "foo"; 3 | let c: boolean = true; -------------------------------------------------------------------------------- /test/fixtures/convert/function-types/predicate01/ts.js: -------------------------------------------------------------------------------- 1 | function isString(y) { 2 | return typeof y === "string"; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/convert/imports/default_import/ts.js: -------------------------------------------------------------------------------- 1 | import type A from "./depsA"; 2 | import type B from "../depsB"; 3 | -------------------------------------------------------------------------------- /test/fixtures/convert/object-types/anonymous-indexer/flow.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | let obj: { 3 | [number]: string, 4 | }; 5 | -------------------------------------------------------------------------------- /test/fixtures/convert/object-types/indexer02/flow.js: -------------------------------------------------------------------------------- 1 | type Key = "foo" | "bar"; 2 | let obj: { [key: Key]: string }; 3 | -------------------------------------------------------------------------------- /test/fixtures/convert/object-types/spread03/flow.js: -------------------------------------------------------------------------------- 1 | let obj: { 2 | x: number, 3 | ...T, 4 | y: number, 5 | }; 6 | -------------------------------------------------------------------------------- /test/fixtures/convert/object-types/spread03/ts.js: -------------------------------------------------------------------------------- 1 | let obj: { 2 | x: number; 3 | } & T & { 4 | y: number; 5 | }; 6 | -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/$Exact/flow.js: -------------------------------------------------------------------------------- 1 | type A = $Exact<{ x: number, y: number }>; 2 | type B = $Exact; 3 | -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/$Exact/ts.js: -------------------------------------------------------------------------------- 1 | type A = { 2 | x: number; 3 | y: number; 4 | }; 5 | type B = C; 6 | -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/$Keys_import/ts.js: -------------------------------------------------------------------------------- 1 | import { $Keys } from "utility-types"; 2 | type B = $Keys; 3 | -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/$Shape_import/ts.js: -------------------------------------------------------------------------------- 1 | import { $Shape } from "utility-types"; 2 | type B = $Shape; 3 | -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/Class_import/ts.js: -------------------------------------------------------------------------------- 1 | import { Class } from "utility-types"; 2 | type B = Class; 3 | -------------------------------------------------------------------------------- /test/fixtures/convert/classes/variance/flow.js: -------------------------------------------------------------------------------- 1 | export default class FooBar { 2 | -foo: number; 3 | -#bar: number; 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/convert/declare/declare-var/ts.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The constant PI ~ 3.141592 3 | */ 4 | declare var PI: number; 5 | -------------------------------------------------------------------------------- /test/fixtures/convert/imports/default_import/flow.js: -------------------------------------------------------------------------------- 1 | import type A from "./depsA.js"; 2 | import type B from "../depsB.js"; 3 | -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/$Call_import/ts.js: -------------------------------------------------------------------------------- 1 | import { $Call } from "utility-types"; 2 | type B = $Call; 3 | -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/$Values_import/ts.js: -------------------------------------------------------------------------------- 1 | import { $Values } from "utility-types"; 2 | type B = $Values; 3 | -------------------------------------------------------------------------------- /playground/examples/imports.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import type { Foo } from "./foo.js"; 3 | import { type Bar, Baz } from "./extras.js"; 4 | -------------------------------------------------------------------------------- /test/fixtures/convert/formatting/import_comments/ts.js: -------------------------------------------------------------------------------- 1 | // foo 2 | import foo from "./foo"; 3 | // bar 4 | import bar from "./bar"; -------------------------------------------------------------------------------- /test/fixtures/convert/formatting/spacing01/flow.js: -------------------------------------------------------------------------------- 1 | let a: number = 5; 2 | 3 | let b: string = "foo"; 4 | 5 | let c: boolean = true; -------------------------------------------------------------------------------- /test/fixtures/convert/imports/export_type_from/ts.js: -------------------------------------------------------------------------------- 1 | export type { A, B } from "./depA"; 2 | export type { C, D } from "../depB"; 3 | -------------------------------------------------------------------------------- /test/fixtures/convert/imports/typed_and_non_typed_imports/ts.js: -------------------------------------------------------------------------------- 1 | import { A } from "./depA"; 2 | import type { B } from "./depA"; 3 | -------------------------------------------------------------------------------- /test/fixtures/convert/comments/flow-ignore/ts.js: -------------------------------------------------------------------------------- 1 | // @ts-ignore: we don't care about type checking in this test file 2 | let a: string; 3 | -------------------------------------------------------------------------------- /test/fixtures/convert/declare/declare-function/ts.js: -------------------------------------------------------------------------------- 1 | // Adds two numbers 2 | declare function foo(arg0: number, arg1: number): number; 3 | -------------------------------------------------------------------------------- /test/fixtures/convert/function-types/predicate01/flow.js: -------------------------------------------------------------------------------- 1 | function isString(y): %checks { 2 | return typeof y === "string"; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/convert/function-types/predicate02/ts.js: -------------------------------------------------------------------------------- 1 | const isString = function (y) { 2 | return typeof y === "isString"; 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/convert/object-types/union-type-indexer/flow.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | let obj: { 3 | [key: string | number]: string, 4 | }; 5 | -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/$ReadOnly_import/ts.js: -------------------------------------------------------------------------------- 1 | import { $ReadOnly } from "utility-types"; 2 | let a: $ReadOnly; 3 | -------------------------------------------------------------------------------- /test/fixtures/convert/comments/flow-ignore/flow.js: -------------------------------------------------------------------------------- 1 | // $FlowIgnore: we don't care about type checking in this test file 2 | let a: string; 3 | -------------------------------------------------------------------------------- /test/fixtures/convert/declare/declare-function/flow.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | // Adds two numbers 3 | declare function foo(number, number): number; 4 | -------------------------------------------------------------------------------- /test/fixtures/convert/declare/declare-var/flow.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | /** 3 | * The constant PI ~ 3.141592 4 | */ 5 | declare var PI: number; 6 | -------------------------------------------------------------------------------- /test/fixtures/convert/formatting/import_comments/flow.js: -------------------------------------------------------------------------------- 1 | // foo 2 | import foo from "./foo.js"; 3 | 4 | // bar 5 | import bar from "./bar.js"; -------------------------------------------------------------------------------- /test/fixtures/convert/imports/export_type_from/flow.js: -------------------------------------------------------------------------------- 1 | export type { A, B } from "./depA.js"; 2 | export type { C, D } from "../depB.js"; 3 | -------------------------------------------------------------------------------- /test/fixtures/convert/imports/typed_and_non_typed_imports/flow.js: -------------------------------------------------------------------------------- 1 | import { A } from "./depA.js"; 2 | import type { B } from "./depA.js"; 3 | -------------------------------------------------------------------------------- /test/fixtures/convert/nullable-types/nullable05/flow.js: -------------------------------------------------------------------------------- 1 | type Foo = { 2 | name: ?string, 3 | onChange: ?(arg0: string) => void, 4 | }; 5 | -------------------------------------------------------------------------------- /test/fixtures/convert/react/element-config/flow.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import * as React from "react"; 3 | type Props = React.ElementConfig; 4 | -------------------------------------------------------------------------------- /playground/images/GitHub-Mark-Light-32px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeSignal/flow-to-ts/master/playground/images/GitHub-Mark-Light-32px.png -------------------------------------------------------------------------------- /playground/images/GitHub-Mark-Light-64px.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeSignal/flow-to-ts/master/playground/images/GitHub-Mark-Light-64px.png -------------------------------------------------------------------------------- /playground/src/overrides.d.ts: -------------------------------------------------------------------------------- 1 | // allows us to use `import` statements with .png files 2 | declare module "*.png"; 3 | declare module "*.js"; 4 | -------------------------------------------------------------------------------- /test/fixtures/convert/classes/readonly-class-properties/flow.js: -------------------------------------------------------------------------------- 1 | export default class FooBar { 2 | +foo: number; 3 | +#bar: number; 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/convert/formatting/object_newlines/ts.js: -------------------------------------------------------------------------------- 1 | const obj = { 2 | // x 3 | x: 5, 4 | // y 5 | y: 10 // trailing comment 6 | 7 | }; -------------------------------------------------------------------------------- /test/fixtures/convert/formatting/spacing02/flow.js: -------------------------------------------------------------------------------- 1 | let a: number = 5; 2 | 3 | 4 | let b: string = "foo"; 5 | 6 | 7 | 8 | let c: boolean = true; -------------------------------------------------------------------------------- /test/fixtures/convert/function-types/predicate02/flow.js: -------------------------------------------------------------------------------- 1 | const isString = function (y): %checks { 2 | return typeof y === "isString"; 3 | }; 4 | -------------------------------------------------------------------------------- /test/fixtures/convert/imports/import_type_within_braces/flow.js: -------------------------------------------------------------------------------- 1 | // comment before 2 | import { type A, B } from "./a.js"; 3 | // comment after 4 | -------------------------------------------------------------------------------- /test/fixtures/convert/imports/named_import_declaration_kind/ts.js: -------------------------------------------------------------------------------- 1 | import type { A, B } from "./depA"; 2 | import type { C, D } from "../depB"; 3 | -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/$ElementType_import/ts.js: -------------------------------------------------------------------------------- 1 | import { $ElementType } from "utility-types"; 2 | type C = $ElementType; 3 | -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/$NonMaybeType_import/ts.js: -------------------------------------------------------------------------------- 1 | import { $NonMaybeType } from "utility-types"; 2 | type B = $NonMaybeType; 3 | -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/$PropertyType_import/ts.js: -------------------------------------------------------------------------------- 1 | import { $PropertyType } from "utility-types"; 2 | type C = $PropertyType; 3 | -------------------------------------------------------------------------------- /test/fixtures/type-check/simple-types-fail.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | const foo: string = 5; 3 | const bar: number = true; 4 | const baz: boolean = 'foo'; 5 | -------------------------------------------------------------------------------- /test/fixtures/convert/classes/readonly-class-properties/ts.js: -------------------------------------------------------------------------------- 1 | export default class FooBar { 2 | readonly foo: number; 3 | #bar: number; 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/convert/formatting/comment_line02/ts.js: -------------------------------------------------------------------------------- 1 | const c = () => { 2 | // foo 3 | const a = 5; 4 | // bar 5 | const b = 10; // baz 6 | }; -------------------------------------------------------------------------------- /test/fixtures/convert/object-types/callables01/flow.js: -------------------------------------------------------------------------------- 1 | type Callable = { 2 | (): void, 3 | (string): number, 4 | foo: boolean, 5 | }; 6 | -------------------------------------------------------------------------------- /test/fixtures/convert/object-types/callables01/ts.js: -------------------------------------------------------------------------------- 1 | type Callable = { 2 | (): void; 3 | (arg0: string): number; 4 | foo: boolean; 5 | }; 6 | -------------------------------------------------------------------------------- /test/fixtures/type-check/.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | 3 | [include] 4 | 5 | [libs] 6 | 7 | [lints] 8 | 9 | [options] 10 | 11 | [strict] 12 | -------------------------------------------------------------------------------- /test/fixtures/type-check/simple-types-pass.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | const foo: string = "hello"; 3 | const bar: number = 5; 4 | const baz: boolean = true; 5 | -------------------------------------------------------------------------------- /test/fixtures/convert/imports/named_import_declaration_kind/flow.js: -------------------------------------------------------------------------------- 1 | import type { A, B } from "./depA.js"; 2 | import type { C, D } from "../depB.js"; 3 | -------------------------------------------------------------------------------- /test/fixtures/convert/react/element-config-typeof/flow.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import * as React from "react"; 3 | type Props = React.ElementConfig; 4 | -------------------------------------------------------------------------------- /playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "types": ["webpack-env"], 4 | "jsx": "react", 5 | "lib": ["es2017", "dom"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/convert/declare/declare-class/ts.js: -------------------------------------------------------------------------------- 1 | declare class Path extends Node { 2 | // converts Path to a string 3 | toString(): string; 4 | } 5 | -------------------------------------------------------------------------------- /test/fixtures/convert/declare/function-overloading/ts.js: -------------------------------------------------------------------------------- 1 | declare function getWidget(n: number): Widget; 2 | declare function getWidget(s: string): Widget[]; 3 | -------------------------------------------------------------------------------- /test/fixtures/convert/formatting/comment_line01/ts.js: -------------------------------------------------------------------------------- 1 | // first leading comment 2 | // second leading comment 3 | const a = 5; 4 | // bar 5 | const b = 10; // baz -------------------------------------------------------------------------------- /test/fixtures/convert/formatting/object_type_newlines/ts.js: -------------------------------------------------------------------------------- 1 | type Point = { 2 | // x 3 | x: number; 4 | // y 5 | y: number; // trailing newline 6 | 7 | }; -------------------------------------------------------------------------------- /test/fixtures/convert/imports/import_type_within_braces/ts.js: -------------------------------------------------------------------------------- 1 | // comment before 2 | import type { A } from "./a"; 3 | import { B } from "./a"; // comment after 4 | -------------------------------------------------------------------------------- /test/fixtures/convert/imports/named_import_specifier_kind/flow.js: -------------------------------------------------------------------------------- 1 | import { type A, type B, C } from "./depA.js"; 2 | import { type D, type E, F } from "../depB.js"; -------------------------------------------------------------------------------- /test/fixtures/convert/react/element-config/ts.js: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | type Props = JSX.LibraryManagedAttributes>; 3 | -------------------------------------------------------------------------------- /test/fixtures/convert/imports/dynamic_import/ts.js: -------------------------------------------------------------------------------- 1 | import("./depsA.js").then(module => module.default()); 2 | import("../depsB.js").then(module => module.default()); -------------------------------------------------------------------------------- /test/fixtures/convert/utility-types/multiple_utility_types/ts.js: -------------------------------------------------------------------------------- 1 | import { $Keys, $Values } from "utility-types"; 2 | type B = $Keys; 3 | type C = $Values; 4 | -------------------------------------------------------------------------------- /test/fixtures/convert/formatting/comment_line01/flow.js: -------------------------------------------------------------------------------- 1 | // first leading comment 2 | // second leading comment 3 | const a = 5; 4 | 5 | // bar 6 | const b = 10; 7 | // baz -------------------------------------------------------------------------------- /test/fixtures/convert/formatting/prettier_options/flow.js: -------------------------------------------------------------------------------- 1 | function a(): string { 2 | return 'hello, world!' 3 | } 4 | const point = { 5 | x: 5, 6 | y: 10, 7 | }; -------------------------------------------------------------------------------- /test/fixtures/convert/formatting/trailing_comment_line/flow.js: -------------------------------------------------------------------------------- 1 | const b = 10; // y 2 | // baz 3 | 4 | // first leading comment 5 | // second leading comment 6 | const c = 5; -------------------------------------------------------------------------------- /test/fixtures/convert/formatting/trailing_comment_line/ts.js: -------------------------------------------------------------------------------- 1 | const b = 10; // y 2 | 3 | // baz 4 | // first leading comment 5 | // second leading comment 6 | const c = 5; -------------------------------------------------------------------------------- /test/fixtures/convert/imports/dynamic_import/flow.js: -------------------------------------------------------------------------------- 1 | import("./depsA.js").then(module => module.default()); 2 | import("../depsB.js").then(module => module.default()); 3 | -------------------------------------------------------------------------------- /test/fixtures/convert/declare/declare-class/flow.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare class Path extends Node { 3 | // converts Path to a string 4 | toString(): string; 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/convert/declare/function-overloading/flow.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare function getWidget(n: number): Widget; 3 | declare function getWidget(s: string): Widget[]; 4 | -------------------------------------------------------------------------------- /test/fixtures/convert/formatting/comment_line02/flow.js: -------------------------------------------------------------------------------- 1 | const c = () => { 2 | // foo 3 | const a = 5; 4 | 5 | // bar 6 | const b = 10; 7 | // baz 8 | 9 | }; -------------------------------------------------------------------------------- /test/fixtures/convert/formatting/prettier_options/ts.js: -------------------------------------------------------------------------------- 1 | function a(): string { 2 | return 'hello, world!' 3 | } 4 | 5 | const point = { 6 | x: 5, 7 | y: 10, 8 | } -------------------------------------------------------------------------------- /test/fixtures/convert/formatting/spacing03/ts.js: -------------------------------------------------------------------------------- 1 | const a = () => { 2 | // first leading comment 3 | // second leading comment 4 | const b = 5; 5 | const c = 10; 6 | }; -------------------------------------------------------------------------------- /test/fixtures/convert/nullable-types/nullable05/ts.js: -------------------------------------------------------------------------------- 1 | type Foo = { 2 | name: string | null | undefined; 3 | onChange: ((arg0: string) => void) | null | undefined; 4 | }; 5 | -------------------------------------------------------------------------------- /test/fixtures/convert/formatting/comment_block01/ts.js: -------------------------------------------------------------------------------- 1 | /** 2 | * foo 3 | */ 4 | const a = 5; 5 | 6 | /** 7 | * bar 8 | */ 9 | const b = 10; 10 | /** 11 | * baz 12 | */ -------------------------------------------------------------------------------- /test/fixtures/convert/formatting/comment_block01/flow.js: -------------------------------------------------------------------------------- 1 | /** 2 | * foo 3 | */ 4 | const a = 5; 5 | 6 | /** 7 | * bar 8 | */ 9 | const b = 10; 10 | /** 11 | * baz 12 | */ -------------------------------------------------------------------------------- /test/fixtures/convert/formatting/object_newlines/flow.js: -------------------------------------------------------------------------------- 1 | const obj = { 2 | 3 | // x 4 | x: 5, 5 | 6 | // y 7 | y: 10, 8 | // trailing comment 9 | 10 | }; -------------------------------------------------------------------------------- /test/fixtures/convert/formatting/spacing03/flow.js: -------------------------------------------------------------------------------- 1 | const a = () => { 2 | // first leading comment 3 | // second leading comment 4 | const b = 5; 5 | 6 | 7 | const c = 10; 8 | 9 | }; -------------------------------------------------------------------------------- /test/fixtures/convert/formatting/object_type_newlines/flow.js: -------------------------------------------------------------------------------- 1 | type Point = { 2 | 3 | // x 4 | x: number, 5 | 6 | // y 7 | y: number, 8 | // trailing newline 9 | 10 | }; -------------------------------------------------------------------------------- /test/fixtures/convert/react/element-config-typeof/ts.js: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | type Props = JSX.LibraryManagedAttributes< 3 | typeof Foo, 4 | React.ComponentProps 5 | >; 6 | -------------------------------------------------------------------------------- /test/fixtures/convert/declare/method-overloading/ts.js: -------------------------------------------------------------------------------- 1 | declare class Path extends Node { 2 | // converts Path to a string 3 | toString(): string; 4 | toString(tabWidth: number): string; 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/convert/decorator/decorator-on-class/flow.js: -------------------------------------------------------------------------------- 1 | import { observer } from "mobx"; 2 | 3 | @observer 4 | class Store { 5 | constructor() {} 6 | 7 | } 8 | 9 | export default Store; 10 | -------------------------------------------------------------------------------- /test/fixtures/convert/decorator/decorator-on-class/ts.js: -------------------------------------------------------------------------------- 1 | import { observer } from "mobx"; 2 | 3 | @observer 4 | class Store { 5 | constructor() {} 6 | 7 | } 8 | 9 | export default Store; 10 | -------------------------------------------------------------------------------- /test/fixtures/convert/imports/named_import_specifier_kind/ts.js: -------------------------------------------------------------------------------- 1 | import type { A, B } from "./depA"; 2 | import { C } from "./depA"; 3 | import type { D, E } from "../depB"; 4 | import { F } from "../depB"; -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | commit-message: 8 | prefix: "[skip netlify]" 9 | -------------------------------------------------------------------------------- /test/fixtures/convert/declare/declare-module/ts.js: -------------------------------------------------------------------------------- 1 | declare module "some-commonjs-module" { 2 | // foo 3 | export default function foo(): string; 4 | // bar 5 | export function bar(): boolean; 6 | } 7 | -------------------------------------------------------------------------------- /test/fixtures/convert/declare/method-overloading/flow.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare class Path extends Node { 3 | // converts Path to a string 4 | toString(): string; 5 | toString(tabWidth: number): string; 6 | } 7 | -------------------------------------------------------------------------------- /playground/examples/basic-types.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | let a: number; 3 | let b: string; 4 | let c: boolean; 5 | let d: void; 6 | let e: any; 7 | let f: mixed; 8 | let g: empty; 9 | let h: [number, string]; 10 | let i: boolean[]; 11 | -------------------------------------------------------------------------------- /test/fixtures/convert/formatting/comment_block02/ts.js: -------------------------------------------------------------------------------- 1 | const c = () => { 2 | /** 3 | * foo 4 | */ 5 | const a = 5; 6 | 7 | /** 8 | * bar 9 | */ 10 | const b = 10; 11 | /** 12 | * baz 13 | */ 14 | }; -------------------------------------------------------------------------------- /test/fixtures/convert/declare/declare-module/flow.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | declare module "some-commonjs-module" { 3 | // foo 4 | declare export default function foo(): string; 5 | 6 | // bar 7 | declare export function bar(): boolean; 8 | } 9 | -------------------------------------------------------------------------------- /test/fixtures/convert/formatting/prettier_options/options.json: -------------------------------------------------------------------------------- 1 | { 2 | "prettier": true, 3 | "prettierOptions": { 4 | "semi": false, 5 | "singleQuote": true, 6 | "tabWidth": 4, 7 | "trailingComma": "all" 8 | } 9 | } -------------------------------------------------------------------------------- /test/fixtures/convert/react/element-config-typeof/options.json: -------------------------------------------------------------------------------- 1 | { 2 | "prettier": true, 3 | "prettierOptions": { 4 | "semi": true, 5 | "singleQuote": false, 6 | "tabWidth": 2, 7 | "trailingComma": "all" 8 | } 9 | } -------------------------------------------------------------------------------- /test/fixtures/convert/formatting/comment_block02/flow.js: -------------------------------------------------------------------------------- 1 | const c = () => { 2 | /** 3 | * foo 4 | */ 5 | const a = 5; 6 | 7 | /** 8 | * bar 9 | */ 10 | const b = 10; 11 | /** 12 | * baz 13 | */ 14 | 15 | }; -------------------------------------------------------------------------------- /test/fixtures/convert/formatting/class_properties01/ts.js: -------------------------------------------------------------------------------- 1 | class Foo { 2 | // line comment 3 | foo: string; 4 | 5 | /** 6 | * block comment 7 | */ 8 | bar = 5; 9 | 10 | // comment on method 11 | baz() { 12 | console.log("baz"); 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /test/fixtures/convert/formatting/class_properties02/ts.js: -------------------------------------------------------------------------------- 1 | class Foo { 2 | // line comment 3 | #foo: string; 4 | 5 | /** 6 | * block comment 7 | */ 8 | #bar = 5; 9 | 10 | // comment on method 11 | #baz() { 12 | console.log("baz"); 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /test/fixtures/convert/react/basic-types-same/ts.js: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | let component: React.Component; 3 | let pureComponent: React.PureComponent; 4 | let componentType: React.ComponentType; 5 | let context: React.Context; 6 | let ref: React.Ref; 7 | let key: React.Key; 8 | -------------------------------------------------------------------------------- /test/fixtures/convert/react/events/ts.js: -------------------------------------------------------------------------------- 1 | const handler = (e: React.SyntheticEvent) => {}; 2 | 3 | const animationHandler = (e: React.AnimationEvent) => {}; 4 | 5 | const clipboardHandler = (e: React.ClipboardEvent) => {}; 6 | 7 | const inputHandler = (e: React.SyntheticEvent) => {}; 8 | -------------------------------------------------------------------------------- /playground/examples/object-types.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | type Foo = { 3 | a(): string, 4 | ...T, 5 | x?: number, 6 | +y?: string, 7 | [num: number]: T 8 | }; 9 | 10 | type Bar = { 11 | +[key: boolean]: string 12 | }; 13 | 14 | type Baz = { 15 | -[id: string]: ?T 16 | }; 17 | -------------------------------------------------------------------------------- /test/fixtures/convert/react/events/flow.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | const handler = (e: SyntheticEvent<>) => {}; 3 | const animationHandler = (e: SyntheticAnimationEvent<>) => {}; 4 | const clipboardHandler = (e: SyntheticClipboardEvent<>) => {}; 5 | const inputHandler = (e: SyntheticInputEvent<>) => {}; 6 | -------------------------------------------------------------------------------- /test/fixtures/convert/formatting/class_properties01/flow.js: -------------------------------------------------------------------------------- 1 | class Foo { 2 | 3 | // line comment 4 | foo: string; 5 | 6 | /** 7 | * block comment 8 | */ 9 | bar = 5; 10 | 11 | // comment on method 12 | baz() { 13 | console.log("baz"); 14 | } 15 | } -------------------------------------------------------------------------------- /test/fixtures/convert/formatting/class_properties02/flow.js: -------------------------------------------------------------------------------- 1 | class Foo { 2 | 3 | // line comment 4 | #foo: string; 5 | 6 | /** 7 | * block comment 8 | */ 9 | #bar = 5; 10 | 11 | // comment on method 12 | #baz() { 13 | console.log("baz"); 14 | } 15 | } -------------------------------------------------------------------------------- /test/fixtures/convert/react/basic-types-same/flow.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import * as React from "react"; 3 | let component: React.Component; 4 | let pureComponent: React.PureComponent; 5 | let componentType: React.ComponentType; 6 | let context: React.Context; 7 | let ref: React.Ref; 8 | let key: React.Key; 9 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | test/fixtures/convert/formatting 2 | test/fixtures/convert/imports/named_import_specifier_kind/ 3 | test/fixtures/convert/function-types/predicate03 4 | test/fixtures/convert/imports/dynamic_import 5 | test/fixtures/convert/decorator/ 6 | playground/static 7 | babel-traverse 8 | babel-types 9 | -------------------------------------------------------------------------------- /test/fixtures/convert/decorator/multi-decorator/flow.js: -------------------------------------------------------------------------------- 1 | import { observer, action } from "mobx"; 2 | export class Store { 3 | @action 4 | @observer 5 | multipleDecorator() { 6 | return 0; 7 | } 8 | 9 | @action @observer 10 | multipleDecorator2() { 11 | return 0; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /test/fixtures/convert/decorator/multi-decorator/ts.js: -------------------------------------------------------------------------------- 1 | import { observer, action } from "mobx"; 2 | export class Store { 3 | @action 4 | @observer 5 | multipleDecorator() { 6 | return 0; 7 | } 8 | 9 | @action 10 | @observer 11 | multipleDecorator2() { 12 | return 0; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /playground/examples/generics.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | type BinaryOp = (a: T, b: T) => T; 3 | 4 | type Item = { 5 | foo: T, 6 | bar: T 7 | }; 8 | 9 | function identity(val: T): T { 10 | let str: string = val; // Works! 11 | // $ExpectError 12 | let bar: "bar" = val; // Error! 13 | return val; 14 | } 15 | -------------------------------------------------------------------------------- /test/fixtures/convert/formatting/switch_cast_newlines/ts.js: -------------------------------------------------------------------------------- 1 | switch (kind) { 2 | /** 3 | * foo 4 | */ 5 | case "foo": 6 | console.log("foo"); 7 | break; 8 | 9 | case "bar": 10 | console.log("bar"); 11 | break; 12 | 13 | // default 14 | default: 15 | break; 16 | 17 | /** 18 | * trailing comment 19 | */ 20 | } -------------------------------------------------------------------------------- /test/fixtures/convert/react/basic-types-different/flow.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | import * as React from "react"; 3 | let node: React.Node; 4 | let text: React.Text; 5 | let child: React.Child; 6 | let children: React.Children; 7 | let fragment: React.Fragment; 8 | let portal: React.Portal; 9 | let nodeArray: React.NodeArray; 10 | let element: React.Element; 11 | -------------------------------------------------------------------------------- /test/fixtures/convert/decorator/decorator-factory/flow.js: -------------------------------------------------------------------------------- 1 | function inject(options: { 2 | api_version: string; 3 | }) { 4 | return target => target; 5 | } 6 | 7 | @inject({ 8 | api_version: "0.3.4" 9 | }) 10 | class MyComponent extends React.Component { 11 | render() { 12 | return document.createElement("div"); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /test/fixtures/convert/decorator/decorator-factory/ts.js: -------------------------------------------------------------------------------- 1 | function inject(options: { 2 | api_version: string; 3 | }) { 4 | return target => target; 5 | } 6 | 7 | @inject({ 8 | api_version: "0.3.4" 9 | }) 10 | class MyComponent extends React.Component { 11 | render() { 12 | return document.createElement("div"); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /playground/examples/utility-types.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | type NumDict = { [key: string]: number }; 3 | 4 | let keys: $Keys; 5 | let values: $Values; 6 | 7 | const numDict: NumDict = { 8 | x: 5, 9 | y: 10 10 | }; 11 | 12 | let prop: $ElementType; 13 | 14 | type Point = [number, number]; 15 | let x: $ElementType; 16 | -------------------------------------------------------------------------------- /test/fixtures/convert/react/basic-types-different/ts.js: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | let node: React.ReactNode; 3 | let text: React.ReactText; 4 | let child: React.ReactChild; 5 | let children: React.ReactChildren; 6 | let fragment: React.ReactFragment; 7 | let portal: React.ReactPortal; 8 | let nodeArray: React.ReactNodeArray; 9 | let element: React.ReactElement; 10 | -------------------------------------------------------------------------------- /playground/examples/function-types.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | type BinaryOp = (number, number) => number; 3 | type UnaryOp = (a: number) => number; 4 | 5 | function add(left: number, right: number, round?: boolean) { 6 | const sum = left + right; 7 | return round ? Math.round(sum) : sum; 8 | } 9 | 10 | const sum = (...terms: number[]): number => terms.reduce((a, x) => a + x, 0); 11 | -------------------------------------------------------------------------------- /test/fixtures/convert/decorator/mobx/flow.js: -------------------------------------------------------------------------------- 1 | import { observable, computed, action } from "mobx"; 2 | export class Store { 3 | @observable 4 | markersMapping: Map = new Map(); 5 | 6 | @computed 7 | get filePath(): ?string { 8 | return "/file"; 9 | } 10 | 11 | @action 12 | newMarkerStore(editorId: number) { 13 | return 1; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /test/fixtures/convert/react/global-types/ts.js: -------------------------------------------------------------------------------- 1 | type A = React.ReactNode; 2 | type B = React.ReactElement, T>; 3 | type C = React.ElementType; 4 | type D = React.Component; 5 | type E = React.Component; 6 | type F = React.ComponentType; 7 | type G = React.Context; 8 | type H = React.Ref; 9 | type I = React.FC; 10 | -------------------------------------------------------------------------------- /test/fixtures/convert/react/global-types/flow.js: -------------------------------------------------------------------------------- 1 | // @flow 2 | type A = React$Node; 3 | type B = React$Element; 4 | type C = React$ElementType; 5 | type D = React$Component; 6 | type E = React$Component; 7 | type F = React$ComponentType; 8 | type G = React$Context; 9 | type H = React$Ref; 10 | type I = React$StatelessFunctionalComponent; 11 | -------------------------------------------------------------------------------- /test/fixtures/convert/decorator/decorator-on-class-with-argument/flow.js: -------------------------------------------------------------------------------- 1 | import { autoBindMethodsForReact } from "class-autobind-decorator"; 2 | 3 | @autoBindMethodsForReact(AUTOBIND_CFG) 4 | class PasswordEditor extends React.PureComponent { 5 | constructor(props: Props) { 6 | super(props); 7 | this.state = { 8 | showPassword: false 9 | }; 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /test/fixtures/convert/decorator/decorator-on-class-with-argument/ts.js: -------------------------------------------------------------------------------- 1 | import { autoBindMethodsForReact } from "class-autobind-decorator"; 2 | 3 | @autoBindMethodsForReact(AUTOBIND_CFG) 4 | class PasswordEditor extends React.PureComponent { 5 | constructor(props: Props) { 6 | super(props); 7 | this.state = { 8 | showPassword: false 9 | }; 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /test/fixtures/convert/formatting/switch_cast_newlines/flow.js: -------------------------------------------------------------------------------- 1 | switch (kind) { 2 | 3 | /** 4 | * foo 5 | */ 6 | case "foo": 7 | console.log("foo"); 8 | break; 9 | 10 | case "bar": 11 | console.log("bar"); 12 | break; 13 | 14 | // default 15 | default: 16 | break; 17 | 18 | /** 19 | * trailing comment 20 | */ 21 | } -------------------------------------------------------------------------------- /playground/src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import * as ReactDOM from "react-dom"; 3 | 4 | import App from "./app"; 5 | 6 | const container = document.querySelector("#container"); 7 | 8 | ReactDOM.render(, container); 9 | 10 | if (module.hot) { 11 | module.hot.dispose(function () { 12 | // module is about to be replaced 13 | }); 14 | 15 | module.hot.accept(); 16 | } 17 | -------------------------------------------------------------------------------- /test/fixtures/convert/decorator/mobx/ts.js: -------------------------------------------------------------------------------- 1 | import { observable, computed, action } from "mobx"; 2 | export class Store { 3 | @observable 4 | markersMapping: Map = new Map(); 5 | 6 | @computed 7 | get filePath(): string | null | undefined { 8 | return "/file"; 9 | } 10 | 11 | @action 12 | newMarkerStore(editorId: number) { 13 | return 1; 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/detect-jsx.ts: -------------------------------------------------------------------------------- 1 | import { parse } from "@babel/parser"; 2 | import traverse from "@babel/traverse"; 3 | 4 | import { parseOptions } from "./convert"; 5 | 6 | export const detectJsx = (code) => { 7 | let jsx = false; 8 | const ast = parse(code, parseOptions); 9 | 10 | traverse(ast, { 11 | JSXOpeningElement({ node }) { 12 | jsx = true; 13 | }, 14 | }); 15 | 16 | return jsx; 17 | }; 18 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | require_ci_to_pass: no 3 | 4 | coverage: 5 | precision: 2 6 | round: down 7 | range: "70...100" 8 | 9 | status: 10 | project: yes 11 | patch: yes 12 | changes: no 13 | 14 | parsers: 15 | gcov: 16 | branch_detection: 17 | conditional: yes 18 | loop: yes 19 | method: no 20 | macro: no 21 | 22 | comment: 23 | layout: "reach,diff,flags,tree" 24 | behavior: default 25 | require_changes: no 26 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | collectCoverageFrom: ["/src/**/*.ts"], 3 | testMatch: ["/test/*.test.ts"], 4 | // TODO: write a custom transform that checks for @flow comments and then 5 | // uses babel to transform the file with either the flow preset or the typescript 6 | // preset. 7 | transform: { 8 | "^.+\\.(ts|tsx|js)$": "/jest.transformer.js", 9 | }, 10 | modulePathIgnorePatterns: ["babel/packages/babel-core"], 11 | }; 12 | -------------------------------------------------------------------------------- /jest.transformer.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const { transformSync } = require("@babel/core"); 3 | 4 | const { flowConfig, tsConfig } = require("./babel.configs.js"); 5 | 6 | module.exports = { 7 | process(src, filename, config, options) { 8 | if (path.extname(filename) === ".js") { 9 | return transformSync(src, { 10 | ...flowConfig, 11 | filename: filename, 12 | }).code; 13 | } else { 14 | return transformSync(src, { 15 | ...tsConfig, 16 | filename: filename, 17 | }).code; 18 | } 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /playground/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 26 | 27 | 28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /babel.configs.js: -------------------------------------------------------------------------------- 1 | const presets = [ 2 | ["@babel/preset-env", {targets: { node: 12 }}], 3 | ]; 4 | 5 | const commonPlugins = [ 6 | "@babel/plugin-proposal-class-properties", 7 | [ 8 | "@babel/plugin-proposal-decorators", 9 | { legacy: true }, 10 | ], 11 | ["module-resolver", { 12 | "alias": { 13 | "^@babel/(.+)": "./babel/packages/babel-\\1/src/index" 14 | } 15 | }], 16 | ]; 17 | 18 | exports.flowConfig = { 19 | babelrc: false, 20 | presets: presets, 21 | plugins: [ 22 | [ 23 | "@babel/plugin-transform-flow-strip-types", 24 | { allowDeclareFields: true }, 25 | ], 26 | ...commonPlugins, 27 | ], 28 | }; 29 | 30 | exports.tsConfig = { 31 | babelrc: false, 32 | presets: presets, 33 | plugins: [ 34 | [ 35 | "@babel/plugin-transform-typescript", 36 | { allowDeclareFields: true }, 37 | ], 38 | ...commonPlugins, 39 | ], 40 | }; 41 | -------------------------------------------------------------------------------- /playground/babel.configs.js: -------------------------------------------------------------------------------- 1 | const presets = [["@babel/preset-env", { targets: { node: 12 } }]]; 2 | 3 | const commonPlugins = [ 4 | "@babel/plugin-proposal-class-properties", 5 | [ 6 | "module-resolver", 7 | { 8 | alias: { 9 | "^@babel/(.+)": "../babel/packages/babel-\\1/src/index", 10 | }, 11 | }, 12 | ], 13 | ]; 14 | 15 | exports.flowConfig = { 16 | babelrc: false, 17 | presets: presets, 18 | plugins: [ 19 | ["@babel/plugin-transform-flow-strip-types", { allowDeclareFields: true }], 20 | ...commonPlugins, 21 | ], 22 | }; 23 | 24 | exports.tsConfig = { 25 | babelrc: false, 26 | presets: presets, 27 | plugins: [ 28 | ["@babel/plugin-transform-typescript", { allowDeclareFields: true }], 29 | ...commonPlugins, 30 | ], 31 | }; 32 | 33 | exports.tsxConfig = { 34 | babelrc: false, 35 | presets: [ 36 | "@babel/preset-env", 37 | "@babel/preset-react", 38 | "@babel/preset-typescript", 39 | ], 40 | plugins: [ 41 | ...commonPlugins, 42 | ], 43 | }; 44 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | 3 | const {flowConfig, tsConfig} = require("./babel.configs.js"); 4 | 5 | module.exports = { 6 | target: "node", 7 | mode: "development", 8 | entry: { 9 | convert: "./src/convert.ts", 10 | cli: "./src/cli.ts", 11 | }, 12 | output: { 13 | path: path.resolve(__dirname, "dist"), 14 | filename: "[name].bundle.js", 15 | library: { 16 | name: "flowToTs", 17 | type: "umd", 18 | }, 19 | globalObject: "this", 20 | }, 21 | module: { 22 | rules: [ 23 | { 24 | test: /\.js$/, 25 | exclude: /(node_modules|bower_components)/, 26 | use: { 27 | loader: "babel-loader", 28 | options: flowConfig, 29 | }, 30 | }, 31 | { 32 | test: /\.ts$/, 33 | exclude: /(node_modules|bower_components)/, 34 | use: { 35 | loader: "babel-loader", 36 | options: tsConfig, 37 | }, 38 | }, 39 | ], 40 | }, 41 | resolve: { 42 | extensions: [".ts", ".js"], 43 | }, 44 | }; 45 | -------------------------------------------------------------------------------- /playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "playground", 3 | "version": "0.0.1", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "dependencies": { 7 | "codemirror": "^5.46.0", 8 | "react": "^16.8.5", 9 | "react-codemirror2": "^6.0.0", 10 | "react-dom": "^16.8.5" 11 | }, 12 | "devDependencies": { 13 | "@babel/core": "^7.13.10", 14 | "@babel/plugin-proposal-class-properties": "^7.13.0", 15 | "@babel/plugin-transform-typescript": "^7.13.0", 16 | "@babel/preset-env": "^7.13.10", 17 | "@babel/preset-react": "^7.12.13", 18 | "@babel/preset-typescript": "^7.3.3", 19 | "@types/react": "^16.8.8", 20 | "@types/react-dom": "^16.8.3", 21 | "@types/webpack-env": "^1.13.9", 22 | "babel-loader": "^8.0.5", 23 | "babel-plugin-module-resolver": "^4.1.0", 24 | "css-loader": "^2.1.1", 25 | "file-loader": "^3.0.1", 26 | "html-webpack-plugin": "^5.3.1", 27 | "raw-loader": "^2.0.0", 28 | "style-loader": "^0.23.1", 29 | "typescript": "^3.3.4000", 30 | "webpack": "^5.27.0", 31 | "webpack-cli": "^4.5.0", 32 | "webpack-dev-server": "^3.11.2" 33 | }, 34 | "scripts": { 35 | "build": "webpack build", 36 | "watch": "webpack -w", 37 | "start": "webpack serve --hot" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.github/workflows/node.yml: -------------------------------------------------------------------------------- 1 | name: Node CI 2 | 3 | on: [pull_request] 4 | 5 | jobs: 6 | coverage: 7 | runs-on: ubuntu-latest 8 | 9 | steps: 10 | - uses: actions/checkout@v1 11 | - name: Use Node.js 12.x 12 | uses: actions/setup-node@v1 13 | with: 14 | node-version: 12.x 15 | - name: checkout babel submodule 16 | run: git submodule update --depth 1 --init -- babel 17 | - name: install babel deps 18 | working-directory: ./babel 19 | run: yarn install 20 | - name: install flow-to-ts deps 21 | run: yarn install 22 | - name: yarn coverage 23 | run: yarn coverage 24 | env: 25 | CI: true 26 | - name: codecov 27 | uses: codecov/codecov-action@v1 28 | 29 | build: 30 | runs-on: ubuntu-latest 31 | 32 | strategy: 33 | matrix: 34 | node-version: [10.x, 12.x, 14.x] 35 | 36 | steps: 37 | - uses: actions/checkout@v1 38 | - name: Use Node.js ${{ matrix.node-version }} 39 | uses: actions/setup-node@v1 40 | with: 41 | node-version: ${{ matrix.node-version }} 42 | - name: checkout babel submodule 43 | run: git submodule update --depth 1 --init -- babel 44 | - name: install babel deps 45 | working-directory: ./babel 46 | run: yarn install 47 | - name: install flow-to-ts deps 48 | run: yarn install 49 | - name: yarn test 50 | run: yarn test 51 | env: 52 | CI: true 53 | -------------------------------------------------------------------------------- /test/matchers.ts: -------------------------------------------------------------------------------- 1 | const diff = require("jest-diff"); 2 | 3 | expect.extend({ 4 | toMatchErrors(received, expected) { 5 | received = received.map((diagnostic) => { 6 | return { 7 | start: diagnostic.start, 8 | length: diagnostic.length, 9 | code: diagnostic.code, 10 | messageText: diagnostic.messageText, 11 | }; 12 | }); 13 | const pass = JSON.stringify(received) === JSON.stringify(expected); 14 | 15 | const options = { 16 | isNot: this.isNot, 17 | promise: this.promise, 18 | }; 19 | 20 | const message = pass 21 | ? () => 22 | this.utils.matcherHint( 23 | "toMatchErrors", 24 | undefined, 25 | undefined, 26 | options 27 | ) + 28 | "\n\n" + 29 | `Expected: ${this.utils.printExpected(expected)}\n` + 30 | `Received: ${this.utils.printReceived(received)}` 31 | : () => { 32 | const difference = diff(expected, received, { 33 | expand: this.expand, 34 | }); 35 | return ( 36 | this.utils.matcherHint("toEqual", undefined, undefined, options) + 37 | "\n\n" + 38 | (difference && difference.includes("- Expect") 39 | ? `Difference:\n\n${difference}` 40 | : `Expected: ${this.utils.printExpected(expected)}\n` + 41 | `Received: ${this.utils.printReceived(received)}`) 42 | ); 43 | }; 44 | 45 | return { actual: received, message, pass }; 46 | }, 47 | }); 48 | -------------------------------------------------------------------------------- /test/convert.test.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import path from "path"; 3 | 4 | const trimTrailingNewlines = (string) => 5 | string.replace(new RegExp("\r\n", "g"), "\n").replace(/(\n)+$/, ""); 6 | 7 | import { convert } from "../src/convert"; 8 | 9 | const failingTestNames = ["spread03", "spread04"]; 10 | 11 | describe.only("convert", () => { 12 | const suites = false 13 | ? [] 14 | : fs.readdirSync(path.join(__dirname, "fixtures/convert")); 15 | for (const suiteName of suites) { 16 | describe(suiteName, () => { 17 | const tests = false 18 | ? [] 19 | : fs.readdirSync(path.join(__dirname, "fixtures/convert", suiteName)); 20 | for (const testName of tests.filter( 21 | (testName) => !failingTestNames.includes(testName) 22 | )) { 23 | const dir = path.join( 24 | __dirname, 25 | "fixtures/convert", 26 | suiteName, 27 | testName 28 | ); 29 | const flowCode = trimTrailingNewlines( 30 | fs.readFileSync(path.join(dir, "flow.js"), "utf-8") 31 | ); 32 | const tsCode = trimTrailingNewlines( 33 | fs.readFileSync(path.join(dir, "ts.js"), "utf-8") 34 | ); 35 | const hasOptions = fs.existsSync(path.join(dir, "options.json")); 36 | 37 | if (hasOptions) { 38 | const options = JSON.parse( 39 | fs.readFileSync(path.join(dir, "options.json"), "utf-8") 40 | ); 41 | test(testName.replace(/_/g, " "), () => { 42 | expect(convert(flowCode, options)).toEqual(tsCode); 43 | }); 44 | } else { 45 | test(testName.replace(/_/g, " "), () => { 46 | expect(convert(flowCode)).toEqual(tsCode); 47 | }); 48 | } 49 | } 50 | }); 51 | } 52 | }); 53 | -------------------------------------------------------------------------------- /playground/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const webpack = require("webpack"); 3 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 4 | 5 | const {flowConfig, tsConfig, tsxConfig} = require("./babel.configs.js"); 6 | 7 | module.exports = { 8 | mode: "development", 9 | target: "web", 10 | entry: { 11 | index: "./src/index.tsx" 12 | }, 13 | output: { 14 | publicPath: "/", 15 | filename: "[name].bundle.js", 16 | chunkFilename: "[name].bundle.js", 17 | path: path.resolve(__dirname, "dist") 18 | }, 19 | module: { 20 | rules: [ 21 | { 22 | test: /\.(js|jsx)$/, 23 | exclude: /(node_modules|bower_components)/, 24 | use: { 25 | loader: "babel-loader", 26 | options: flowConfig, 27 | }, 28 | }, 29 | { 30 | test: /\.ts$/, 31 | exclude: /(node_modules|bower_components)/, 32 | use: { 33 | loader: "babel-loader", 34 | options: tsConfig, 35 | }, 36 | }, 37 | { 38 | test: /\.tsx$/, 39 | exclude: /(node_modules|bower_components)/, 40 | use: { 41 | loader: "babel-loader", 42 | options: tsxConfig, 43 | }, 44 | }, 45 | { 46 | test: /\.css$/, 47 | use: ["style-loader", "css-loader"] 48 | }, 49 | { 50 | test: /.png$/, 51 | use: ["file-loader"] 52 | } 53 | ] 54 | }, 55 | resolve: { 56 | extensions: [".ts", ".tsx", ".js", ".json"], 57 | fallback: { 58 | fs: false, 59 | constants: false, 60 | }, 61 | }, 62 | plugins: [ 63 | new HtmlWebpackPlugin({ 64 | template: "./templates/index.html" 65 | }), 66 | new webpack.DefinePlugin({ 67 | "process.platform": JSON.stringify("web"), 68 | "process.env": { 69 | NODE_ENV: JSON.stringify("development"), 70 | }, 71 | }), 72 | new webpack.ProvidePlugin({ 73 | Buffer: ['buffer', 'Buffer'], 74 | }), 75 | ], 76 | node: { 77 | global: true, 78 | }, 79 | }; 80 | -------------------------------------------------------------------------------- /src/util.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Track which nodes a comment is attached to. 3 | * 4 | * state.commentsToNodesMap is a Map() between comment position in the file and 5 | * an object with references to node(s) it was attached to as either a leading 6 | * or trailing comment (or both). 7 | * 8 | * In order to call this function correctly, the transformed node must be passed 9 | * in. This requires copying over the following properties from the original 10 | * node: 11 | * - loc 12 | * - leadingComments 13 | * - trailingComments 14 | * 15 | * NOTE: The copied `loc` will be wrong for the new node. It's need by convert 16 | * though which uses it to determine whether maintain the position of trailing 17 | * line comments. 18 | * 19 | * @param {*} node 20 | * @param {*} state 21 | */ 22 | export const trackComments = (node, state) => { 23 | let leadingNode = node, 24 | trailingNode = node; 25 | 26 | if (Array.isArray(node)) { 27 | leadingNode = node[0]; 28 | trailingNode = node[node.length - 1]; 29 | } 30 | 31 | if (leadingNode.leadingComments) { 32 | for (const comment of leadingNode.leadingComments) { 33 | const { start, end } = comment; 34 | const key = `${start}:${end}`; 35 | 36 | if (state.commentsToNodesMap.has(key)) { 37 | state.commentsToNodesMap.get(key).leading = leadingNode; 38 | } else { 39 | state.commentsToNodesMap.set(key, { leading: leadingNode }); 40 | } 41 | } 42 | } 43 | if (trailingNode.trailingComments) { 44 | for (const comment of trailingNode.trailingComments) { 45 | const { start, end } = comment; 46 | const key = `${start}:${end}`; 47 | 48 | if (state.commentsToNodesMap.has(key)) { 49 | state.commentsToNodesMap.get(key).trailing = trailingNode; 50 | } else { 51 | state.commentsToNodesMap.set(key, { trailing: trailingNode }); 52 | } 53 | } 54 | } 55 | }; 56 | 57 | export function partition( 58 | iter: Iterable, 59 | fn: (val: T) => val is U 60 | ): [T[], U[]] { 61 | const l = [], 62 | r = []; 63 | for (const v of iter) { 64 | (fn(v) ? r : l).push(v); 65 | } 66 | return [l, r]; 67 | } 68 | 69 | export function returning(v: T, fn: (arg: T) => unknown): T { 70 | fn(v); 71 | return v; 72 | } 73 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@khanacademy/flow-to-ts", 3 | "version": "0.5.2", 4 | "publishConfig": { 5 | "access": "public" 6 | }, 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/khan/flow-to-ts" 10 | }, 11 | "main": "dist/convert.js", 12 | "bin": { 13 | "flow-to-ts": "./bin/flow-to-ts.js" 14 | }, 15 | "files": [ 16 | "bin/", 17 | "dist/" 18 | ], 19 | "license": "MIT", 20 | "dependencies": { 21 | "commander": "^2.19.0", 22 | "glob": "^7.1.6", 23 | "prettier": "2.2.1", 24 | "simple-git": "^3.5.0", 25 | "trim-right": "^1.0.1" 26 | }, 27 | "devDependencies": { 28 | "@babel/core": "^7.13.10", 29 | "@babel/plugin-proposal-class-properties": "^7.13.0", 30 | "@babel/plugin-proposal-decorators": "^7.13.5", 31 | "@babel/plugin-transform-flow-strip-types": "^7.13.0", 32 | "@babel/plugin-transform-typescript": "^7.13.0", 33 | "@babel/preset-env": "^7.13.10", 34 | "@babel/preset-flow": "^7.12.13", 35 | "@babel/preset-typescript": "^7.13.0", 36 | "@types/jest": "^26.0.20", 37 | "@types/prettier": "^2.2.3", 38 | "@types/typescript": "^2.0.0", 39 | "babel-loader": "^8.2.2", 40 | "babel-plugin-module-resolver": "^4.1.0", 41 | "codecov": "^3.2.0", 42 | "flow-bin": "^0.98.0", 43 | "husky": "^1.3.1", 44 | "jest": "^26.6.3", 45 | "jest-mock-console": "^0.4.2", 46 | "jest-mock-process": "^1.2.0", 47 | "tmp": "^0.1.0", 48 | "ts-jest": "^26.5.4", 49 | "typescript": "^4.2.3", 50 | "webpack": "^5.27.0", 51 | "webpack-cli": "^4.5.0" 52 | }, 53 | "scripts": { 54 | "prepare": "git submodule update --init --recursive && cd babel && yarn install && cd ..", 55 | "prepublishOnly": "webpack build", 56 | "build": "webpack build", 57 | "type-check": "tsc --project tsconfig.json", 58 | "watch": "tsc --watch --project tsconfig.json", 59 | "test": "jest", 60 | "coverage": "jest --coverage", 61 | "pretty-quick": "prettier src/*.ts test/*.ts playground/src/*.tsx --write", 62 | "pretty-tests": "prettier ./test/fixtures/**/ts.js --parser typescript --write && prettier ./test/fixtures/**/flow.js --parser flow --write" 63 | }, 64 | "husky": { 65 | "hooks": { 66 | "pre-push": "npm test", 67 | "pre-commit": "npm run pretty-quick && npm run pretty-tests" 68 | } 69 | }, 70 | "jest": { 71 | "setupFilesAfterEnv": [ 72 | "jest-mock-console/dist/setupTestFramework.js" 73 | ] 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /playground/src/hash.ts: -------------------------------------------------------------------------------- 1 | import { Options } from "./options-panel"; 2 | 3 | export const maybeDecodeHash = ( 4 | hash: string 5 | ): { code: string; options: Options } => { 6 | try { 7 | const urlParams = hash 8 | .slice(1) 9 | .split("&") 10 | .reduce((params, param) => { 11 | const [key, value] = param.split("="); 12 | return { 13 | ...params, 14 | [key]: value === undefined ? true : value 15 | }; 16 | }, {}) as any; 17 | 18 | if (!urlParams.code) { 19 | return; 20 | } 21 | 22 | const options = { prettierOptions: {} } as Options; 23 | 24 | if (urlParams.prettier) { 25 | options.prettier = Boolean(parseInt(urlParams.prettier)); 26 | } 27 | if (urlParams.semi) { 28 | options.prettierOptions.semi = Boolean(parseInt(urlParams.semi)); 29 | } 30 | if (urlParams.singleQuote) { 31 | options.prettierOptions.singleQuote = Boolean( 32 | parseInt(urlParams.singleQuote) 33 | ); 34 | } 35 | if (urlParams.tabWidth) { 36 | options.prettierOptions.tabWidth = parseInt(urlParams.tabWidth); 37 | } 38 | if (urlParams.trailingComma) { 39 | options.prettierOptions.trailingComma = urlParams.trailingComma; 40 | } 41 | if (urlParams.bracketSpacing) { 42 | options.prettierOptions.bracketSpacing = Boolean( 43 | parseInt(urlParams.bracketSpacing) 44 | ); 45 | } 46 | if (urlParams.arrowParens) { 47 | options.prettierOptions.arrowParens = urlParams.arrowParams; 48 | } 49 | if (urlParams.printWidth) { 50 | options.prettierOptions.printWidth = parseInt(urlParams.printWidth); 51 | } 52 | if (urlParams.inlineUtilityTypes) { 53 | options.inlineUtilityTypes = Boolean( 54 | parseInt(urlParams.inlineUtilityTypes) 55 | ); 56 | } 57 | 58 | const code = atob(urlParams.code); 59 | 60 | return { code, options }; 61 | } catch (e) { 62 | return; 63 | } 64 | }; 65 | 66 | export const encodeHash = (code: string, options: Options) => { 67 | const urlParams = { 68 | code: btoa(code) 69 | } as any; 70 | 71 | for (const [key, value] of Object.entries(options)) { 72 | urlParams[key] = value; 73 | } 74 | 75 | return Object.entries(urlParams) 76 | .map(mapParams) 77 | .join("&"); 78 | }; 79 | 80 | const mapParams = ([key, value]) => { 81 | if (typeof value === "boolean") { 82 | return `${key}=${value ? 1 : 0}`; 83 | } else if (typeof value === "object") { 84 | return Object.entries(value) 85 | .map(mapParams) 86 | .join("&"); 87 | } else { 88 | return `${key}=${value}`; 89 | } 90 | }; 91 | -------------------------------------------------------------------------------- /test/type-check.test.ts: -------------------------------------------------------------------------------- 1 | import ts from "typescript"; 2 | import tmp from "tmp"; 3 | import fs from "fs"; 4 | import path from "path"; 5 | import { execFile, spawnSync } from "child_process"; 6 | import flow from "flow-bin"; 7 | 8 | import "./matchers"; 9 | 10 | import { convert } from "../src/convert"; 11 | 12 | const tsOptions = { 13 | filename: "foo.ts", 14 | reportDiagnostics: true, 15 | }; 16 | 17 | tmp.setGracefulCleanup(); 18 | 19 | // @ts-ignore 20 | const tmpobj = tmp.dirSync(); 21 | const fixturesPath = path.join(__dirname, "fixtures", "type-check"); 22 | 23 | const convertAndGetDiagnostics = (basename) => { 24 | const value = fs.readFileSync( 25 | path.join(fixturesPath, `${basename}.js`), 26 | "utf-8" 27 | ); 28 | const tsFilename = path.join(tmpobj.name, `${basename}.ts`); 29 | fs.writeFileSync(tsFilename, convert(value)); 30 | const prog = ts.createProgram([tsFilename], { lib: [] }); 31 | 32 | // Getting diagnostics for a single file is a lot faster than getting them 33 | // for a whoel program, even if the program only contains a single file. 34 | const sf = prog.getSourceFile(tsFilename); 35 | return ts.getPreEmitDiagnostics(prog, sf).map((diag) => diag.messageText); 36 | }; 37 | 38 | describe("type-checking", () => { 39 | const flowResults = {}; 40 | 41 | const spawn = spawnSync(flow, ["--json"], { cwd: fixturesPath }); 42 | const stdout = spawn.stdout.toString().trim(); 43 | 44 | const errors = JSON.parse(stdout).errors; 45 | for (const error of errors) { 46 | for (const message of error.message) { 47 | const filename = path.relative(fixturesPath, message.path); 48 | if (!flowResults.hasOwnProperty(filename)) { 49 | flowResults[filename] = []; 50 | } 51 | flowResults[filename].push(message.descr); 52 | } 53 | } 54 | 55 | it("simple-types-fail", () => { 56 | const diagnostics = convertAndGetDiagnostics("simple-types-fail"); 57 | 58 | expect(diagnostics).toEqual([ 59 | "Type 'number' is not assignable to type 'string'.", 60 | "Type 'boolean' is not assignable to type 'number'.", 61 | "Type 'string' is not assignable to type 'boolean'.", 62 | ]); 63 | expect(flowResults["simple-types-fail.js"]).toEqual([ 64 | "Cannot assign `5` to `foo` because number [1] is incompatible with string [2].", 65 | "Cannot assign `true` to `bar` because boolean [1] is incompatible with number [2].", 66 | "Cannot assign `'foo'` to `baz` because string [1] is incompatible with boolean [2].", 67 | ]); 68 | }); 69 | 70 | it("simple-types-pass", () => { 71 | const diagnostics = convertAndGetDiagnostics("simple-types-pass"); 72 | 73 | expect(diagnostics).toEqual([]); 74 | expect(flowResults["simple-types-pass.js"]).toEqual(undefined); 75 | }); 76 | }); 77 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flow-to-ts 2 | 3 | [![Actions Status](https://github.com/Khan/flow-to-ts/workflows/Node%20CI/badge.svg)](https://github.com/Khan/flow-to-ts/actions) 4 | [![codecov](https://codecov.io/gh/Khan/flow-to-ts/branch/master/graph/badge.svg)](https://codecov.io/gh/Khan/flow-to-ts) 5 | [![npm version](https://badge.fury.io/js/%40khanacademy%2Fflow-to-ts.svg)](https://badge.fury.io/js/%40khanacademy%2Fflow-to-ts) 6 | 7 | Convert Flow code to TypeScript. 8 | 9 | > :warning: This is a WIP and many things still do not work properly. There 10 | > may also be the odd regression from time to time as work progresses. 11 | 12 | The goal of this project is to provide a tool that can translate 95% of Flow 13 | to TypeScript while maintaining a high percentage of the existing type 14 | information. We don't want to convert code and end up with everything using 15 | `any`. We also want to avoid having to make a lot of manual changes to files 16 | afterwards, e.g. changing `SyntheticEvent` to `React.Event`. 17 | 18 | # Quick start 19 | 20 | - `yarn global add @khanacademy/flow-to-ts` 21 | - `flow-to-ts [options] ` 22 | 23 | For a comprehensive list of available options, please check out the [CLI code](./src/cli.ts). 24 | 25 | # Playground 26 | 27 | https://flow-to-ts.netlify.com 28 | 29 | # Principles 30 | 31 | - when exact translation isn't possible: 32 | - downgrade to `any` when possible and provide a gentle warning 33 | - when it isn't possible to downgrade (e.g. `%checks`), remove the syntax 34 | and provide a forceful warning that the code in question will need a human 35 | to convert it manually. 36 | - best effort to maintain blank lines and the position comments, this isn't 37 | always possible so warn in those situations where we can't, e.g. converting 38 | object type spreads to intersection types. 39 | 40 | # Contributing 41 | 42 | ## Bugs 43 | 44 | Bug reports for converting Flow to TypeScript should include a link to the 45 | playground with an example of a minimal reproducible example of the bug. 46 | 47 | ## Feature Requests 48 | 49 | Feature requests are welcome. 50 | 51 | ## Pull Requests 52 | 53 | Please make sure there is a GitHub issue first before creating a pull request 54 | except for small things. Also, please sign our [Contributor License Agreement](https://docs.google.com/forms/d/e/1FAIpQLSdyXYrc8ogVoA46J9KXyIj5nKlZzNkOnQG-4A1R7X_BWGTShQ/viewform). 55 | 56 | Pull requests that fix a bug in the conversion code should include one or more 57 | test cases and should have 100% diff coverage. 58 | 59 | ## Dev quick start 60 | 61 | ```bash 62 | git clone git@github.com:Khan/flow-to-ts.git 63 | cd flow-to-ts 64 | git submodule update --depth 1 --init -- babel 65 | cd babel 66 | yarn 67 | cd .. 68 | yarn 69 | yarn test 70 | ``` 71 | 72 | ## Helpful resources 73 | 74 | - https://github.com/niieani/typescript-vs-flowtype 75 | - https://astexplorer.net/ 76 | -------------------------------------------------------------------------------- /src/convert.ts: -------------------------------------------------------------------------------- 1 | import { parse } from "@babel/parser"; 2 | import traverse from "@babel/traverse"; 3 | import generate from "@babel/generator"; 4 | import * as prettier from "prettier/standalone.js"; 5 | const plugins = [require("prettier/parser-typescript.js")]; 6 | 7 | import { transform } from "./transform"; 8 | import type { ParserOptions } from "@babel/parser"; 9 | 10 | export const parseOptions: ParserOptions = { 11 | sourceType: "module", 12 | plugins: [ 13 | // enable jsx and flow syntax 14 | "jsx", 15 | "flow", 16 | 17 | // handle esnext syntax 18 | "classProperties", 19 | "objectRestSpread", 20 | "dynamicImport", 21 | "optionalChaining", 22 | "nullishCoalescingOperator", 23 | "classPrivateProperties", 24 | "classPrivateMethods", 25 | // decorators 26 | "decorators-legacy", 27 | ], 28 | }; 29 | 30 | const fixComments = (commentsToNodesMap) => { 31 | for (const [key, value] of commentsToNodesMap) { 32 | const { leading, trailing } = value; 33 | 34 | if (leading && trailing) { 35 | trailing.trailingComments = trailing.trailingComments.filter( 36 | (comment) => { 37 | if (comment.type === "CommentLine") { 38 | try { 39 | if (comment.loc.start.line === trailing.loc.start.line) { 40 | // Leave this comment as is because it's at the end of a line, 41 | // e.g. console.log("hello, world"); // print 'hello, world' 42 | return true; 43 | } 44 | } catch (e) { 45 | console.log(trailing); 46 | } 47 | } 48 | const { start, end } = comment; 49 | return `${start}:${end}` !== key; 50 | } 51 | ); 52 | } 53 | } 54 | }; 55 | 56 | export const convert = (flowCode: string, options?: any) => { 57 | const ast = parse(flowCode, parseOptions); 58 | 59 | // key = startLine:endLine, value = {leading, trailing} (nodes) 60 | const commentsToNodesMap = new Map(); 61 | 62 | const startLineToComments = {}; 63 | for (const comment of ast.comments) { 64 | startLineToComments[comment.loc.start.line] = comment; 65 | } 66 | 67 | // apply our transforms, traverse mutates the ast 68 | const state = { 69 | usedUtilityTypes: new Set(), 70 | options: Object.assign({ inlineUtilityTypes: false }, options), 71 | commentsToNodesMap, 72 | startLineToComments, 73 | }; 74 | traverse(ast, transform, null, state); 75 | 76 | fixComments(commentsToNodesMap); 77 | 78 | if (options && options.debug) { 79 | console.log(JSON.stringify(ast, null, 4)); 80 | } 81 | 82 | // we pass flowCode so that generate can compute source maps 83 | // if we ever decide to 84 | let tsCode = generate(ast, undefined, flowCode).code; 85 | 86 | if (options && options.prettier) { 87 | const prettierOptions = { 88 | parser: "typescript", 89 | plugins, 90 | ...options.prettierOptions, 91 | }; 92 | return prettier.format(tsCode, prettierOptions).trim(); 93 | } else { 94 | return tsCode; 95 | } 96 | }; 97 | -------------------------------------------------------------------------------- /src/transforms/utility-types.ts: -------------------------------------------------------------------------------- 1 | import * as t from "@babel/types"; 2 | 3 | // TODO: figure out how to template these inline definitions 4 | const utilityTypes = { 5 | $Keys: (T) => { 6 | // $Keys -> keyof T 7 | // TODO: patch @babel/types - tsTypeOperator should accept two arguments 8 | // return t.tsTypeOperator(typeAnnotation, "keyof"); 9 | return { 10 | type: "TSTypeOperator", 11 | typeAnnotation: T, 12 | operator: "keyof", 13 | }; 14 | }, 15 | $Values: (T) => { 16 | // $Keys -> T[keyof T] 17 | return t.tsIndexedAccessType( 18 | T, 19 | // @ts-ignore 20 | { 21 | type: "TSTypeOperator", 22 | typeAnnotation: T, 23 | operator: "keyof", 24 | } 25 | // TODO: patch @babel/types - tsTypeOperator should accept two arguments 26 | //t.tsTypeOperator(typeAnnotation, "keyof"), 27 | ); 28 | }, 29 | $ReadOnly: (T) => { 30 | // $ReadOnly -> Readonly 31 | const typeName = t.identifier("Readonly"); 32 | const typeParameters = t.tsTypeParameterInstantiation([T]); 33 | return t.tsTypeReference(typeName, typeParameters); 34 | }, 35 | $Shape: (T) => { 36 | // $Shape -> Partial 37 | const typeName = t.identifier("Partial"); 38 | const typeParameters = t.tsTypeParameterInstantiation([T]); 39 | return t.tsTypeReference(typeName, typeParameters); 40 | }, 41 | $NonMaybeType: (T) => { 42 | // $NonMaybeType -> NonNullable 43 | const typeName = t.identifier("NonNullable"); 44 | const typeParameters = t.tsTypeParameterInstantiation([T]); 45 | return t.tsTypeReference(typeName, typeParameters); 46 | }, 47 | $Exact: (T) => { 48 | // $Exact -> T 49 | return T; 50 | }, 51 | $PropertyType: (T, name) => { 52 | // $PropertyType -> T["name"] 53 | return t.tsIndexedAccessType(T, name); 54 | }, 55 | $FlowFixMe: () => { 56 | return t.tsAnyKeyword(); 57 | }, 58 | Class: null, // TODO 59 | 60 | // These are too complicated to inline so we'll leave them as imports 61 | $Diff: null, 62 | $ElementType: null, 63 | $Call: null, 64 | 65 | // The behavior of $Rest only differs when exact object types are involved. 66 | // And since TypeScript doesn't have exact object types using $Diff is okay. 67 | $Rest: "$Diff", 68 | }; 69 | 70 | export const GenericTypeAnnotation = { 71 | exit(path, state) { 72 | const { id: typeName, typeParameters } = path.node; 73 | 74 | if (typeName.name in utilityTypes) { 75 | const value = utilityTypes[typeName.name]; 76 | 77 | if ( 78 | (typeof value === "function" && state.options.inlineUtilityTypes) || 79 | // $Exact and $FlowFixMe don't exist in utility-types so we always inline them. 80 | typeName.name === "$Exact" || 81 | typeName.name === "$FlowFixMe" 82 | ) { 83 | return typeParameters ? value(...typeParameters.params) : value(); 84 | } 85 | 86 | if (typeof value === "string") { 87 | state.usedUtilityTypes.add(value); 88 | return t.tsTypeReference(t.identifier(value), typeParameters); 89 | } 90 | 91 | state.usedUtilityTypes.add(typeName.name); 92 | } 93 | }, 94 | }; 95 | -------------------------------------------------------------------------------- /src/transforms/declare.ts: -------------------------------------------------------------------------------- 1 | import * as t from "@babel/types"; 2 | import { trackComments } from "../util"; 3 | 4 | export const DeclareVariable = { 5 | exit(path) { 6 | const { id } = path.node; 7 | 8 | // TODO: patch @babel/types - t.variableDeclaration omits declare param 9 | // const declaration = t.variableDeclaration("var", [ 10 | // t.variableDeclarator(id), 11 | // ], true), 12 | 13 | path.replaceWith({ 14 | type: "VariableDeclaration", 15 | kind: "var", 16 | declarations: [t.variableDeclarator(id)], 17 | declare: true, 18 | }); 19 | }, 20 | }; 21 | 22 | export const DeclareClass = { 23 | exit(path, state) { 24 | const { 25 | id, 26 | body, 27 | typeParameters, 28 | leadingComments, 29 | trailingComments, 30 | loc, 31 | } = path.node; 32 | const superClass = 33 | path.node.extends.length > 0 ? path.node.extends[0] : undefined; 34 | 35 | // TODO: patch @babel/types - t.classDeclaration omits typescript params 36 | // t.classDeclaration(id, superClass, body, [], false, true, [], undefined) 37 | 38 | const replacementNode = { 39 | type: "ClassDeclaration", 40 | id, 41 | typeParameters, 42 | superClass, 43 | superClassTypeParameters: superClass 44 | ? superClass.typeParameters 45 | : undefined, 46 | body, 47 | declare: true, 48 | leadingComments, 49 | trailingComments, 50 | loc, 51 | }; 52 | 53 | trackComments(replacementNode, state); 54 | 55 | path.replaceWith(replacementNode); 56 | }, 57 | }; 58 | 59 | export const DeclareFunction = { 60 | exit(path, state) { 61 | const { id, leadingComments, trailingComments, loc } = path.node; 62 | const { name, typeAnnotation } = id; 63 | 64 | // TSFunctionType 65 | const functionType = typeAnnotation.typeAnnotation; 66 | 67 | // TODO: patch @babel/types - t.tsDeclaration only accepts 4 params but should accept 7 68 | // t.tsDeclareFunction( 69 | // t.identifier(name), 70 | // t.noop(), 71 | // functionType.parameters, 72 | // functionType.typeAnnotation, 73 | // false, // async 74 | // true, 75 | // false, // generator 76 | // ), 77 | 78 | const replacementNode = { 79 | type: "TSDeclareFunction", 80 | id: t.identifier(name), 81 | typeParameters: functionType.typeParameters, 82 | params: functionType.parameters, 83 | returnType: functionType.typeAnnotation, 84 | declare: !t.isDeclareExportDeclaration(path.parent), 85 | async: false, // TODO 86 | generator: false, // TODO 87 | leadingComments, 88 | trailingComments, 89 | loc, 90 | }; 91 | 92 | trackComments(replacementNode, state); 93 | 94 | path.replaceWith(replacementNode); 95 | }, 96 | }; 97 | 98 | export const DeclareExportDeclaration = { 99 | exit(path, state) { 100 | const { 101 | declaration, 102 | default: _default, 103 | leadingComments, 104 | trailingComments, 105 | loc, 106 | } = path.node; 107 | 108 | const replacementNode = { 109 | type: _default ? "ExportDefaultDeclaration" : "ExportNamedDeclaration", 110 | declaration, 111 | leadingComments, 112 | trailingComments, 113 | loc, 114 | }; 115 | 116 | trackComments(replacementNode, state); 117 | 118 | path.replaceWith(replacementNode); 119 | }, 120 | }; 121 | -------------------------------------------------------------------------------- /src/cli.ts: -------------------------------------------------------------------------------- 1 | import { Command } from "commander"; 2 | import fs from "fs"; 3 | import glob from "glob"; 4 | import prettier from "prettier"; 5 | import simpleGit from "simple-git"; 6 | 7 | import { convert } from "./convert"; 8 | import { detectJsx } from "./detect-jsx"; 9 | import { version } from "../package.json"; 10 | 11 | export const cli = async (argv) => { 12 | const program = new Command(); 13 | program 14 | .version(version) 15 | .option( 16 | "--inline-utility-types", 17 | "inline utility types when possible, defaults to 'false'" 18 | ) 19 | .option("--prettier", "use prettier for formatting") 20 | .option( 21 | "--semi", 22 | "add semi-colons, defaults to 'false' (depends on --prettier)" 23 | ) 24 | .option( 25 | "--keep-untyped", 26 | "do not change files that do not have @flow at the top" 27 | ) 28 | .option( 29 | "--single-quote", 30 | "use single quotes instead of double quotes, defaults to 'false' (depends on --prettier)" 31 | ) 32 | .option( 33 | "--tab-width [width]", 34 | "size of tabs (depends on --prettier)", 35 | /2|4/, 36 | 4 37 | ) 38 | .option( 39 | "--trailing-comma [all|es5|none]", 40 | "where to put trailing commas (depends on --prettier)", 41 | /all|es5|none/, 42 | "all" 43 | ) 44 | .option( 45 | "--bracket-spacing", 46 | "put spaces between braces and contents, defaults to 'false' (depends on --prettier)" 47 | ) 48 | .option( 49 | "--arrow-parens [avoid|always]", 50 | "arrow function param list parens (depends on --prettier)", 51 | /avoid|always/, 52 | "avoid" 53 | ) 54 | .option("--print-width [width]", "line width (depends on --prettier)", 80) 55 | .option( 56 | "--write [new|replace|none]", 57 | "write output to disk instead of STDOUT", 58 | /new|replace|none/, 59 | "none" 60 | ) 61 | .option("--delete-source", "delete the source file"); 62 | 63 | program.parse(argv); 64 | 65 | if (program.args.length === 0) { 66 | program.outputHelp(); 67 | process.exit(1); 68 | } 69 | 70 | const options = { 71 | inlineUtilityTypes: Boolean(program.inlineUtilityTypes), 72 | prettier: program.prettier, 73 | prettierOptions: { 74 | semi: Boolean(program.semi), 75 | singleQuote: Boolean(program.singleQuote), 76 | tabWidth: parseInt(program.tabWidth), 77 | trailingComma: program.trailingComma, 78 | bracketSpacing: Boolean(program.bracketSpacing), 79 | arrowParens: program.arrowParens, 80 | printWidth: parseInt(program.printWidth), 81 | }, 82 | }; 83 | 84 | if (options.prettier) { 85 | try { 86 | const prettierConfig = prettier.resolveConfig.sync(process.cwd()); 87 | if (prettierConfig) { 88 | // @ts-ignore 89 | options.prettierOptions = prettierConfig; 90 | } 91 | } catch (e) { 92 | console.error("error parsing prettier config file"); 93 | console.error(e); 94 | process.exit(1); 95 | } 96 | } 97 | 98 | const files = new Set(); 99 | for (const arg of program.args) { 100 | for (const file of glob.sync(arg)) { 101 | files.add(file); 102 | } 103 | } 104 | 105 | for (const file of files) { 106 | const inFile = file; 107 | const inCode = fs.readFileSync(inFile, "utf-8"); 108 | 109 | if (program.keepUntyped && !inCode.startsWith("// @flow")) { 110 | console.log(`Skipping ${inFile} as it is not typed`); 111 | continue; 112 | } 113 | 114 | try { 115 | const outCode = convert(inCode, options); 116 | 117 | switch (program.write) { 118 | case "new": { 119 | const extension = detectJsx(inCode) ? ".tsx" : ".ts"; 120 | const outFile = file.replace(/\.jsx?$/, extension); 121 | fs.writeFileSync(outFile, outCode); 122 | break; 123 | } 124 | case "replace": { 125 | const extension = detectJsx(inCode) ? ".tsx" : ".ts"; 126 | const outFile = file.replace(/\.jsx?$/, extension); 127 | await _maybeMoveWithGit(inFile, outFile); 128 | fs.writeFileSync(outFile, outCode); 129 | break; 130 | } 131 | case "none": 132 | default: 133 | console.log(outCode); 134 | break; 135 | } 136 | 137 | if (program.deleteSource) { 138 | fs.unlinkSync(inFile); 139 | } 140 | } catch (e) { 141 | console.error(`error processing ${inFile}`); 142 | console.error(e); 143 | } 144 | } 145 | }; 146 | 147 | async function _maybeMoveWithGit(from, to) { 148 | try { 149 | const git = simpleGit({ baseDir: process.cwd() }); 150 | await git.mv(from, to); 151 | } catch (error) { 152 | fs.renameSync(from, to); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "ES2018", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */ 5 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 6 | // "lib": [], /* Specify library files to be included in the compilation. */ 7 | "allowJs": true, /* Allow javascript files to be compiled. */ 8 | // "checkJs": true, /* Report errors in .js files. */ 9 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 10 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 11 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 12 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 13 | // "outFile": "./", /* Concatenate and emit output to single file. */ 14 | // "outDir": "./", /* Redirect output structure to the directory. */ 15 | "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 16 | // "composite": true, /* Enable project compilation */ 17 | // "removeComments": true, /* Do not emit comments to output. */ 18 | "noEmit": true, /* Do not emit outputs. */ 19 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 20 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 21 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 22 | 23 | /* Strict Type-Checking Options */ 24 | // "strict": true, /* Enable all strict type-checking options. */ 25 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 26 | // "strictNullChecks": true, /* Enable strict null checks. */ 27 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 28 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 29 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 30 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 31 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 32 | 33 | /* Additional Checks */ 34 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 35 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 36 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 37 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 38 | 39 | /* Module Resolution Options */ 40 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 41 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 42 | "paths": { 43 | "@babel/*": [ 44 | "./babel/packages/babel-*/src" 45 | ] 46 | }, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 47 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 48 | // "typeRoots": [], /* List of folders to include type definitions from. */ 49 | // "types": [], /* Type declaration files to be included in compilation. */ 50 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 51 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 52 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 53 | "resolveJsonModule": true, 54 | 55 | /* Source Map Options */ 56 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 57 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 58 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 59 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 60 | 61 | /* Experimental Options */ 62 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 63 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 64 | }, 65 | "include": ["src", "babel/packages/babel-types"] 66 | } 67 | -------------------------------------------------------------------------------- /src/transforms/react-types.ts: -------------------------------------------------------------------------------- 1 | import * as t from "@babel/types"; 2 | 3 | export const GenericTypeAnnotation = { 4 | exit(path, state) { 5 | const { id: typeName, typeParameters } = path.node; 6 | 7 | if (typeName.name in UnqualifiedReactTypeNameMap) { 8 | // TODO: make sure that React was imported in this file 9 | return t.tsTypeReference( 10 | t.tsQualifiedName( 11 | t.identifier("React"), 12 | t.identifier(UnqualifiedReactTypeNameMap[typeName.name]) 13 | ), 14 | // TypeScript doesn't support empty type param lists 15 | typeParameters && typeParameters.params.length > 0 ? typeParameters : null 16 | ); 17 | } 18 | 19 | if (typeName.name === "React$Node") { 20 | // React$Node -> React.ReactNode 21 | return t.tsTypeReference( 22 | t.tsQualifiedName(t.identifier("React"), t.identifier("ReactNode")) 23 | ); 24 | } 25 | 26 | if (typeName.name === "React$Element") { 27 | // React$Element -> React.ReactElement, T> 28 | return t.tsTypeReference( 29 | t.tsQualifiedName(t.identifier("React"), t.identifier("ReactElement")), 30 | t.tsTypeParameterInstantiation([ 31 | // React.ComponentProps 32 | t.tsTypeReference( 33 | t.tsQualifiedName( 34 | t.identifier("React"), 35 | t.identifier("ComponentProps") 36 | ), 37 | t.tsTypeParameterInstantiation([typeParameters.params[0]]) 38 | ), 39 | typeParameters.params[0], 40 | ]) 41 | ); 42 | } 43 | 44 | if (typeName.name === "React$Component") { 45 | // React$Component -> React.Component 46 | return t.tsTypeReference( 47 | t.tsQualifiedName(t.identifier("React"), t.identifier("Component")), 48 | typeParameters 49 | ); 50 | } 51 | 52 | if (typeName.name === "React$ComponentType") { 53 | // React$ComponentType -> React.ComponentType 54 | return t.tsTypeReference( 55 | t.tsQualifiedName(t.identifier("React"), t.identifier("ComponentType")), 56 | typeParameters 57 | ); 58 | } 59 | 60 | if (typeName.name === "React$Context") { 61 | // React$Context -> React.Context 62 | return t.tsTypeReference( 63 | t.tsQualifiedName(t.identifier("React"), t.identifier("Context")), 64 | typeParameters 65 | ); 66 | } 67 | 68 | if (typeName.name === "React$Ref") { 69 | // React$Ref -> React.Ref 70 | return t.tsTypeReference( 71 | t.tsQualifiedName(t.identifier("React"), t.identifier("Ref")), 72 | typeParameters 73 | ); 74 | } 75 | 76 | if (typeName.name === "React$StatelessFunctionalComponent") { 77 | // React$StatelessFunctionalComponent -> React.FC 78 | return t.tsTypeReference( 79 | t.tsQualifiedName(t.identifier("React"), t.identifier("FC")), 80 | typeParameters 81 | ); 82 | } 83 | 84 | if (t.isTSQualifiedName(typeName)) { 85 | const { left, right } = typeName; 86 | 87 | // React.ElementConfig -> JSX.LibraryManagedAttributes> 88 | if ( 89 | t.isIdentifier(left, { name: "React" }) && 90 | t.isIdentifier(right, { name: "ElementConfig" }) 91 | ) { 92 | return t.tsTypeReference( 93 | t.tsQualifiedName( 94 | t.identifier("JSX"), 95 | t.identifier("LibraryManagedAttributes") 96 | ), 97 | t.tsTypeParameterInstantiation([ 98 | typeParameters.params[0], 99 | t.tsTypeReference( 100 | t.tsQualifiedName( 101 | t.identifier("React"), 102 | t.identifier("ComponentProps") 103 | ), 104 | t.tsTypeParameterInstantiation([typeParameters.params[0]]) 105 | ), 106 | ]) 107 | ); 108 | } 109 | } 110 | } 111 | }; 112 | 113 | // Mapping between React types for Flow and those for TypeScript. 114 | const UnqualifiedReactTypeNameMap = { 115 | SyntheticEvent: "SyntheticEvent", 116 | SyntheticAnimationEvent: "AnimationEvent", 117 | SyntheticClipboardEvent: "ClipboardEvent", 118 | SyntheticCompositionEvent: "CompositionEvent", 119 | SyntheticInputEvent: "SyntheticEvent", 120 | SyntheticUIEvent: "UIEvent", 121 | SyntheticFocusEvent: "FocusEvent", 122 | SyntheticKeyboardEvent: "KeyboardEvent", 123 | SyntheticMouseEvent: "MouseEvent", 124 | SyntheticDragEvent: "DragEvent", 125 | SyntheticWheelEvent: "WheelEvent", 126 | SyntheticPointerEvent: "PointerEvent", 127 | SyntheticTouchEvent: "TouchEvent", 128 | SyntheticTransitionEvent: "TransitionEvent", 129 | 130 | // React$ElementType takes no type params, but React.ElementType takes one 131 | // optional type param 132 | React$ElementType: "ElementType", 133 | }; 134 | 135 | export const QualifiedTypeIdentifier = { 136 | exit(path, state) { 137 | const { qualification, id } = path.node; 138 | const left = qualification; 139 | const right = id; 140 | 141 | if (left.name === "React" && right.name in QualifiedReactTypeNameMap) { 142 | return t.tsQualifiedName( 143 | left, 144 | t.identifier(QualifiedReactTypeNameMap[right.name]) 145 | ); 146 | } 147 | } 148 | }; 149 | 150 | // Only types with different names are included. 151 | const QualifiedReactTypeNameMap = { 152 | Node: "ReactNode", 153 | Text: "ReactText", 154 | Child: "ReactChild", 155 | Children: "ReactChildren", 156 | Element: "ReactElement", // 1:1 mapping is wrong, since ReactElement takes two type params 157 | Fragment: "ReactFragment", 158 | Portal: "ReactPortal", 159 | NodeArray: "ReactNodeArray", 160 | 161 | // TODO: private types, e.g. React$ElementType, React$Node, etc. 162 | 163 | // TODO: handle ComponentType, ElementConfig, ElementProps, etc. 164 | }; 165 | -------------------------------------------------------------------------------- /src/transforms/object-type.ts: -------------------------------------------------------------------------------- 1 | import * as t from "@babel/types"; 2 | import { trackComments } from "../util"; 3 | 4 | export const ObjectTypeAnnotation = { 5 | enter(path, state) { 6 | const { properties } = path.node; 7 | if (properties.length > 0) { 8 | // Workaround babylon bug where the last ObjectTypeProperty in an 9 | // ObjectTypeAnnotation doesn't have its trailingComments. 10 | // TODO: file a ticket for this bug 11 | const trailingComments = []; 12 | const lastProp = properties[properties.length - 1]; 13 | for (let i = lastProp.loc.end.line; i < path.node.loc.end.line; i++) { 14 | if (state.startLineToComments[i]) { 15 | trailingComments.push(state.startLineToComments[i]); 16 | } 17 | } 18 | lastProp.trailingComments = trailingComments; 19 | } 20 | }, 21 | exit(path) { 22 | const { exact, callProperties, properties, indexers } = path.node; // TODO: inexact 23 | 24 | if (exact) { 25 | console.warn("downgrading exact object type"); 26 | } 27 | 28 | // TODO: create multiple sets of elements so that we can convert 29 | // {x: number, ...T, y: number} to {x: number} & T & {y: number} 30 | const elements = []; 31 | const spreads = []; 32 | 33 | if (callProperties) { 34 | for (const prop of callProperties) { 35 | elements.push(prop); 36 | } 37 | } 38 | 39 | for (const prop of properties) { 40 | if (t.isObjectTypeSpreadProperty(prop)) { 41 | const { argument } = prop; 42 | spreads.push(argument); 43 | } else { 44 | elements.push(prop); 45 | } 46 | } 47 | 48 | // TODO: maintain the position of indexers 49 | indexers.forEach((indexer) => { 50 | const value = indexer.typeAnnotation.typeAnnotation; 51 | const key = indexer.parameters[0].typeAnnotation.typeAnnotation; 52 | if ( 53 | t.isTSSymbolKeyword(key) || 54 | t.isTSStringKeyword(key) || 55 | t.isTSNumberKeyword(key) || 56 | t.isTSTypeReference(key) 57 | ) { 58 | elements.push(indexer); 59 | } else { 60 | const name = indexer.parameters[0].name; 61 | // @ts-ignore 62 | const typeParameter = t.tsTypeParameter(key, undefined, name); 63 | 64 | const mappedType = { 65 | type: "TSMappedType", 66 | typeParameter: typeParameter, 67 | typeAnnotation: value, 68 | optional: true, 69 | }; 70 | 71 | spreads.push(mappedType); 72 | } 73 | }); 74 | 75 | // If there's only one property and it's an indexer convert the object 76 | // type to use Record, e.g. 77 | // {[string]: number} -> Record 78 | if ( 79 | spreads.length === 0 && 80 | elements.length === 1 && 81 | indexers.length === 1 82 | ) { 83 | const indexer = indexers[0]; 84 | const value = indexer.typeAnnotation.typeAnnotation; 85 | const key = indexer.parameters[0].typeAnnotation.typeAnnotation; 86 | 87 | const record = t.tsTypeReference( 88 | t.identifier("Record"), 89 | t.tsTypeParameterInstantiation([key, value]) 90 | ); 91 | 92 | if (indexer.readonly) { 93 | path.replaceWith( 94 | t.tsTypeReference( 95 | t.identifier("Readonly"), 96 | t.tsTypeParameterInstantiation([record]) 97 | ) 98 | ); 99 | } else { 100 | path.replaceWith(record); 101 | } 102 | 103 | return; 104 | } 105 | 106 | if (spreads.length > 0 && elements.length > 0) { 107 | path.replaceWith( 108 | t.tsIntersectionType([...spreads, t.tsTypeLiteral(elements)]) 109 | ); 110 | } else if (spreads.length > 0) { 111 | path.replaceWith(t.tsIntersectionType(spreads)); 112 | } else { 113 | const typeLiteral = t.tsTypeLiteral(elements); 114 | path.replaceWith(typeLiteral); 115 | } 116 | }, 117 | }; 118 | 119 | export const ObjectTypeCallProperty = { 120 | exit(path, state) { 121 | // NOTE: `value` has already been converted to a TSFunctionType 122 | const { value, leadingComments, trailingComments, loc } = path.node; 123 | const { typeParameters, parameters, typeAnnotation } = value; 124 | const replacement = t.tsCallSignatureDeclaration( 125 | typeParameters, 126 | parameters, 127 | typeAnnotation 128 | ); 129 | replacement.leadingComments = leadingComments; 130 | replacement.trailingComments = trailingComments; 131 | replacement.loc = loc; 132 | 133 | trackComments(replacement, state); 134 | 135 | path.replaceWith(replacement); 136 | }, 137 | }; 138 | 139 | export const ObjectTypeProperty = { 140 | exit(path, state) { 141 | const { 142 | key, 143 | value, 144 | optional, 145 | variance, 146 | kind, 147 | method, 148 | leadingComments, 149 | trailingComments, 150 | loc, 151 | } = path.node; // TODO: static, kind 152 | const typeAnnotation = t.tsTypeAnnotation(value); 153 | const initializer = undefined; // TODO: figure out when this used 154 | const computed = false; // TODO: maybe set this to true for indexers 155 | const readonly = variance && variance.kind === "plus"; 156 | 157 | if (variance && variance.kind === "minus") { 158 | // TODO: include file and location of infraction 159 | console.warn("typescript doesn't support writeonly properties"); 160 | } 161 | if (kind !== "init") { 162 | console.warn("we don't handle get() or set() yet, :P"); 163 | } 164 | 165 | if (method) { 166 | // TODO: assert value is a FunctionTypeAnnotation 167 | const methodSignature = { 168 | type: "TSMethodSignature", 169 | key, 170 | typeParameters: value.typeParameters, 171 | parameters: value.parameters, 172 | typeAnnotation: value.typeAnnotation, 173 | computed, 174 | optional, 175 | leadingComments, 176 | trailingComments, 177 | loc, 178 | }; 179 | 180 | trackComments(methodSignature, state); 181 | 182 | // TODO: patch @babel/types - tsMethodSignature ignores two out of the six params 183 | // const methodSignature = t.tsMethodSignature(key, value.typeParameters, value.parameters, value.typeAnnotation, computed, optional); 184 | path.replaceWith(methodSignature); 185 | } else { 186 | const propertySignature = { 187 | type: "TSPropertySignature", 188 | key, 189 | typeAnnotation, 190 | // initializer, 191 | computed, 192 | optional, 193 | readonly, 194 | leadingComments, 195 | trailingComments, 196 | loc, 197 | }; 198 | 199 | trackComments(propertySignature, state); 200 | 201 | // TODO: patch @babel/types - tsPropertySignature ignores typeAnnotation, optional, and readonly 202 | // const = propertySignature = t.tsPropertySignature(key, typeAnnotation, initializer, computed, optional, readonly), 203 | path.replaceWith(propertySignature); 204 | } 205 | }, 206 | }; 207 | 208 | export const ObjectTypeIndexer = { 209 | exit(path, state) { 210 | const { 211 | id, 212 | key, 213 | value, 214 | variance, 215 | leadingComments, 216 | trailingComments, 217 | loc, 218 | } = path.node; 219 | 220 | const readonly = variance && variance.kind === "plus"; 221 | if (variance && variance.kind === "minus") { 222 | // TODO: include file and location of infraction 223 | console.warn("typescript doesn't support writeonly properties"); 224 | } 225 | 226 | const identifier = { 227 | type: "Identifier", 228 | name: id ? id.name : "key", 229 | typeAnnotation: t.tsTypeAnnotation(key), 230 | }; 231 | // TODO: patch @babel/types - t.identifier omits typeAnnotation 232 | // const identifier = t.identifier(name.name, decorators, optional, t.tsTypeAnnotation(typeAnnotation)); 233 | 234 | const indexSignature = { 235 | type: "TSIndexSignature", 236 | parameters: [identifier], // TODO: figure when multiple parameters are used 237 | typeAnnotation: t.tsTypeAnnotation(value), 238 | readonly, 239 | leadingComments, 240 | trailingComments, 241 | loc, 242 | }; 243 | 244 | trackComments(indexSignature, state); 245 | 246 | // TODO: patch @babel/types - t.tsIndexSignature omits readonly 247 | // const indexSignature = t.tsIndexSignature([identifier], t.tsTypeAnnotation(value), readonly); 248 | path.replaceWith(indexSignature); 249 | }, 250 | }; 251 | -------------------------------------------------------------------------------- /playground/src/options-panel.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | 3 | // examples 4 | import basicTypes from "!!raw-loader!../examples/basic-types.js"; 5 | import functionTypes from "!!raw-loader!../examples/function-types.js"; 6 | import generics from "!!raw-loader!../examples/generics.js"; 7 | import imports from "!!raw-loader!../examples/imports.js"; 8 | import objectTypes from "!!raw-loader!../examples/object-types.js"; 9 | import utilityTypes from "!!raw-loader!../examples/utility-types.js"; 10 | 11 | const examples = { 12 | basicTypes, 13 | functionTypes, 14 | generics, 15 | imports, 16 | objectTypes, 17 | utilityTypes, 18 | }; 19 | 20 | export type Options = { 21 | prettier: boolean; 22 | prettierOptions: { 23 | semi: boolean; 24 | singleQuote: boolean; 25 | tabWidth: number; 26 | trailingComma: "all" | "es5" | "none"; 27 | bracketSpacing: boolean; 28 | arrowParens: "avoid" | "always"; 29 | printWidth: number; 30 | }; 31 | inlineUtilityTypes: boolean; 32 | }; 33 | 34 | type Props = { 35 | options: Options; 36 | onOptionsChange: (newOptions: Options) => unknown; 37 | onCodeChange: (newCode: string) => unknown; 38 | }; 39 | 40 | class OptionsPanel extends React.Component { 41 | constructor(props: Props) { 42 | super(props); 43 | } 44 | 45 | handleExampleChange = (e: React.ChangeEvent) => { 46 | const value = e.currentTarget.value; 47 | if (value in examples) { 48 | this.props.onCodeChange(examples[value]); 49 | } 50 | }; 51 | 52 | render() { 53 | const optionsPanel = { 54 | gridRow: "2 / span 3", 55 | padding: 8, 56 | backgroundColor: "#DDD", 57 | fontFamily: "sans-serif", 58 | color: "#333", 59 | display: "flex", 60 | flexDirection: "column", 61 | } as React.CSSProperties; 62 | 63 | const { options, onOptionsChange } = this.props; 64 | 65 | return ( 66 |
67 |
68 | Example: 69 |
70 | 79 | 80 |
Output Options
81 |
89 |
90 | 91 | { 96 | onOptionsChange({ 97 | ...options, 98 | prettier: e.currentTarget.checked, 99 | }); 100 | }} 101 | /> 102 |
103 | 106 | { 112 | onOptionsChange({ 113 | ...options, 114 | prettierOptions: { 115 | ...options.prettierOptions, 116 | semi: e.currentTarget.checked, 117 | }, 118 | }); 119 | }} 120 | /> 121 | 124 | { 130 | onOptionsChange({ 131 | ...options, 132 | prettierOptions: { 133 | ...options.prettierOptions, 134 | singleQuote: e.currentTarget.checked, 135 | }, 136 | }); 137 | }} 138 | /> 139 | 142 | { 148 | onOptionsChange({ 149 | ...options, 150 | prettierOptions: { 151 | ...options.prettierOptions, 152 | bracketSpacing: e.currentTarget.checked, 153 | }, 154 | }); 155 | }} 156 | /> 157 | 160 | 177 | 180 | 197 | 200 | 221 | 224 | { 230 | onOptionsChange({ 231 | ...options, 232 | prettierOptions: { 233 | ...options.prettierOptions, 234 | printWidth: Number(e.currentTarget.value), 235 | }, 236 | }); 237 | }} 238 | /> 239 |
240 | 241 | { 246 | onOptionsChange({ 247 | ...options, 248 | inlineUtilityTypes: e.currentTarget.checked, 249 | }); 250 | }} 251 | /> 252 | 253 |
254 | ); 255 | } 256 | } 257 | 258 | export default OptionsPanel; 259 | -------------------------------------------------------------------------------- /playground/src/app.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import "codemirror/lib/codemirror.css"; 3 | import "codemirror/theme/material.css"; 4 | import { Controlled as CodeMirror } from "react-codemirror2"; 5 | import "codemirror/mode/jsx/jsx.js"; 6 | import "codemirror/mode/javascript/javascript.js"; 7 | 8 | import { convert } from "../../src/convert"; 9 | import OptionsPanel, { Options } from "./options-panel"; 10 | import { maybeDecodeHash, encodeHash } from "./hash"; 11 | 12 | import smallLogo from "../images/GitHub-Mark-Light-32px.png"; 13 | import largeLogo from "../images/GitHub-Mark-Light-64px.png"; 14 | 15 | const initCode = `// @flow 16 | let a: number = 5; 17 | 18 | type Foo = { 19 | bar: string, 20 | baz: ?number, 21 | +qux: T, 22 | }; 23 | `; 24 | 25 | type Flow = { 26 | registerFile: (filename: string, contents: string) => void; 27 | setLibs: (libs: string[]) => void; 28 | }; 29 | 30 | // Copied from https://github.com/facebook/flow/blob/master/website/_assets/js/flow-loader.js.es6 31 | const TRY_LIB_CONTENTS = ` 32 | declare type $JSXIntrinsics = { 33 | [string]: { 34 | instance: any, 35 | props: { 36 | children?: React$Node, 37 | [key: string]: any, 38 | }, 39 | }, 40 | }; 41 | `.slice(1); 42 | 43 | type Props = {}; 44 | type State = { 45 | flowCode: string; 46 | tsCode: string; 47 | errors: string[]; 48 | focusedEditor: any; 49 | options: Options; 50 | scroll: { x: number; y: number }; 51 | }; 52 | 53 | const defaultOptions: Options = { 54 | prettier: true, 55 | prettierOptions: { 56 | semi: true, 57 | singleQuote: false, 58 | tabWidth: 4, 59 | trailingComma: "all", 60 | bracketSpacing: false, 61 | arrowParens: "avoid", 62 | printWidth: 80, 63 | }, 64 | inlineUtilityTypes: false, 65 | }; 66 | 67 | class App extends React.Component { 68 | flowEditor: any; 69 | tsEditor: any; 70 | flow: any; 71 | 72 | constructor(props: Props) { 73 | super(props); 74 | const { hash } = window.location; 75 | 76 | const data = maybeDecodeHash(hash); 77 | 78 | const flowCode = (data && data.code) || initCode; 79 | const options = (data && data.options) || defaultOptions; 80 | 81 | try { 82 | this.state = { 83 | flowCode, 84 | tsCode: convert(flowCode, options), 85 | errors: [], 86 | focusedEditor: null, 87 | options, 88 | scroll: { x: 0, y: 0 }, 89 | }; 90 | } catch (e) { 91 | this.state = { 92 | flowCode, 93 | tsCode: "", 94 | errors: [e.toString()], 95 | focusedEditor: null, 96 | options: defaultOptions, 97 | scroll: { x: 0, y: 0 }, 98 | }; 99 | } 100 | } 101 | 102 | componentDidUpdate(prevProps: Props, prevState: State) { 103 | if ( 104 | JSON.stringify(prevState.options) !== JSON.stringify(this.state.options) 105 | ) { 106 | const { flowCode } = this.state; 107 | // update the permalink regardless of whether conversion succeeds 108 | window.location.hash = encodeHash(flowCode, this.state.options); 109 | try { 110 | const tsCode = convert(flowCode, this.state.options); 111 | this.setState({ tsCode }); 112 | } catch (e) { 113 | this.setState({ errors: [e.toString()] }); 114 | console.log(e); 115 | } 116 | } 117 | } 118 | 119 | update(flowCode: string) { 120 | window.location.hash = encodeHash(flowCode, this.state.options); 121 | try { 122 | this.setState({ flowCode }); 123 | const tsCode = convert(flowCode, this.state.options); 124 | this.setState({ tsCode }); 125 | this.setState({ errors: [] }); 126 | } catch (e) { 127 | this.setState({ errors: [e.toString()] }); 128 | console.log(e); 129 | } 130 | } 131 | 132 | render() { 133 | const { errors } = this.state; 134 | 135 | const editorStyle = { 136 | position: "absolute", 137 | left: 0, 138 | top: 0, 139 | right: 0, 140 | bottom: 0, 141 | } as React.CSSProperties; 142 | 143 | const flowOverlayStyle = { 144 | position: "absolute", 145 | left: 0, 146 | right: 0, 147 | bottom: 0, 148 | pointerEvents: errors.length > 0 ? "" : "none", 149 | zIndex: 3, 150 | } as React.CSSProperties; 151 | 152 | const errorStyle = { 153 | backgroundColor: errors.length > 0 ? "rgba(255, 0, 0, 0.5)" : "", 154 | padding: 16, 155 | fontSize: 16, 156 | fontFamily: "sans-serif", 157 | }; 158 | 159 | const tsOverlayStyle = { 160 | position: "absolute", 161 | left: 0, 162 | top: 0, 163 | right: 0, 164 | bottom: 0, 165 | pointerEvents: "none", 166 | backgroundColor: errors.length > 0 ? "rgba(255, 255, 255, 0.5)" : "", 167 | } as React.CSSProperties; 168 | 169 | const headerStyle = { 170 | fontFamily: "sans-serif", 171 | margin: 0, 172 | fontSize: 24, 173 | fontWeight: 500, 174 | } as React.CSSProperties; 175 | 176 | const globalHeader = { 177 | backgroundColor: "#444", 178 | gridColumn: "1 / span 3", 179 | display: "flex", 180 | flexDirection: "row", 181 | justifyContent: "space-between", 182 | alignItems: "center", 183 | padding: 8, 184 | color: "white", 185 | } as React.CSSProperties; 186 | 187 | const tabStyle = { 188 | padding: "8px 16px 8px 16px", 189 | backgroundColor: "#FFF", 190 | fontFamily: "sans-serif", 191 | fontWeight: 300, 192 | } as React.CSSProperties; 193 | 194 | const tabContainerStyle = { 195 | backgroundColor: "#DDD", 196 | display: "flex", 197 | borderBottom: "solid 1px #DDD", 198 | } as React.CSSProperties; 199 | 200 | return ( 201 |
210 | 219 | this.setState({ options })} 222 | onCodeChange={(code) => this.update(code)} 223 | /> 224 |
225 |
232 | input.js 233 |
234 |
235 |
236 |
243 | output.ts [readonly] 244 |
245 |
246 |
253 | (this.flowEditor = editor)} 264 | onBeforeChange={(editor, data, value) => { 265 | this.update(value); 266 | }} 267 | onScroll={(editor, data) => { 268 | this.tsEditor.scrollTo(data.left, data.top); 269 | }} 270 | onFocus={(editor, event) => 271 | this.setState({ focusedEditor: editor }) 272 | } 273 | scroll={this.state.scroll} 274 | /> 275 |
276 | {errors.length > 0 && 277 | errors.map((error, index) => ( 278 |
279 | {error} 280 |
281 | ))} 282 |
283 |
284 |
291 | (this.tsEditor = editor)} 302 | onBeforeChange={(editor, data, value) => { 303 | // we only want changes to flow editor to update the ts editor 304 | }} 305 | onScroll={(editor, data) => { 306 | this.flowEditor && this.flowEditor.scrollTo(data.left, data.top); 307 | }} 308 | onFocus={(editor, event) => 309 | this.setState({ focusedEditor: editor }) 310 | } 311 | scroll={this.state.scroll} 312 | /> 313 |
314 |
315 |
316 | ); 317 | } 318 | } 319 | 320 | export default App; 321 | -------------------------------------------------------------------------------- /test/cli.test.ts: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import tmp from "tmp"; 3 | import fs from "fs"; 4 | import mockConsole from "jest-mock-console"; 5 | import * as mockProcess from "jest-mock-process"; 6 | import * as prettier from "prettier"; 7 | 8 | // jest.mock("prettier", () => ({ 9 | // __esModule: true, 10 | // resolveConfig: { 11 | // sync: jest.fn(), 12 | // }, 13 | // })); 14 | 15 | import { cli } from "../src/cli"; 16 | 17 | // cleanup temp dir automatically in case of an exception 18 | tmp.setGracefulCleanup(); 19 | 20 | describe("cli", () => { 21 | let tmpdir; 22 | let tmpobj; 23 | 24 | beforeEach(() => { 25 | // @ts-ignore 26 | tmpobj = tmp.dirSync(); 27 | tmpdir = tmpobj.name; 28 | }); 29 | 30 | afterEach(() => { 31 | // cleanup temp dir 32 | tmpobj.removeCallback(); 33 | }); 34 | 35 | it("should exit with code one when no files have been provided", async () => { 36 | // Arrange 37 | mockConsole(); 38 | const mockExit = mockProcess.mockProcessExit(); 39 | const mockStdout = mockProcess.mockProcessStdout(); 40 | 41 | // Act 42 | await cli(["node", path.join(__dirname, "../flow-to-ts.js")]); 43 | 44 | // Assert 45 | expect(mockExit).toHaveBeenCalledWith(1); 46 | mockExit.mockRestore(); 47 | mockStdout.mockRestore(); 48 | }); 49 | 50 | it("should console.log output", async () => { 51 | // Arrange 52 | mockConsole(); 53 | const inputPath = path.join(tmpdir, "test.js"); 54 | fs.writeFileSync(inputPath, "const a: number = 5;", "utf-8"); 55 | 56 | // Act 57 | await cli(["node", path.join(__dirname, "../flow-to-ts.js"), inputPath]); 58 | 59 | // Assert 60 | expect(console.log).toHaveBeenCalledWith("const a: number = 5;"); 61 | }); 62 | 63 | it("should not write a file", async () => { 64 | // Arrange 65 | mockConsole(); 66 | const inputPath = path.join(tmpdir, "test.js"); 67 | fs.writeFileSync(inputPath, "const a: number = 5;", "utf-8"); 68 | 69 | // Act 70 | await cli(["node", path.join(__dirname, "../flow-to-ts.js"), inputPath]); 71 | 72 | // Assert 73 | const outputPath = path.join(tmpdir, "test.ts"); 74 | expect(fs.existsSync(outputPath)).toBe(false); 75 | }); 76 | 77 | it("should error any files with errors", async () => { 78 | // Arrange 79 | mockConsole(); 80 | const inputPath = path.join(tmpdir, "test.js"); 81 | fs.writeFileSync(inputPath, "?", "utf-8"); 82 | 83 | // Act 84 | await cli(["node", path.join(__dirname, "../flow-to-ts.js"), inputPath]); 85 | 86 | // Assert 87 | expect(console.error).toHaveBeenCalledWith(`error processing ${inputPath}`); 88 | }); 89 | 90 | it("should write a file", async () => { 91 | // Arrange 92 | const inputPath = path.join(tmpdir, "test.js"); 93 | fs.writeFileSync(inputPath, "const a: number = 5;", "utf-8"); 94 | 95 | // Act 96 | await cli([ 97 | "node", 98 | path.join(__dirname, "../flow-to-ts.js"), 99 | "--write=new", 100 | inputPath, 101 | ]); 102 | 103 | // Assert 104 | expect(fs.existsSync(path.join(tmpdir, "test.ts"))).toBe(true); 105 | }); 106 | 107 | it("should write many files with a glob", async () => { 108 | // Arrange 109 | const inputGlob = path.join(tmpdir, "*.js"); 110 | fs.writeFileSync( 111 | path.join(tmpdir, "foo.js"), 112 | "const a: number = 5;", 113 | "utf-8" 114 | ); 115 | fs.writeFileSync( 116 | path.join(tmpdir, "bar.js"), 117 | "const b: boolean = true;", 118 | "utf-8" 119 | ); 120 | 121 | // Act 122 | await cli([ 123 | "node", 124 | path.join(__dirname, "../flow-to-ts.js"), 125 | "--write=new", 126 | inputGlob, 127 | ]); 128 | 129 | // Assert 130 | expect(fs.existsSync(path.join(tmpdir, "foo.ts"))).toBe(true); 131 | expect(fs.existsSync(path.join(tmpdir, "bar.ts"))).toBe(true); 132 | }); 133 | 134 | it("should delete the original file", async () => { 135 | // Arrange 136 | const inputPath = path.join(tmpdir, "test.js"); 137 | const outputPath = path.join(tmpdir, "test.ts"); 138 | fs.writeFileSync(inputPath, "const a: number = 5;", "utf-8"); 139 | 140 | // Act 141 | await cli([ 142 | "node", 143 | path.join(__dirname, "../flow-to-ts.js"), 144 | "--write=new", 145 | "--delete-source", 146 | inputPath, 147 | ]); 148 | 149 | // Assert 150 | expect(fs.existsSync(outputPath)).toBe(true); 151 | expect(fs.existsSync(inputPath)).toBe(false); 152 | }); 153 | 154 | it("should delete many original files", async () => { 155 | // Arrange 156 | const inputGlob = path.join(tmpdir, "*.js"); 157 | fs.writeFileSync( 158 | path.join(tmpdir, "foo.js"), 159 | "const a: number = 5;", 160 | "utf-8" 161 | ); 162 | fs.writeFileSync( 163 | path.join(tmpdir, "bar.js"), 164 | "const b: boolean = true;", 165 | "utf-8" 166 | ); 167 | 168 | // Act 169 | await cli([ 170 | "node", 171 | path.join(__dirname, "../flow-to-ts.js"), 172 | "--write=new", 173 | "--delete-source", 174 | inputGlob, 175 | ]); 176 | 177 | // Assert 178 | expect(fs.existsSync(path.join(tmpdir, "foo.ts"))).toBe(true); 179 | expect(fs.existsSync(path.join(tmpdir, "bar.ts"))).toBe(true); 180 | expect(fs.existsSync(path.join(tmpdir, "foo.js"))).toBe(false); 181 | expect(fs.existsSync(path.join(tmpdir, "bar.js"))).toBe(false); 182 | }); 183 | 184 | it("should convert jsx to tsx and delete many original files", async () => { 185 | // Arrange 186 | const inputGlob = path.join(tmpdir, "*.js?(x)"); 187 | fs.writeFileSync( 188 | path.join(tmpdir, "foo.js"), 189 | "const a: number = 5;", 190 | "utf-8" 191 | ); 192 | fs.writeFileSync( 193 | path.join(tmpdir, "bar.jsx"), 194 | "const b: React.Node =

hello

;", 195 | "utf-8" 196 | ); 197 | fs.writeFileSync( 198 | path.join(tmpdir, "baz.jsx"), 199 | "const c: boolean = false;", 200 | "utf-8" 201 | ); 202 | 203 | // Act 204 | await cli([ 205 | "node", 206 | path.join(__dirname, "../flow-to-ts.js"), 207 | "--write=new", 208 | "--delete-source", 209 | inputGlob, 210 | ]); 211 | 212 | // Assert 213 | expect(fs.existsSync(path.join(tmpdir, "foo.ts"))).toBe(true); 214 | expect(fs.existsSync(path.join(tmpdir, "bar.tsx"))).toBe(true); 215 | expect( 216 | fs.existsSync(path.join(tmpdir, "baz.ts")) // Uses .ts extension if no JSX syntax found 217 | ).toBe(true); 218 | expect(fs.existsSync(path.join(tmpdir, "foo.jsx"))).toBe(false); 219 | expect(fs.existsSync(path.join(tmpdir, "bar.jsx"))).toBe(false); 220 | expect(fs.existsSync(path.join(tmpdir, "baz.jsx"))).toBe(false); 221 | }); 222 | 223 | it("should write to the file", async () => { 224 | // Arrange 225 | const inputPath = path.join(tmpdir, "test.js"); 226 | const outputPath = path.join(tmpdir, "test.ts"); 227 | fs.writeFileSync(inputPath, "const a: number = 5;", "utf-8"); 228 | 229 | // Act 230 | await cli([ 231 | "node", 232 | path.join(__dirname, "../flow-to-ts.js"), 233 | "--write=new", 234 | inputPath, 235 | ]); 236 | 237 | // Assert 238 | const output = fs.readFileSync(outputPath, "utf-8"); 239 | expect(output).toBe("const a: number = 5;"); 240 | }); 241 | 242 | it("should not attempt to load the prettier config file", async () => { 243 | // Arrange 244 | mockConsole(); 245 | const inputPath = path.join(tmpdir, "test.js"); 246 | fs.writeFileSync(inputPath, "const a: number = 5;", "utf-8"); 247 | const syncSpy = jest.spyOn(prettier.resolveConfig, "sync"); 248 | 249 | // Act 250 | await cli(["node", path.join(__dirname, "../flow-to-ts.js"), inputPath]); 251 | 252 | // Assert 253 | expect(syncSpy).not.toHaveBeenCalled(); 254 | }); 255 | 256 | it("should attempt to load the prettier config file", async () => { 257 | // Arrange 258 | mockConsole(); 259 | const inputPath = path.join(tmpdir, "test.js"); 260 | fs.writeFileSync(inputPath, "const a: number = 5;", "utf-8"); 261 | const syncSpy = jest.spyOn(prettier.resolveConfig, "sync"); 262 | 263 | // Act 264 | await cli([ 265 | "node", 266 | path.join(__dirname, "../flow-to-ts.js"), 267 | "--prettier", 268 | inputPath, 269 | ]); 270 | 271 | // Assert 272 | expect(syncSpy).toHaveBeenCalled(); 273 | }); 274 | 275 | it("should exit with code one when parsing the prettier config fails", async () => { 276 | // Arrange 277 | mockConsole(); 278 | const mockExit = mockProcess.mockProcessExit(); 279 | const mockStdout = mockProcess.mockProcessStdout(); 280 | const inputPath = path.join(tmpdir, "test.js"); 281 | fs.writeFileSync(inputPath, "const a: number = 5;", "utf-8"); 282 | const syncSpy = jest.spyOn(prettier.resolveConfig, "sync"); 283 | syncSpy.mockImplementationOnce(() => { 284 | throw new Error(); 285 | }); 286 | 287 | // Act 288 | await cli([ 289 | "node", 290 | path.join(__dirname, "../flow-to-ts.js"), 291 | "--prettier", 292 | inputPath, 293 | ]); 294 | 295 | // Assert 296 | expect(mockExit).toHaveBeenCalledWith(1); 297 | mockExit.mockRestore(); 298 | mockStdout.mockRestore(); 299 | }); 300 | 301 | it("should use prettier options from file when a config file is found", async () => { 302 | // Arrange 303 | mockConsole(); 304 | const inputPath = path.join(tmpdir, "test.js"); 305 | fs.writeFileSync(inputPath, 'const a: string = "string";', "utf-8"); 306 | const prettierConfig = { 307 | singleQuote: true, 308 | }; 309 | const syncSpy = jest.spyOn(prettier.resolveConfig, "sync"); 310 | syncSpy.mockReturnValueOnce(prettierConfig); 311 | 312 | // Act 313 | await cli([ 314 | "node", 315 | path.join(__dirname, "../flow-to-ts.js"), 316 | "--prettier", 317 | inputPath, 318 | ]); 319 | 320 | // Assert 321 | expect(console.log).toHaveBeenCalledWith("const a: string = 'string';"); 322 | }); 323 | 324 | it("should use default prettier options when no config file is found", async () => { 325 | // Arrange 326 | mockConsole(); 327 | const inputPath = path.join(tmpdir, "test.js"); 328 | fs.writeFileSync(inputPath, 'const a: string = "string";', "utf-8"); 329 | const syncSpy = jest.spyOn(prettier.resolveConfig, "sync"); 330 | syncSpy.mockReturnValueOnce(null); 331 | 332 | // Act 333 | await cli([ 334 | "node", 335 | path.join(__dirname, "../flow-to-ts.js"), 336 | "--prettier", 337 | inputPath, 338 | ]); 339 | 340 | // Assert 341 | expect(console.log).toHaveBeenCalledWith('const a: string = "string"'); 342 | }); 343 | 344 | // TODO: add tests for option handling 345 | }); 346 | -------------------------------------------------------------------------------- /src/transform.ts: -------------------------------------------------------------------------------- 1 | import * as t from "@babel/types"; 2 | import { ImportSpecifier, ImportDeclaration } from "@babel/types"; 3 | 4 | import * as declare from "./transforms/declare"; 5 | import * as reactTypes from "./transforms/react-types"; 6 | import * as objectType from "./transforms/object-type"; 7 | import * as utilityTypes from "./transforms/utility-types"; 8 | 9 | import { trackComments, partition, returning } from "./util"; 10 | 11 | const locToString = (loc) => 12 | `${loc.start.line}:${loc.start.column}-${loc.end.line}:${loc.end.column}`; 13 | 14 | const stripSuffixFromImportSource = (path) => { 15 | // TODO: make this configurable so we can output .ts[x]? 16 | const src = /\.\.?\//.test(path.node.source.value) 17 | ? path.node.source.value.replace(/\.js[x]?$/, "") 18 | : path.node.source.value; 19 | path.node.source = t.stringLiteral(src); 20 | }; 21 | 22 | const transformFunction = (path) => { 23 | if (path.node.predicate) { 24 | console.warn(`removing %checks at ${locToString(path.node.predicate.loc)}`); 25 | delete path.node.predicate; 26 | } 27 | for (const param of path.node.params) { 28 | if (t.isAssignmentPattern(param)) { 29 | // @ts-ignore: Property 'optional' does not exist on type 'ObjectPattern'. 30 | param.left.optional = false; 31 | } 32 | } 33 | }; 34 | 35 | export const transform = { 36 | Program: { 37 | enter(path, state) { 38 | const { body } = path.node; 39 | 40 | for (let i = 0; i < body.length; i++) { 41 | const stmt = body[i]; 42 | 43 | // filter out @flow and $FlowIssue comments 44 | if (stmt.leadingComments) { 45 | stmt.leadingComments = stmt.leadingComments.filter((comment) => { 46 | const value = comment.value.trim(); 47 | return !(value.includes("@flow") || value.includes("$FlowIssue")); 48 | }); 49 | } 50 | if (stmt.trailingComments) { 51 | stmt.trailingComments = stmt.trailingComments.filter((comment) => { 52 | const value = comment.value.trim(); 53 | return !(value.includes("@flow") || value.includes("$FlowIssue")); 54 | }); 55 | } 56 | 57 | // TODO(#207): Handle error codes 58 | // - filter out [incompatible-exact] comments 59 | // - merge remaining comments 60 | // convert $FlowFixMe, $FlowIgnore, $FlowExpectedError comments 61 | if (stmt.leadingComments) { 62 | for (const comment of stmt.leadingComments) { 63 | comment.value = comment.value 64 | .replace(/\$(FlowFixMe|FlowExpectError)/g, "@ts-expect-error") 65 | .replace(/\$FlowIgnore/g, "@ts-ignore"); 66 | } 67 | } 68 | if (stmt.trailingComments) { 69 | for (const comment of stmt.trailingComments) { 70 | comment.value = comment.value 71 | .replace(/\$(FlowFixMe|FlowExpectError)/g, "@ts-expect-error") 72 | .replace(/\$FlowIgnore/g, "@ts-ignore"); 73 | } 74 | } 75 | } 76 | }, 77 | exit(path, state) { 78 | const { body } = path.node; 79 | if (state.usedUtilityTypes.size > 0) { 80 | const specifiers = [...state.usedUtilityTypes].map((name) => { 81 | const imported = t.identifier(name); 82 | const local = t.identifier(name); 83 | return t.importSpecifier(local, imported); 84 | }); 85 | const source = t.stringLiteral("utility-types"); 86 | const importDeclaration = t.importDeclaration(specifiers, source); 87 | path.node.body = [importDeclaration, ...path.node.body]; 88 | } 89 | }, 90 | }, 91 | 92 | // Basic Types 93 | StringTypeAnnotation(path) { 94 | path.replaceWith(t.tsStringKeyword()); 95 | }, 96 | BooleanTypeAnnotation(path) { 97 | path.replaceWith(t.tsBooleanKeyword()); 98 | }, 99 | NumberTypeAnnotation(path) { 100 | path.replaceWith(t.tsNumberKeyword()); 101 | }, 102 | AnyTypeAnnotation(path) { 103 | path.replaceWith(t.tsAnyKeyword()); 104 | }, 105 | VoidTypeAnnotation(path) { 106 | path.replaceWith(t.tsVoidKeyword()); 107 | }, 108 | MixedTypeAnnotation(path) { 109 | path.replaceWith(t.tsUnknownKeyword()); 110 | }, 111 | EmptyTypeAnnotation(path) { 112 | path.replaceWith(t.tsNeverKeyword()); 113 | }, 114 | ExistsTypeAnnotation(path) { 115 | console.warn("downgrading * to any"); 116 | path.replaceWith(t.tsAnyKeyword()); 117 | }, 118 | 119 | // Literals 120 | StringLiteralTypeAnnotation(path) { 121 | path.replaceWith(t.tsLiteralType(t.stringLiteral(path.node.value))); 122 | }, 123 | BooleanLiteralTypeAnnotation(path) { 124 | path.replaceWith(t.tsLiteralType(t.booleanLiteral(path.node.value))); 125 | }, 126 | NumberLiteralTypeAnnotation(path) { 127 | path.replaceWith(t.tsLiteralType(t.numericLiteral(path.node.value))); 128 | }, 129 | NullLiteralTypeAnnotation(path) { 130 | path.replaceWith(t.tsNullKeyword()); 131 | }, 132 | 133 | // It's okay to process these non-leaf nodes on enter() 134 | // since we're modifying them in a way doesn't affect 135 | // the processing of other nodes. 136 | FunctionDeclaration(path, state) { 137 | trackComments(path.node, state); 138 | 139 | transformFunction(path); 140 | }, 141 | FunctionExpression(path) { 142 | transformFunction(path); 143 | }, 144 | ArrowFunctionExpression(path) { 145 | transformFunction(path); 146 | }, 147 | 148 | VariableDeclaration(path, state) { 149 | trackComments(path.node, state); 150 | }, 151 | 152 | ObjectProperty(path, state) { 153 | trackComments(path.node, state); 154 | }, 155 | 156 | // Statements 157 | ExpressionStatement(path, state) { 158 | trackComments(path.node, state); 159 | }, 160 | BlockStatement(path, state) { 161 | trackComments(path.node, state); 162 | }, 163 | EmptyStatement(path, state) { 164 | trackComments(path.node, state); 165 | }, 166 | DebuggerStatement(path, state) { 167 | trackComments(path.node, state); 168 | }, 169 | WithStatement(path, state) { 170 | trackComments(path.node, state); 171 | }, 172 | ReturnStatement(path, state) { 173 | trackComments(path.node, state); 174 | }, 175 | LabeledStatement(path, state) { 176 | trackComments(path.node, state); 177 | }, 178 | BreakStatement(path, state) { 179 | trackComments(path.node, state); 180 | }, 181 | ContinueStatement(path, state) { 182 | trackComments(path.node, state); 183 | }, 184 | IfStatement(path, state) { 185 | trackComments(path.node, state); 186 | }, 187 | SwitchStatement(path, state) { 188 | trackComments(path.node, state); 189 | }, 190 | SwitchCase(path, state) { 191 | trackComments(path.node, state); 192 | }, 193 | ThrowStatement(path, state) { 194 | trackComments(path.node, state); 195 | }, 196 | TryStatement(path, state) { 197 | trackComments(path.node, state); 198 | }, 199 | CatchClause(path, state) { 200 | trackComments(path.node, state); 201 | }, 202 | WhileStatement(path, state) { 203 | trackComments(path.node, state); 204 | }, 205 | DoWhileStatement(path, state) { 206 | trackComments(path.node, state); 207 | }, 208 | ForStatement(path, state) { 209 | trackComments(path.node, state); 210 | }, 211 | ForInStatement(path, state) { 212 | trackComments(path.node, state); 213 | }, 214 | ForOfStatement(path, state) { 215 | trackComments(path.node, state); 216 | }, 217 | 218 | // Class children 219 | ClassMethod(path, state) { 220 | trackComments(path.node, state); 221 | }, 222 | ClassPrivateMethod(path, state) { 223 | trackComments(path.node, state); 224 | }, 225 | ClassProperty(path, state) { 226 | trackComments(path.node, state); 227 | 228 | const { node } = path; 229 | if (node.variance && node.variance.kind === "plus") { 230 | node.readonly = true; 231 | } 232 | delete node.variance; 233 | }, 234 | ClassPrivateProperty(path, state) { 235 | trackComments(path.node, state); 236 | 237 | // There's a @babel/generator bug such that the `readonly` modifier isn't 238 | // included in the output. 239 | const { node } = path; 240 | if (node.variance && node.variance.kind === "plus") { 241 | node.readonly = true; 242 | } 243 | delete node.variance; 244 | }, 245 | 246 | // All other non-leaf nodes must be processed on exit() 247 | TypeAnnotation: { 248 | exit(path) { 249 | const { typeAnnotation } = path.node; 250 | path.replaceWith(t.tsTypeAnnotation(typeAnnotation)); 251 | }, 252 | }, 253 | NullableTypeAnnotation: { 254 | exit(path) { 255 | const { typeAnnotation } = path.node; 256 | 257 | // conditionally unwrap TSTypeAnnotation nodes 258 | const unwrappedType = t.isTSTypeAnnotation(typeAnnotation) 259 | ? typeAnnotation.typeAnnotation 260 | : typeAnnotation; 261 | 262 | path.replaceWith( 263 | t.tsUnionType([ 264 | // conditionally wrap function types in parens 265 | t.isTSFunctionType(unwrappedType) 266 | ? t.tsParenthesizedType(unwrappedType) 267 | : unwrappedType, 268 | t.tsNullKeyword(), 269 | t.tsUndefinedKeyword(), 270 | ]) 271 | ); 272 | }, 273 | }, 274 | ArrayTypeAnnotation: { 275 | exit(path) { 276 | const { elementType } = path.node; 277 | path.replaceWith(t.tsArrayType(elementType)); 278 | }, 279 | }, 280 | TupleTypeAnnotation: { 281 | exit(path) { 282 | const { types } = path.node; 283 | const elementTypes = types; 284 | path.replaceWith(t.tsTupleType(elementTypes)); 285 | }, 286 | }, 287 | FunctionTypeAnnotation: { 288 | exit(path) { 289 | const { typeParameters, params, rest, returnType } = path.node; 290 | const parameters = params.map((param, index) => { 291 | if (param.name === "") { 292 | return { 293 | ...param, 294 | name: `arg${index}`, 295 | }; 296 | } else { 297 | return param; 298 | } 299 | }); 300 | if (rest) { 301 | const restElement = { 302 | type: "RestElement", 303 | argument: rest, 304 | decorators: [], // flow doesn't support decorators 305 | typeAnnotation: rest.typeAnnotation, 306 | }; 307 | // TODO: patch @babel/types - t.restElement omits typeAnnotation 308 | // const restElement = t.restElement(rest, [], rest.typeAnnotation); 309 | parameters.push(restElement); 310 | delete rest.typeAnnotation; 311 | } 312 | const typeAnnotation = t.tsTypeAnnotation(returnType); 313 | path.replaceWith( 314 | !path.parent || 315 | t.isUnionTypeAnnotation(path.parent) || 316 | t.isIntersectionTypeAnnotation(path.parent) || 317 | t.isArrayTypeAnnotation(path.parent) 318 | ? t.tsParenthesizedType( 319 | t.tsFunctionType(typeParameters, parameters, typeAnnotation) 320 | ) 321 | : t.tsFunctionType(typeParameters, parameters, typeAnnotation) 322 | ); 323 | }, 324 | }, 325 | FunctionTypeParam: { 326 | exit(path) { 327 | const { name, optional, typeAnnotation } = path.node; 328 | const decorators = []; // flow doesn't support decorators 329 | const identifier = { 330 | type: "Identifier", 331 | name: name ? name.name : "", 332 | optional, 333 | typeAnnotation: t.tsTypeAnnotation(typeAnnotation), 334 | }; 335 | // TODO: patch @babel/types - t.identifier omits typeAnnotation 336 | // const identifier = t.identifier(name.name, decorators, optional, t.tsTypeAnnotation(typeAnnotation)); 337 | path.replaceWith(identifier); 338 | }, 339 | }, 340 | TypeParameterInstantiation: { 341 | exit(path) { 342 | const { params } = path.node; 343 | path.replaceWith(t.tsTypeParameterInstantiation(params)); 344 | }, 345 | }, 346 | TypeParameterDeclaration: { 347 | exit(path) { 348 | const { params } = path.node; 349 | path.replaceWith(t.tsTypeParameterDeclaration(params)); 350 | }, 351 | }, 352 | TypeParameter: { 353 | exit(path) { 354 | const { name, variance, bound } = path.node; 355 | if (variance) { 356 | console.warn("TypeScript doesn't support variance on type parameters"); 357 | } 358 | const typeParameter = { 359 | type: "TSTypeParameter", 360 | constraint: bound && bound.typeAnnotation, 361 | default: path.node.default, 362 | name, 363 | }; 364 | // TODO: patch @babel/types - tsTypeParameter omits name 365 | // const typeParameter = t.tsTypeParameter(constraint, _default, name)); 366 | path.replaceWith(typeParameter); 367 | }, 368 | }, 369 | GenericTypeAnnotation: { 370 | exit(path, state) { 371 | const { id, typeParameters } = path.node; 372 | 373 | const typeName = id; 374 | // utility-types doesn't have a definition for $ReadOnlyArray 375 | // TODO: add one 376 | if (typeName.name === "$ReadOnlyArray") { 377 | typeName.name = "ReadonlyArray"; 378 | } 379 | 380 | if (typeName.name === "Function") { 381 | path.replaceWith( 382 | t.functionTypeAnnotation( 383 | null, // type parameters 384 | [], 385 | t.functionTypeParam( 386 | t.identifier("args"), 387 | t.genericTypeAnnotation( 388 | t.identifier("Array"), 389 | t.typeParameterInstantiation([t.anyTypeAnnotation()]) 390 | ) 391 | ), 392 | t.anyTypeAnnotation() 393 | ) 394 | ); 395 | return; 396 | } 397 | 398 | if (typeName.name === "Object") { 399 | path.replaceWith( 400 | t.objectTypeAnnotation( 401 | [], 402 | [ 403 | t.objectTypeIndexer( 404 | t.identifier("key"), 405 | t.stringTypeAnnotation(), 406 | t.anyTypeAnnotation() 407 | ), 408 | ] 409 | ) 410 | ); 411 | return; 412 | } 413 | 414 | let replacement; 415 | 416 | replacement = utilityTypes.GenericTypeAnnotation.exit(path, state); 417 | if (replacement) { 418 | path.replaceWith(replacement); 419 | return; 420 | } 421 | 422 | replacement = reactTypes.GenericTypeAnnotation.exit(path, state); 423 | if (replacement) { 424 | path.replaceWith(replacement); 425 | return; 426 | } 427 | 428 | // fallthrough case 429 | path.replaceWith(t.tsTypeReference(typeName, typeParameters)); 430 | }, 431 | }, 432 | QualifiedTypeIdentifier: { 433 | exit(path, state) { 434 | const { qualification, id } = path.node; 435 | const left = qualification; 436 | const right = id; 437 | 438 | const replacement = reactTypes.QualifiedTypeIdentifier.exit(path, state); 439 | if (replacement) { 440 | path.replaceWith(replacement); 441 | return; 442 | } 443 | 444 | // fallthrough case 445 | path.replaceWith(t.tsQualifiedName(left, right)); 446 | }, 447 | }, 448 | ObjectTypeCallProperty: objectType.ObjectTypeCallProperty, 449 | ObjectTypeProperty: objectType.ObjectTypeProperty, 450 | ObjectTypeIndexer: objectType.ObjectTypeIndexer, 451 | ObjectTypeAnnotation: objectType.ObjectTypeAnnotation, 452 | TypeAlias: { 453 | exit(path, state) { 454 | const { 455 | id, 456 | typeParameters, 457 | right, 458 | leadingComments, 459 | trailingComments, 460 | loc, 461 | } = path.node; 462 | 463 | const replacementNode = t.tsTypeAliasDeclaration( 464 | id, 465 | typeParameters, 466 | right 467 | ); 468 | replacementNode.leadingComments = leadingComments; 469 | replacementNode.trailingComments = trailingComments; 470 | replacementNode.loc = loc; 471 | 472 | trackComments(replacementNode, state); 473 | 474 | path.replaceWith(replacementNode); 475 | }, 476 | }, 477 | IntersectionTypeAnnotation: { 478 | exit(path) { 479 | const { types } = path.node; 480 | path.replaceWith(t.tsIntersectionType(types)); 481 | }, 482 | }, 483 | UnionTypeAnnotation: { 484 | exit(path) { 485 | const { types } = path.node; 486 | path.replaceWith(t.tsUnionType(types)); 487 | }, 488 | }, 489 | TypeofTypeAnnotation: { 490 | exit(path) { 491 | const { argument } = path.node; 492 | // argument has already been converted from GenericTypeAnnotation to 493 | // TSTypeReference. 494 | const exprName = argument.typeName; 495 | path.replaceWith(t.tsTypeQuery(exprName)); 496 | }, 497 | }, 498 | TypeCastExpression: { 499 | exit(path, state) { 500 | const { expression, typeAnnotation } = path.node; 501 | // TODO: figure out how to get this working with prettier and make it configurable 502 | // const typeCastExpression = { 503 | // type: "TSTypeCastExpression", 504 | // expression, 505 | // typeAnnotation, 506 | // }; 507 | // TODO: add tsTypeCastExpression to @babel/types 508 | // const typeCastExpression = t.tsTypeCastExpression(expression, typeAnnotation); 509 | const tsAsExpression = t.tsAsExpression( 510 | expression, 511 | typeAnnotation.typeAnnotation 512 | ); 513 | path.replaceWith(tsAsExpression); 514 | }, 515 | }, 516 | InterfaceDeclaration: { 517 | exit(path, state) { 518 | const { 519 | id, 520 | typeParameters, 521 | leadingComments, 522 | trailingComments, 523 | loc, 524 | } = path.node; // TODO: implements, mixins 525 | const body = t.tsInterfaceBody(path.node.body.members); 526 | const _extends = 527 | path.node.extends.length > 0 ? path.node.extends : undefined; 528 | const replacementNode = t.tsInterfaceDeclaration( 529 | id, 530 | typeParameters, 531 | _extends, 532 | body 533 | ); 534 | 535 | replacementNode.leadingComments = leadingComments; 536 | replacementNode.trailingComments = trailingComments; 537 | replacementNode.loc = loc; 538 | 539 | trackComments(replacementNode, state); 540 | 541 | path.replaceWith(replacementNode); 542 | }, 543 | }, 544 | InterfaceExtends: { 545 | exit(path) { 546 | const { id, typeParameters } = path.node; 547 | path.replaceWith(t.tsExpressionWithTypeArguments(id, typeParameters)); 548 | }, 549 | }, 550 | ClassImplements: { 551 | exit(path) { 552 | const { id, typeParameters } = path.node; 553 | path.replaceWith(t.tsExpressionWithTypeArguments(id, typeParameters)); 554 | }, 555 | }, 556 | ExportAllDeclaration: { 557 | exit(path, state) { 558 | trackComments(path.node, state); 559 | 560 | // TypeScript doesn't support `export type * from ...` 561 | path.node.exportKind = "value"; 562 | if (path.node.source) { 563 | stripSuffixFromImportSource(path); 564 | } 565 | }, 566 | }, 567 | ExportNamedDeclaration: { 568 | exit(path, state) { 569 | trackComments(path.node, state); 570 | 571 | if (path.node.source) { 572 | stripSuffixFromImportSource(path); 573 | } 574 | }, 575 | }, 576 | ImportDeclaration: { 577 | exit(path, state) { 578 | stripSuffixFromImportSource(path); 579 | let replacementNode = null; 580 | 581 | const { 582 | importKind, 583 | specifiers, 584 | source, 585 | leadingComments, 586 | trailingComments, 587 | loc, 588 | } = path.node as ImportDeclaration; 589 | 590 | if ( 591 | importKind === "typeof" && 592 | t.isImportDefaultSpecifier(specifiers[0]) 593 | ) { 594 | replacementNode = t.tsTypeAliasDeclaration( 595 | specifiers[0].local, 596 | undefined, 597 | t.tsTypeQuery(t.tsImportType(source, t.identifier("default"))) 598 | ); 599 | replacementNode.leadingComments = leadingComments; 600 | replacementNode.trailingComments = trailingComments; 601 | replacementNode.loc = loc; 602 | } else if (importKind === "value") { 603 | // find any "type" imports within the ImportSpecifier list 604 | // and pull them out to separate ImportDeclarations 605 | 606 | const [valSpecs, typeSpecs] = partition( 607 | specifiers, 608 | (s): s is ImportSpecifier => 609 | s.type === "ImportSpecifier" && s.importKind === "type" 610 | ); 611 | 612 | if (typeSpecs.length > 0) { 613 | const typeNode = t.importDeclaration( 614 | typeSpecs.map((s) => 615 | t.importSpecifier( 616 | returning(t.identifier(s.local.name), (n) => { 617 | n.loc = s.local.loc; 618 | }), 619 | returning( 620 | t.identifier( 621 | s.imported.type === "Identifier" 622 | ? s.imported.name 623 | : s.imported.value 624 | ), 625 | (n) => { 626 | n.loc = s.imported.loc; 627 | } 628 | ) 629 | ) 630 | ), 631 | source 632 | ); 633 | typeNode.leadingComments = leadingComments; 634 | typeNode.importKind = "type"; 635 | typeNode.loc = loc; 636 | 637 | const valNode = t.importDeclaration(valSpecs, source); 638 | valNode.trailingComments = trailingComments; 639 | valNode.loc = { 640 | start: { 641 | line: loc.start.line + 1, 642 | column: 0, 643 | }, 644 | end: { 645 | line: loc.start.line + 1, 646 | column: 0, 647 | }, 648 | }; 649 | 650 | replacementNode = [typeNode, valNode]; 651 | } 652 | } 653 | 654 | trackComments(replacementNode ?? path.node, state); 655 | 656 | if (replacementNode != null) { 657 | if (Array.isArray(replacementNode)) { 658 | path.replaceWithMultiple(replacementNode); 659 | } else { 660 | path.replaceWith(replacementNode); 661 | } 662 | } 663 | }, 664 | }, 665 | ImportSpecifier: { 666 | exit(path) { 667 | // TODO(#223): Handle "typeof" imports. 668 | if (path.node.importKind === "typeof") { 669 | path.node.importKind = "value"; 670 | } 671 | }, 672 | }, 673 | DeclareVariable: declare.DeclareVariable, 674 | DeclareClass: declare.DeclareClass, 675 | DeclareFunction: declare.DeclareFunction, 676 | DeclareExportDeclaration: declare.DeclareExportDeclaration, 677 | }; 678 | --------------------------------------------------------------------------------