Skip to content

Commit

Permalink
flow gen-flow-files
Browse files Browse the repository at this point in the history
Summary:
This adds a new command, `flow gen-flow-files path/to/file.js`, which generates the minimal `.js.flow` interface file that can be used for publishing compiled Flow source. Specifically: It generates only `import`, `declare`, and `declare export` statements in order to omit all the implementation text.

Background: Currently the best practice for publishing Flow source is (roughly):

1. `cp src/foo.js dist/foo.js.flow`
2. `babel src/foo.js > dist/foo.js`

This mostly works, but has a few downsides:

1. The published contents of `dist/*` are much larger than they need to be -- they contain a pre-compiled copy of all the original source. This is basically just crufty bytes that need to be downloaded every time the package is `npm install`-ed.
2. The published `.js.flow` files contain implementation details -- which are much more susceptible to breaking changes across Flow versions. While breaking changes across Flow version are still possible even in `declare` statements, this happens *far* less often.
3. We also want to re-use this type -> code codegen infrastructure to generate libdefs for flow-typed at some point. This particular diff is only about `.js.flow` files (i.e. no `declare module ...` statements) -- but that can be added on in a follow up diff.

This diff includes a new `codegen.ml` file which exposes an API intended for generating code from various things (types for now, possibly ASTs at some point if that's useful/performant/worth the effort). It's minimal in terms of feature-set right now but we can expand/improve it later. I didn't use `type_printer.ml` because it only handles some types and many of the types it will print aren't actual code or easily composable with other bits of code. It's also used by lots of stuff that I didn't really want to investigate breaking changes for while building out this feature.

At some point it probably makes sense to either improve `Type_printer` enough to subsume `Codegen` or the other way around. I suspect the `Codegen` is going to be easier to generalize, but we'll leave that for a later time to look into.

Closes #2184

Reviewed By: gabelevi

Differential Revision: D3663107

Pulled By: jeffmo

fbshipit-source-id: 8f1147590e8bf48ebedb7b6ea5d1b720669c7518
  • Loading branch information
jeffmo authored and Facebook Github Bot 8 committed Sep 2, 2016
1 parent b70fc3d commit 35494b3
Show file tree
Hide file tree
Showing 43 changed files with 1,470 additions and 30 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ MODULES=\
src/server\
src/services/autocomplete\
src/services/inference\
src/services/flowFileGen\
src/services/port\
src/stubs\
src/typing\
Expand Down
14 changes: 7 additions & 7 deletions hack/utils/tty.ml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ let style_num = function
| NormalWithBG (text, bg) -> (text_num text) ^ ";" ^ (background_num bg)
| BoldWithBG (text, bg) -> (text_num text) ^ ";" ^ (background_num bg) ^ ";1"

let print_one ?(color_mode=Color_Auto) c s =
let print_one ?(color_mode=Color_Auto) ?(out_channel=stdout) c s =
let should_color = match color_mode with
| Color_Always -> true
| Color_Never -> false
Expand All @@ -84,14 +84,14 @@ let print_one ?(color_mode=Color_Auto) c s =
Unix.isatty Unix.stdout && term <> "dumb"
end in
if should_color
then Printf.printf "\x1b[%sm%s\x1b[0m" (style_num c) (s)
else Printf.printf "%s" s
then Printf.fprintf out_channel "\x1b[%sm%s\x1b[0m" (style_num c) (s)
else Printf.fprintf out_channel "%s" s

let cprint ?(color_mode=Color_Auto) strs =
List.iter strs (fun (c, s) -> print_one ~color_mode c s)
let cprint ?(color_mode=Color_Auto) ?(out_channel=stdout) strs =
List.iter strs (fun (c, s) -> print_one ~color_mode ~out_channel c s)

let cprintf ?(color_mode=Color_Auto) c =
Printf.ksprintf (print_one ~color_mode c)
let cprintf ?(color_mode=Color_Auto) ?(out_channel=stdout) c =
Printf.ksprintf (print_one ~color_mode ~out_channel c)

let (spinner, spinner_used) =
let state = ref 0 in
Expand Down
8 changes: 4 additions & 4 deletions hack/utils/tty.mli
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ type color_mode =
| Color_Auto

(*
* Print a sequence of colorized strings to stdout, using ANSI color escapes
* codes.
* Print a sequence of colorized strings to stdout/stderr, using ANSI color
* escapes codes.
*)
val cprint : ?color_mode:color_mode -> (style * string) list -> unit
val cprintf : ?color_mode:color_mode -> style ->
val cprint : ?color_mode:color_mode -> ?out_channel:out_channel -> (style * string) list -> unit
val cprintf : ?color_mode:color_mode -> ?out_channel:out_channel -> style ->
('a, unit, string, unit) format4 -> 'a

(* These two functions provide a four-state TTY-friendly spinner that
Expand Down
8 changes: 8 additions & 0 deletions newtests/gen_flow_files_command/_flowconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[ignore]

[include]

[libs]

[options]
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe
8 changes: 8 additions & 0 deletions newtests/gen_flow_files_command/default_class_export.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// @flow

export class Base<A, B, C> {
};

export default class Child<A, B> extends Base<A, B, mixed> {
p: number
}
4 changes: 4 additions & 0 deletions newtests/gen_flow_files_command/default_function_exports.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// @flow

export default function<T: number, U: T, V: U> (a: V) { return a; }
export function mono(a: number, b: {c: number}) { return a + b.c; };
4 changes: 4 additions & 0 deletions newtests/gen_flow_files_command/default_variable_exports.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// @flow

export default 42;
export const str = "asdf";
5 changes: 5 additions & 0 deletions newtests/gen_flow_files_command/export_imported_type.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// @flow

import {Child} from "./named_class_exports";

export const a: Child<number, string> = new Child();
3 changes: 3 additions & 0 deletions newtests/gen_flow_files_command/exports_builtins.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// @flow

export function fn(a: Array<number>) {};
16 changes: 16 additions & 0 deletions newtests/gen_flow_files_command/literal_types.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// @flow

export var varBool = true;
export var varBoolLiteral: true = true;
export var varNum = 42;
export var varNumLiteral: 42 = 42;
export var varStr = "asdf";
export var varStrLiteral: "asdf" = "asdf";

export function f1(p: number) {
return "asdf";
};

export function f2(p: 42): "asdf" {
return "asdf";
};
29 changes: 29 additions & 0 deletions newtests/gen_flow_files_command/named_class_exports.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// @flow

export class Base<A, B, C> {
static baseStaticMethod(a: number, b: string) { return a; }
static overriddenStaticMethod(a: {b: number, c: number}) { return a.b + a.c; }

// Testing infinite type recursion
baseInst: Base<number, string, mixed>;

// Testing forward references
childInst: Child<string, number>;

baseMethod(a: number, b: string) { return a; }
overriddenMethod(a: {b: number, c: number}) { return a.b + a.c; }
};

export class Child<A, B> extends Base<A, B, mixed> {
static overriddenStaticMethod(a: {b: number}) { return a.b; }

notExported: NotExportedUsed<number>;
overriddenMethod(a: {b: number}) { return a.b; }
}

class NotExportedUsed<T> {
map<U>(f: (x:T) => U): NotExportedUsed<U> {
return new NotExportedUsed();
};
}
class NotExportedNotUsed {}
4 changes: 4 additions & 0 deletions newtests/gen_flow_files_command/named_function_exports.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// @flow

export function mono(a: number, b: {c: number}) { return a + b.c; };
export function poly<T: number, U: T, V: U> (a: V) { return a; }
4 changes: 4 additions & 0 deletions newtests/gen_flow_files_command/named_type_exports.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// @flow

export type T1 = number;
export type T2<U, V> = Array<U>;
13 changes: 13 additions & 0 deletions newtests/gen_flow_files_command/named_variable_exports.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// @flow

export const constExport = 42;
export let letExport = 43;
export var varExport = 44;

export type typeExport = number;

type UnexportedT = string;
export const unexportedAlias = ((0: any): UnexportedT);

class C {}
export const unexportedNominal = ((0: any): C);
1 change: 1 addition & 0 deletions newtests/gen_flow_files_command/non_flow_file.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export function addNum(a: number, b: number) { return a + b; }
13 changes: 13 additions & 0 deletions newtests/gen_flow_files_command/object_types.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// @flow

export var emptyObj = {};

export var singleProp = {p1: 42};
export var multiProp = {p1: 42, p2: 42};
export var nestedObject = {p1: {p2: 42}};

export var dict: {[key: string]: string} = {};
export var dictWithProps: {
p1: string,
[key: string]: number,
} = {p1: "asdf"};
7 changes: 7 additions & 0 deletions newtests/gen_flow_files_command/optional_types.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// @flow

var obj: {b?: number} = {b: 42};

export {obj};
export var optNum = obj.b;
export var optFunc = (p?: number) => p;
4 changes: 4 additions & 0 deletions newtests/gen_flow_files_command/suppressions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// @flow

// $FlowFixMe
export function fn() { return ('asdf': number); };
193 changes: 193 additions & 0 deletions newtests/gen_flow_files_command/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
/* @flow */


import {suite, test} from '../../tsrc/test/Tester';

export default suite(({addFile, flowCmd}) => [
test('named class exports', [
addFile('named_class_exports.js'),
flowCmd(['gen-flow-files', 'named_class_exports.js']).stdout(`
// @flow
declare class Class0 {
map<U>(f: (x: T) => U): Class0<U>;
}
declare export class Base<A, B, C> {
static baseStaticMethod(a: number, b: string): number;
static overriddenStaticMethod(a: {b: number, c: number}): number;
baseInst: Base<number, string, mixed>;
childInst: Child<string, number>;
baseMethod(a: number, b: string): number;
overriddenMethod(a: {b: number, c: number}): number;
}
declare export class Child<A, B> extends Base<A, B, mixed> {
static overriddenStaticMethod(a: {b: number}): number;
notExported: Class0<number>;
overriddenMethod(a: {b: number}): number;
}
`)
.stderr('')
]),

test('named variable exports', [
addFile('named_variable_exports.js'),
flowCmd(['gen-flow-files', 'named_variable_exports.js']).stderr('').stdout(`
// @flow
declare class Class0 {
}
declare export var constExport: number;
declare export var letExport: number;
export type typeExport = number;
declare export var unexportedAlias: string;
declare export var unexportedNominal: Class0;
declare export var varExport: number;
`)
]),

test('named function exports', [
addFile('named_function_exports.js'),
flowCmd(['gen-flow-files', 'named_function_exports.js']).stderr('').stdout(`
// @flow
declare export function mono(a: number, b: {c: number}): number;
declare export function poly<T: number, U: T, V: U>(a: V): number;
`)
]),

test('named type exports', [
addFile('named_type_exports.js'),
flowCmd(['gen-flow-files', 'named_type_exports.js']).stderr('').stdout(`
// @flow
export type T1 = number;
export type T2<U, V> = Array<U>;
declare module.exports: {};
`),
]),

test('default class exports', [
addFile('default_class_export.js'),
flowCmd(['gen-flow-files', 'default_class_export.js']).stderr('').stdout(`
// @flow
declare export class Base<A, B, C> {
}
declare export default class<A, B> extends Base<A, B, mixed> {
p: number;
}
`),
]),

test('default variable exports', [
addFile('default_variable_exports.js'),
flowCmd(['gen-flow-files', 'default_variable_exports.js']).stderr('').stdout(`
// @flow
declare export default number;
declare export var str: string;
`),
]),

test('default function exports', [
addFile('default_function_exports.js'),
flowCmd(['gen-flow-files', 'default_function_exports.js']).stderr('').stdout(`
// @flow
declare export default function<T: number, U: T, V: U>(a: V): number;
declare export function mono(a: number, b: {c: number}): number;
`),
]),

test('non-@flow files', [
addFile('non_flow_file.js'),
flowCmd(['gen-flow-files', '--strip-root', 'non_flow_file.js']).stderr('').stdout(`
// This file does not have an @flow at the top!
`),
]),

test('type errors halt and print to stderr', [
addFile('type_error.js'),
flowCmd(['gen-flow-files', 'type_error.js']).stdout('').stderr(`
type_error.js:3
3: export var a: string = 42;
^^ number. This type is incompatible with
3: export var a: string = 42;
^^^^^^ string
Found 1 error
In order to generate a shadow file there must be no type errors!
`)
]),

test('imported class types arent redefined', [
addFile('named_class_exports.js'),
addFile('export_imported_type.js'),
flowCmd(['gen-flow-files', 'export_imported_type.js']).stderr('').stdout(`
// @flow
import {Child} from "./named_class_exports";
declare export var a: Child<number, string>;
`)
]),

test('builtin class types arent redefined', [
addFile('exports_builtins.js'),
flowCmd(['gen-flow-files', 'exports_builtins.js']).stderr('').stdout(`
// @flow
declare export function fn(a: Array<number>): void;
`)
]),

test('suppressed type errors get normalized', [
addFile('suppressions.js'),
flowCmd(['gen-flow-files', 'suppressions.js']).stderr('').stdout(`
// @flow
declare export function fn(): number;
`)
]),

test('literal types respect polarity', [
addFile('literal_types.js'),
flowCmd(['gen-flow-files', 'literal_types.js']).stderr('').stdout(`
// @flow
declare export function f1(p: number): string;
declare export function f2(p: 42): "asdf";
declare export var varBool: boolean;
declare export var varBoolLiteral: true;
declare export var varNum: number;
declare export var varNumLiteral: 42;
declare export var varStr: string;
declare export var varStrLiteral: "asdf";
`)
]),

test('optional types', [
addFile('optional_types.js'),
flowCmd(['gen-flow-files', 'optional_types.js']).stderr('').stdout(`
// @flow
declare export var obj: {b?: number};
declare export function optFunc(p?: number): void | number;
declare export var optNum: void | number;
`)
]),

test('object types', [
addFile('object_types.js'),
flowCmd(['gen-flow-files', 'object_types.js']).stderr('').stdout(`
// @flow
declare export var dict: {[key: string]: string};
declare export var dictWithProps: {p1: string, [key: string]: number};
declare export var emptyObj: {};
declare export var multiProp: {p1: number, p2: number};
declare export var nestedObject: {p1: {p2: number}};
declare export var singleProp: {p1: number};
`)
]),
]);
3 changes: 3 additions & 0 deletions newtests/gen_flow_files_command/type_error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// @flow

export var a: string = 42;
Loading

0 comments on commit 35494b3

Please sign in to comment.