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

identity transformation adds parens to IIFE #81

Closed
sophiebits opened this issue May 9, 2014 · 11 comments
Closed

identity transformation adds parens to IIFE #81

sophiebits opened this issue May 9, 2014 · 11 comments
Labels

Comments

@sophiebits
Copy link
Contributor

Given the source file

(function() {
}());

(with no parens around the function expression), recast outputs the file as

((function() {
})());

even if no changes are made (e.g., using examples/identity).

@sophiebits
Copy link
Contributor Author

Apparently because needsParens returns true for the function. I don't understand why needsParens checks:

    if (n.FunctionExpression.check(node) &&
        this.firstInStatement())
        return true;

@benjamn
Copy link
Owner

benjamn commented May 9, 2014

I believe this happens because the hasParens function is dumb and gets confused. The reprinter knows the function expression needs parentheses, but when it goes to check whether it already has parentheses, it gets the answer wrong, because the first character after the closing } is a (.

If this theory is true, then

(function() {
})();

should print without the extra layer of parentheses (because hasParens gets the answer right when you move the invoking parens outside the parentheses wrapping the whole expression).

@sophiebits
Copy link
Contributor Author

Yes, that already reprints correctly.

@benjamn
Copy link
Owner

benjamn commented May 9, 2014

For what it's worth, the function definitely does need parentheses, since the following code would be illegal:

function() {
}();

Also, NodePath.prototype.needsParens unfortunately only knows about the AST, so it's up to the Recast reprinter to second-guess needsParens when parentheses are already present in the surrounding code.

@sophiebits
Copy link
Contributor Author

(2 + function() {
}());

also doesn't add extra parens.

@benjamn
Copy link
Owner

benjamn commented May 9, 2014

That'll be because the function expression is no longer firstInStatement.

By the way, these explanations are descriptive (of how things work currently, which is broken) rather than normative (I'm not trying to justify the way things work!).

@benjamn benjamn added the bug label May 9, 2014
@sophiebits
Copy link
Contributor Author

Ahh, I see. firstInStatement isn't checking whether the expression is the first statement in the block but whether the function is the first thing in the statement. Sorry for my confusion.

@sophiebits
Copy link
Contributor Author

After thinking about this a little bit, I think needsParens is conflating two concepts that should be separate (precedence and function/{ at the beginning of a statement). In these cases the parens must always be exactly where they are:

(2 + x) * 3
(7).toString()

but with the expression

(function() {})()

the parens can be removed as long as there's another leading parenthesis to distinguish it from a statement.

@benjamn
Copy link
Owner

benjamn commented May 14, 2014

Some thoughts: in the failing case we're talking about, the FunctionExpression shares the same .loc.start position as the parent CallExpression node, though their .loc.end positions differ.

The hasParens function returns false for the FunctionExpression because it encounters characters involved in the CallExpression before it finds the closing parenthesis, but hasParens would return true if passed the CallExpression instead of the FunctionExpression.

I'm not yet sure what algorithm is suggested by this observation, but there's a possibility of salvaging hasParens if we're willing to walk the parent path in cases like this.

benjamn added a commit to benjamn/ast-types that referenced this issue May 14, 2014
As @spicyj pointed out in
benjamn/recast#81 (comment), the
NodePath.prototype.needsParens method has been conflating precedence logic
with garden-path parser lookahead quirks.

Rather than removing that edge case logic from the function, I have made
it possible to skip the canBeFirstInStatement/firstInStatement checks by
explicitly passing true to needsParens.

Since the behavior of needsParens differs only if client code passes true
for the assumeExpressionContext parameter, this change is completely
backwards-compatible and does not require a minor version bump.
@benjamn
Copy link
Owner

benjamn commented May 15, 2014

Just published v0.5.19 to NPM with a fix for this issue: https://www.npmjs.org/package/recast

Thanks for bringing my attention to this problem area, @spicyj!

@sophiebits
Copy link
Contributor Author

Thanks for fixing!

divanutq added a commit to divanutq/ast-types that referenced this issue Aug 1, 2024
As @spicyj pointed out in
benjamn/recast#81 (comment), the
NodePath.prototype.needsParens method has been conflating precedence logic
with garden-path parser lookahead quirks.

Rather than removing that edge case logic from the function, I have made
it possible to skip the canBeFirstInStatement/firstInStatement checks by
explicitly passing true to needsParens.

Since the behavior of needsParens differs only if client code passes true
for the assumeExpressionContext parameter, this change is completely
backwards-compatible and does not require a minor version bump.
citiranom2w added a commit to citiranom2w/ast-types that referenced this issue Aug 4, 2024
As @spicyj pointed out in
benjamn/recast#81 (comment), the
NodePath.prototype.needsParens method has been conflating precedence logic
with garden-path parser lookahead quirks.

Rather than removing that edge case logic from the function, I have made
it possible to skip the canBeFirstInStatement/firstInStatement checks by
explicitly passing true to needsParens.

Since the behavior of needsParens differs only if client code passes true
for the assumeExpressionContext parameter, this change is completely
backwards-compatible and does not require a minor version bump.
adriaticaz9 added a commit to adriaticaz9/ast-types that referenced this issue Aug 26, 2024
As @spicyj pointed out in
benjamn/recast#81 (comment), the
NodePath.prototype.needsParens method has been conflating precedence logic
with garden-path parser lookahead quirks.

Rather than removing that edge case logic from the function, I have made
it possible to skip the canBeFirstInStatement/firstInStatement checks by
explicitly passing true to needsParens.

Since the behavior of needsParens differs only if client code passes true
for the assumeExpressionContext parameter, this change is completely
backwards-compatible and does not require a minor version bump.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants