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 () { // Import type annotations check("import type foo from 'foo';"); check("import typeof foo from 'foo';"); check("import { type foo } from 'foo';", flowParserParseOptions); // Export type annotations check("export type { foo };"); // Scalar type annotations 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;"); // Nullable check("var a: ?number;"); // Unions & Intersections check("var a: number | string | boolean = 26;"); check("var a: number & string & boolean = 26;"); // Types check("var a: A = 5;"); // TODO!? check("var a: typeof A;"); // Type aliases 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, ); // Opaque types 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: X = B;", flowParserParseOptions); check("opaque type A: X.Y = B.C;", flowParserParseOptions); check( "opaque type A: { optional?: T } = {" + eol + " stringProperty: string;" + eol + " optional?: T;" + eol + "};", flowParserParseOptions, ); // Generic check("var a: Array;"); check("var a: number[];"); check("var a: () => T;"); // Return types check("function a(): number {}"); check("var a: () => X = fn;"); // Object 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 + "};", ); // Casts check("(1 + 1: number);"); // Declare 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): void;"); check("declare class C { x: string }"); check("declare class A extends B { x: X }"); check( "declare class A extends B implements I, 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: X;", flowParserParseOptions); check("declare opaque type A: X.Y;", flowParserParseOptions); check("declare opaque type A: { property: T };", flowParserParseOptions); // Classes 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 + "}"); // Type parameters check("class A {}"); check("class A {}"); check("class A extends B {}"); check("function a(y: Y): T {}"); check("class A {" + eol + " foo(a: number): string {}" + eol + "}"); // Interfaces check("interface A extends B, C { a: number }"); check("class A extends B implements C, Y {}"); // Bounded polymorphism check("class A {}"); // Inexact object types check("type InexactFoo = { foo: number; ... };", flowParserParseOptions); check( [ "type MultiLineInexact = {", " reallyLongPropertyNameOyezOyezOyezFiddlyFeeDiDumDeDoo: VeryLongTypeName;", " somewhatShorterButStillNotVeryShortPropertyName: string;", " ...", "};", ].join(eol), flowParserParseOptions, ); // typeArguments check("new A();", flowParserParseOptions); check("createPlugin();", 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?.[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); } // Since FastPath#needsParens does not currently add any parentheses to // these expressions, make sure they do not matter for parsing the AST. checkEquiv( "type F = (Obj?.['bar'])?.[string][];", "type F = Obj?.['bar']?.[string][];", ); checkEquiv( "type F = (Obj['bar'])?.[string][];", "type F = Obj['bar']?.[string][];", ); }); });