benjamn / recast

JavaScript syntax tree transformer, nondestructive pretty-printer, and automatic source map generator

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

ArrowFunctionExpression returning ObjectPattern prints without parens when a return type is removed

conartist6 opened this issue · comments

I've created a minimal repro for this behavior here: https://github.com/conartist6/recast-arrow-object-pattern-repro

The input is:

(): Type => ({ prop: true });

and the output is invalid, as it is missing parens:

() => { prop: true };

I made the initial PR to add support for node.extra.parenthesized as a way to declare parens, but I'm thinking I missed a case. I notice a few things.

First: fastPath.needsParens will always return false because node.extra.parenthesized.

Second: I implemented the logic to check node.extra.parenthesized and print parens in genericPrint. But the ObjectPattern node is not printed with genericPrint. It has a reprinter:

recast/lib/printer.ts

Lines 107 to 126 in cb7ff9a

const reprinter = getReprinter(path);
const lines = reprinter
? // Since the print function that we pass to the reprinter will
// be used to print "new" nodes, it's tempting to think we
// should pass printRootGenerically instead of print, to avoid
// calling maybeReprint again, but that would be a mistake
// because the new nodes might not be entirely new, but merely
// moved from elsewhere in the AST. The print function is the
// right choice because it gives us the opportunity to reprint
// such nodes using their original source.
reprinter(print)
: genericPrint(
path,
config,
options,
makePrintFunctionWith(options, {
includeComments: true,
avoidRootParens: false,
}),
);

Third: the patcher seems to rely only on fastPath.needsParens:

recast/lib/patcher.ts

Lines 234 to 236 in cb7ff9a

if (path.needsParens()) {
return linesModule.concat(["(", patchedLines, ")"]);
}

So I guess I understand how it's broken now. How should it work?

I clearly made a faulty assumption when I made the initial PR, that extra.parenthesized and ParenthesizedExpression could be handled exactly the same way, but they're not quite the same thing. One has its own node, and one does not.

While it is true that needsParens is always false for a ParenthesizedExpression because that node prints itself as a set of parens, I think for the implementation to be correct it cannot possibly return false for a node with extra.parenthesized. A node which has that flag does always need parens, and it's presence also must not override the following logic, which dictates whether a node needs parens for any other reason. What I've actually done is short-circuited the logic that would say that an ObjectPattern which is the body of an ArrowFunctionExpression must ALWAYS get parens. Oops!!!!!