Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

flow: Fix printing of methods and static properties in declare class #1106

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 16 additions & 29 deletions lib/printer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1752,6 +1752,8 @@ function genericPrintNoParens(path: any, options: any, print: any) {
const parent = path.getParentNode(0);
const isArrowFunctionTypeAnnotation = !(
namedTypes.ObjectTypeCallProperty.check(parent) ||
// @ts-expect-error https://github.com/benjamn/ast-types/pull/755
(namedTypes.ObjectTypeProperty.check(parent) && parent.method) ||
(namedTypes.ObjectTypeInternalSlot.check(parent) && parent.method) ||
namedTypes.DeclareFunction.check(path.getParentNode(2))
);
Expand Down Expand Up @@ -1864,40 +1866,25 @@ function genericPrintNoParens(path: any, options: any, print: any) {
case "ObjectTypeCallProperty":
return path.call(print, "value");

case "ObjectTypeIndexer":
if (n.static) {
parts.push("static ");
}

parts.push(printVariance(path, print), "[");

if (n.id) {
parts.push(path.call(print, "id"), ": ");
}

parts.push(path.call(print, "key"), "]: ", path.call(print, "value"));

return concat(parts);

case "ObjectTypeProperty":
return concat([
printVariance(path, print),
path.call(print, "key"),
n.optional ? "?" : "",
": ",
path.call(print, "value"),
]);

case "ObjectTypeIndexer":
case "ObjectTypeInternalSlot":
return concat([
n.static ? "static " : "",
"[[",
path.call(print, "id"),
"]]",
parts.push(n.static ? "static " : "", printVariance(path, print));
if (n.type === "ObjectTypeProperty") {
parts.push(path.call(print, "key"));
} else if (n.type === "ObjectTypeIndexer") {
parts.push("[");
if (n.id) parts.push(path.call(print, "id"), ": ");
parts.push(path.call(print, "key"), "]");
} else {
parts.push("[[", path.call(print, "id"), "]]");
}
parts.push(
n.optional ? "?" : "",
n.value.type !== "FunctionTypeAnnotation" ? ": " : "",
path.call(print, "value"),
]);
);
return concat(parts);

case "QualifiedTypeIdentifier":
return concat([
Expand Down
99 changes: 55 additions & 44 deletions test/flow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,12 @@ describe("type syntax", function () {
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;
parseOptions = parseOptions || flowParserParseOptions;
const ast1 = parse(source, parseOptions);
const code = printer.printGenerically(ast1).code;
const ast2 = parse(code, parseOptions);
Expand All @@ -30,7 +27,7 @@ describe("type syntax", function () {
// Import type annotations
check("import type foo from 'foo';");
check("import typeof foo from 'foo';");
check("import { type foo } from 'foo';", flowParserParseOptions);
check("import { type foo } from 'foo';");

// Export type annotations
check("export type { foo };");
Expand Down Expand Up @@ -69,9 +66,8 @@ describe("type syntax", function () {
" optionalNumber?: number;" +
eol +
"};",
flowParserParseOptions,
);
check("type A = {| optionalNumber?: number |};", flowParserParseOptions);
check("type A = {| optionalNumber?: number |};");
check(
"type A = {|" +
eol +
Expand All @@ -80,18 +76,14 @@ describe("type syntax", function () {
" 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 = B;");
check("opaque type A = B.C;");
check("opaque type A = { optionalNumber?: number };");
check("opaque type A: X = B;");
check("opaque type A: X.Y = B.C;");
check(
"opaque type A: { stringProperty: string } = {" +
eol +
Expand All @@ -100,10 +92,9 @@ describe("type syntax", function () {
" 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>: X<T> = B<T>;");
check("opaque type A<T>: X.Y<T> = B.C<T>;");
check(
"opaque type A<T>: { optional?: T } = {" +
eol +
Expand All @@ -112,7 +103,6 @@ describe("type syntax", function () {
" optional?: T;" +
eol +
"};",
flowParserParseOptions,
);

// Generic
Expand Down Expand Up @@ -155,6 +145,14 @@ describe("type syntax", function () {
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 C { constructor(): void }");
check("declare class D { f(): D }");
check("declare class C { [number]: string }");
check("declare class C { [key: number]: string }");
check("declare class C { static make(): C }");
check("declare class C { static make: () => C }");
check("declare class C { static instance: C }");

check(
"declare module M {" +
eol +
Expand All @@ -163,16 +161,13 @@ describe("type syntax", function () {
"}",
);

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("declare opaque type A;");
check("declare opaque type A: X;");
check("declare opaque type A: X.Y;");
check("declare opaque type A: { stringProperty: string };");
check("declare opaque type A<T>: X<T>;");
check("declare opaque type A<T>: X.Y<T>;");
check("declare opaque type A<T>: { property: T };");

// Classes
check("class A {" + eol + " a: number;" + eol + "}");
Expand All @@ -194,7 +189,7 @@ describe("type syntax", function () {
check("class A<T: number> {}");

// Inexact object types
check("type InexactFoo = { foo: number; ... };", flowParserParseOptions);
check("type InexactFoo = { foo: number; ... };");
check(
[
"type MultiLineInexact = {",
Expand All @@ -203,26 +198,42 @@ describe("type syntax", function () {
" ...",
"};",
].join(eol),
flowParserParseOptions,
);

// Internal slots
check(
[
"declare class C {",
" [[myInternalSlot]]: any;",
" [[myOptionalInternalSlot]]?: any;",
" [[myMethodInternalSlot]](arg: any): any;",
" static [[myStaticInternalSlot]]: any;",
" static [[myStaticOptionalInternalSlot]]?: any;",
" static [[myStaticMethodInternalSlot]](arg: any): any;",
// Is there actually syntax for an optional method like this?
// Can't seem to find one that Flow's parser accepts.
// " static [[myStaticMethodOptionalInternalSlot]]?(arg: any): any;",
"}",
].join(eol),
);

// typeArguments
check("new A<string>();", flowParserParseOptions);
check("createPlugin<number>();", flowParserParseOptions);
check("new A<string>();");
check("createPlugin<number>();");

check("function myFunction([param1]: Params) {}", flowParserParseOptions);
check("function myFunction([param1]: Params) {}");
});

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);
check("type A = Obj?.['a'];");
check("type B = Array<string>?.[number];");
check("type C = Obj?.['bar']['baz'];");
check("type D = (Obj?.['bar'])['baz'];");
check("type E = Obj?.['bar'][];");
check("type F = Obj?.['bar'][boolean][];");
check("type G = Obj['bar']?.[boolean][];");
check("type H = (Obj?.['bar'])[string][];");
check("type I = Obj?.['bar']?.[string][];");

function checkEquiv(a: string, b: string) {
const aAst = parse(a, flowParserParseOptions);
Expand Down
140 changes: 0 additions & 140 deletions test/printer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1873,146 +1873,6 @@ describe("printer", function () {
assert.strictEqual(pretty, code);
});

it("prints flow object type internal slots correctly", function () {
const code = [
"type MyObjectType = {",
" [myIndexer: string]: any,",
" (myParameter: any): any,",
" (myOptionalParameter?: any): any,",
" (myParameterWithRest: any, ...rest: any[]): any,",
" [[myInternalSlot]]: any,",
" static [[myStaticOptionalInternalSlot]]?: (arg: any) => any,",
" static [[myStaticMethodOptionalInternalSlot]]?(arg: any): any,",
" myProperty: any,",
"};",
].join(eol);

const ast = b.program([
b.typeAlias(
b.identifier("MyObjectType"),
null,
b.objectTypeAnnotation.from({
properties: [
b.objectTypeProperty(
b.identifier("myProperty"),
b.anyTypeAnnotation(),
false,
),
],
indexers: [
b.objectTypeIndexer(
b.identifier("myIndexer"),
b.stringTypeAnnotation(),
b.anyTypeAnnotation(),
),
],
callProperties: [
b.objectTypeCallProperty(
b.functionTypeAnnotation(
[
b.functionTypeParam(
b.identifier("myParameter"),
b.anyTypeAnnotation(),
false,
),
],
b.anyTypeAnnotation(),
null,
null,
),
),
b.objectTypeCallProperty(
b.functionTypeAnnotation(
[
b.functionTypeParam(
b.identifier("myOptionalParameter"),
b.anyTypeAnnotation(),
true,
),
],
b.anyTypeAnnotation(),
null,
null,
),
),
b.objectTypeCallProperty(
b.functionTypeAnnotation(
[
b.functionTypeParam(
b.identifier("myParameterWithRest"),
b.anyTypeAnnotation(),
false,
),
],
b.anyTypeAnnotation(),
b.functionTypeParam(
b.identifier("rest"),
b.arrayTypeAnnotation(b.anyTypeAnnotation()),
false,
),
null,
),
),
],
internalSlots: [
b.objectTypeInternalSlot.from({
id: b.identifier("myInternalSlot"),
value: b.anyTypeAnnotation(),
static: false,
method: false,
optional: false,
}),
b.objectTypeInternalSlot.from({
id: b.identifier("myStaticOptionalInternalSlot"),
value: b.functionTypeAnnotation(
[
b.functionTypeParam(
b.identifier("arg"),
b.anyTypeAnnotation(),
false,
),
],
b.anyTypeAnnotation(),
null,
null,
),
static: true,
method: false,
optional: true,
}),
b.objectTypeInternalSlot.from({
id: b.identifier("myStaticMethodOptionalInternalSlot"),
value: b.functionTypeAnnotation(
[
b.functionTypeParam(
b.identifier("arg"),
b.anyTypeAnnotation(),
false,
),
],
b.anyTypeAnnotation(),
null,
null,
),
static: true,
method: true,
optional: true,
}),
],
}),
),
]);

const printer = new Printer({
tabWidth: 2,
wrapColumn: 40,
trailingComma: true,
});

const pretty = printer.printGenerically(ast).code;
assert.strictEqual(pretty, code);
});

it("prints flow type alias to function correctly", function () {
const code = ["type MyTypeAlias = (x?: ?number) => string;"].join(eol);

Expand Down