import assert from "assert";
import { parse } from "../lib/parser";
import { Printer } from "../lib/printer";
import * as types from "ast-types";
import { EOL as eol } from "os";
describe("type syntax", function () {
const printer = new Printer({
tabWidth: 2,
quote: "single",
flowObjectCommas: false,
});
const esprimaParserParseOptions = {
parser: require("esprima-fb"),
};
const flowParserParseOptions = {
parser: require("flow-parser"),
};
function check(source: string, parseOptions?: any) {
parseOptions = parseOptions || esprimaParserParseOptions;
const ast1 = parse(source, parseOptions);
const code = printer.printGenerically(ast1).code;
const ast2 = parse(code, parseOptions);
types.astNodesAreEquivalent.assert(ast1, ast2);
assert.strictEqual(source, code);
}
it("should parse and print type annotations correctly", function () {
check("import type foo from 'foo';");
check("import typeof foo from 'foo';");
check("import { type foo } from 'foo';", flowParserParseOptions);
check("export type { foo };");
check("var a: number;");
check("var a: number = 5;");
check("var a: any;");
check("var a: boolean;");
check("var a: string;");
check("var a: 'foo';");
check("var a: void;");
check("var a: ?number;");
check("var a: number | string | boolean = 26;");
check("var a: number & string & boolean = 26;");
check("var a: A = 5;");
check("var a: typeof A;");
check("type A = B;");
check("type A = B.C;");
check("type A = { optionalNumber?: number };");
check(
"type A = {" +
eol +
" ...B;" +
eol +
" optionalNumber?: number;" +
eol +
"};",
flowParserParseOptions,
);
check("type A = {| optionalNumber?: number |};", flowParserParseOptions);
check(
"type A = {|" +
eol +
" ...B;" +
eol +
" optionalNumber?: number;" +
eol +
"|};",
flowParserParseOptions,
);
check("opaque type A = B;", flowParserParseOptions);
check("opaque type A = B.C;", flowParserParseOptions);
check(
"opaque type A = { optionalNumber?: number };",
flowParserParseOptions,
);
check("opaque type A: X = B;", flowParserParseOptions);
check("opaque type A: X.Y = B.C;", flowParserParseOptions);
check(
"opaque type A: { stringProperty: string } = {" +
eol +
" stringProperty: string;" +
eol +
" optionalNumber?: number;" +
eol +
"};",
flowParserParseOptions,
);
check("opaque type A<T>: X<T> = B<T>;", flowParserParseOptions);
check("opaque type A<T>: X.Y<T> = B.C<T>;", flowParserParseOptions);
check(
"opaque type A<T>: { optional?: T } = {" +
eol +
" stringProperty: string;" +
eol +
" optional?: T;" +
eol +
"};",
flowParserParseOptions,
);
check("var a: Array<Foo>;");
check("var a: number[];");
check("var a: <T>() => T;");
check("function a(): number {}");
check("var a: () => X = fn;");
check(
"var a: {" + eol + " b: number;" + eol + " x: { y: A };" + eol + "};",
);
check("var b: { [key: string]: number };");
check("var c: { (): number };");
check(
"var d: {" +
eol +
" [key: string]: A;" +
eol +
" [key: number]: B;" +
eol +
" (): C;" +
eol +
" a: D;" +
eol +
"};",
);
check("(1 + 1: number);");
check("declare var A: string;");
check("declare function foo(c: C): void;");
check("declare function foo(c: C, b: B): void;");
check("declare function foo(c: (e: Event) => void, b: B): void;");
check("declare function foo(c: C, d?: Array<D>): void;");
check("declare class C { x: string }");
check("declare class A<X> extends B<X[]> { x: X }");
check(
"declare class A extends B implements I<string>, J {}",
flowParserParseOptions,
);
check(
"declare module M {" +
eol +
" declare function foo(c: C): void;" +
eol +
"}",
);
check("declare opaque type A;", flowParserParseOptions);
check("declare opaque type A: X;", flowParserParseOptions);
check("declare opaque type A: X.Y;", flowParserParseOptions);
check(
"declare opaque type A: { stringProperty: string };",
flowParserParseOptions,
);
check("declare opaque type A<T>: X<T>;", flowParserParseOptions);
check("declare opaque type A<T>: X.Y<T>;", flowParserParseOptions);
check("declare opaque type A<T>: { property: T };", flowParserParseOptions);
check("class A {" + eol + " a: number;" + eol + "}");
check("class A {" + eol + " foo(a: number): string {}" + eol + "}");
check("class A {" + eol + " static foo(a: number): string {}" + eol + "}");
check("class A<T> {}");
check("class A<X, Y> {}");
check("class A<X> extends B<Y> {}");
check("function a<T>(y: Y<T>): T {}");
check("class A {" + eol + " foo<T>(a: number): string {}" + eol + "}");
check("interface A<X> extends B<A>, C { a: number }");
check("class A extends B implements C<T>, Y {}");
check("class A<T: number> {}");
check("type InexactFoo = { foo: number; ... };", flowParserParseOptions);
check(
[
"type MultiLineInexact = {",
" reallyLongPropertyNameOyezOyezOyezFiddlyFeeDiDumDeDoo: VeryLongTypeName<With, Generic, Type, Parameters>;",
" somewhatShorterButStillNotVeryShortPropertyName: string;",
" ...",
"};",
].join(eol),
flowParserParseOptions,
);
check("new A<string>();", flowParserParseOptions);
check("createPlugin<number>();", flowParserParseOptions);
check("function myFunction([param1]: Params) {}", flowParserParseOptions);
});
it("can pretty-print [Optional]IndexedAccessType AST nodes", () => {
check("type A = Obj?.['a'];", flowParserParseOptions);
check("type B = Array<string>?.[number];", flowParserParseOptions);
check("type C = Obj?.['bar']['baz'];", flowParserParseOptions);
check("type D = (Obj?.['bar'])['baz'];", flowParserParseOptions);
check("type E = Obj?.['bar'][];", flowParserParseOptions);
check("type F = Obj?.['bar'][boolean][];", flowParserParseOptions);
check("type G = Obj['bar']?.[boolean][];", flowParserParseOptions);
check("type H = (Obj?.['bar'])[string][];", flowParserParseOptions);
check("type I = Obj?.['bar']?.[string][];", flowParserParseOptions);
function checkEquiv(a: string, b: string) {
const aAst = parse(a, flowParserParseOptions);
const bAst = parse(b, flowParserParseOptions);
types.astNodesAreEquivalent.assert(aAst, bAst);
}
checkEquiv(
"type F = (Obj?.['bar'])?.[string][];",
"type F = Obj?.['bar']?.[string][];",
);
checkEquiv(
"type F = (Obj['bar'])?.[string][];",
"type F = Obj['bar']?.[string][];",
);
});
});