Skip to content

Disallow line terminator after arrow function parameters, before => #2283

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

Merged
merged 10 commits into from
Mar 16, 2015

Conversation

caitp
Copy link
Contributor

@caitp caitp commented Mar 10, 2015

Closes #2282

@caitp
Copy link
Contributor Author

caitp commented Mar 10, 2015

quick attempt at a fix, some things are probably missing / test in wrong place / error messages not that helpful

@@ -3160,6 +3160,11 @@ module ts {
return undefined;
}

// Must be no line terminator before token `=>`.
if (scanner.hasPrecedingLineBreak()) {
return undefined;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, that was quick! I started a bit while I was in the office, but if you'd like to take this task on I wouldn't mind.

I actually had the idea of continuing to parse arrow functions exactly as before, but to report the error as part of our grammatical checking in checker.ts. Since => blah(blah, blahblah) is not a valid expression in any context anyway, this should be acceptable, and it's ideal for language service scenarios to gracefully recover as well as possible. For this reason, I'm not sure if checking for a line break here (and above) is the right thing to do.

However, the problem I ran into when I tried to fix this was that it's a real pain to work on the token-level in the checker. So I wonder if the benefit of the idea is actually worth the cost. When I discussed with @CyrusNajmabadi, he seemed to favor the idea, but I'd like him to weigh in.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct. I would not do this here. I would detect and parse out the arrow function, but then report later that it isn't valid. This will help ensure that all the higher layers of the stack properly understand what's going on.

@caitp
Copy link
Contributor Author

caitp commented Mar 10, 2015

Could we get a few more expression-bodied tests?

We should be testing different parsing contexts as well, so kill two birds with one stone:

Certainly

However, the problem I ran into when I tried to fix this was that it's a real pain to work on the token-level in the checker. So I wonder if the benefit of the idea is actually worth the cost.

Maybe it would be fine to flag whether a newline occured before the => token (in the node itself), and emit an error for that in the checker

@CyrusNajmabadi
Copy link
Contributor

I agree with @DanielRosenwasser We shoudl continue parsing these, but report an appropriate error (likely in the checker, when we do grammar checks) that the arrow function isn't legal.

@caitp
Copy link
Contributor Author

caitp commented Mar 10, 2015

I'm not sure how to do this "work with tokens" thing in the checker, the approach I've taken is to add a boolean field to a new interface, and flag the error during parsing, but only report it during checking. It seemed like the simplest approach.

@caitp caitp force-pushed the issue-2282 branch 2 times, most recently from 530f8f9 to 447ed96 Compare March 10, 2015 21:24
@CyrusNajmabadi
Copy link
Contributor

Just add the equalsGreaterThanToken to the ArrowFunction interface. You can see how we parse tokens into the tree with helpers like parseExpectedToken. (If we don't have an ArrowFunction interface, then make one). Thanks!

@caitp
Copy link
Contributor Author

caitp commented Mar 10, 2015

@CyrusNajmabadi how does the presence of the node in the interface make it easier to figure out if there was a line terminator or not? It looks like it could produce a better error message though (or at least the position of the error would be better). From a glance, I'd have to use getLineAndCharacterOfPosition(), and compare the computed line positions of the end of the parameters with the beginning of the arrow token. That seems like more work than is really needed. But, why not

@CyrusNajmabadi
Copy link
Contributor

it doesn't necessarily make it easier. But it's much more consistent with how we do the rest of our nodes.

From a glance, I'd have to use getLineAndCharacterOfPosition(), and compare the computed line positions of the end of the parameters with the beginning of the arrow token. That seems like more work than is really needed. But, why not

That sounds right to me.

@@ -584,6 +584,9 @@ declare module "typescript" {
name?: Identifier;
body: Block | Expression;
}
interface ArrowFunctionExpression extends FunctionExpression {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should just be extending from Expression and FunctionLikeDeclaration (not a FunctionExpression since it's not a PrimaryExpression nor does it have a name).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@CyrusNajmabadi
Copy link
Contributor

Sorry. That was a misreading on my part. The change looks great. Feel free to merge at any time.

@caitp
Copy link
Contributor Author

caitp commented Mar 11, 2015

hmm, I think it was slightly wrong though... the bottom branch won't ever be taken because a node is always created. I'll fix that.

// have an opening brace, just in case we're in an error state.
if (parseExpected(SyntaxKind.EqualsGreaterThanToken) || token === SyntaxKind.OpenBraceToken) {
arrowFunction.equalsGreaterThanToken = parseExpectedToken(SyntaxKind.EqualsGreaterThanToken, false, Diagnostics._0_expected, "=>");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parseExpectedToken will always return a token of type EqualsGreaterThanToken. So the below check will always be true. I think what you want is:

if (token === SyntaxKind.EqualsGreaterThanToken || token === SyntaxKind.OpenBraceToken)) {
    node.equalsGreaterThanToken = parseExpectedToken(...);
    node.body = parseArrowFunctionExpressionBody();
}
else {
    node.equalsGreaterThanToken = parseExpectedToken(...);
    node.body = parseIdentifier();
}

This will ensure the same behavior as before, and will ensure that we get appropriate error messages in the absence of a => token.

Also, add a /*namedParameter:*/ for the 'false' parameter.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or

var lastToken = token;
node.equalsGreaterThanToken = parseExpectedToken(...);
node.body = lastToken === SyntaxKind.EqualsGreaterThanToken || lastToken === SyntaxKind.OpenBraceToken
    ? parseArrowFunctionExpressionBody()
    : parseIdentifier();

@caitp
Copy link
Contributor Author

caitp commented Mar 11, 2015

done (I preferred the second approach). Sorry about the amended last commit, but it was a very minor change to use ternary instead of an if statement

@CyrusNajmabadi
Copy link
Contributor

👍 Awesome job. Thanks!

var arrowFunction = <ArrowFunction>node;
var sourceFile = getSourceFileOfNode(node);
var equalsGreaterThanLine = getLineAndCharacterOfPosition(sourceFile, getTokenPosOfNode(arrowFunction.equalsGreaterThanToken, sourceFile)).line;
var parametersLine = getLineAndCharacterOfPosition(sourceFile, arrowFunction.parameters.end).line;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Swap parametersLine and equalsGreaterThanLine parameters; also use the ends of arrowFunction.equalsGreaterThanToken, since the full start and end are on the same line.

@DanielRosenwasser
Copy link
Member

Glad I realized it before signing off, but this still has some issues. We'll inappropriately give you an error in the following (I add the first just to iterate on):

// Should not be valid.
var func = (a: number): number
    => a;

// Should be valid.
var funk = (a: number
    ) => a;

// Should be valid.
var funkier = (a: number)
    : number => a;

// Should be valid.
var funkiest = (a: number):
    number => a;

At least, I think the latter 3 should be valid. @JsonFreeman and @CyrusNajmabadi can weigh in. Let's add that to the tests.

As for a fix, this is a bit of a hack - check whether or not the full start of the arrow (just .start) is on the same line as its end (.end).

@caitp
Copy link
Contributor Author

caitp commented Mar 13, 2015

you're right, I didn't consider the case with a type annotation for the return value at all (although I think the second case should have worked, since I was using the "end" position for the parameters).

Anyways, yeah, the suggested fix solves that, and the extra tests with return value types are good.

@caitp caitp force-pushed the issue-2282 branch 2 times, most recently from 44766b4 to 25e3a95 Compare March 13, 2015 06:34
@JsonFreeman
Copy link
Contributor

Yes, @DanielRosenwasser is correct, those latter 3 should be valid. funk is allowed per the ES6 grammar, and the other two can be allowed by our grammar if we put No LineTerminator Here after the type annotation.

function checkGrammarArrowFunction(node: FunctionLikeDeclaration): boolean {
if (node.kind === SyntaxKind.ArrowFunction) {
var arrowFunction = <ArrowFunction>node;
var sourceFile = getSourceFileOfNode(node);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually recall one of the other functions needing the sourceFile as well. Could we grab the sourceFile once in checkGrammarFunctionLikeDeclaration and pass it in as a parameter to these checkers?

@DanielRosenwasser
Copy link
Member

I've made one last suggestion. After dealing with that and a merge conflict, I think this should be good to go.

@caitp
Copy link
Contributor Author

caitp commented Mar 15, 2015

done

@@ -95,6 +95,7 @@ module ts {
visitNodes(cbNodes, (<FunctionLikeDeclaration>node).typeParameters) ||
visitNodes(cbNodes, (<FunctionLikeDeclaration>node).parameters) ||
visitNode(cbNode, (<FunctionLikeDeclaration>node).type) ||
visitNode(cbNode, (<ArrowFunction>node).equalsGreaterThanToken) ||
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is weird, but I don't mind it terribly.

@DanielRosenwasser
Copy link
Member

This looks great, thanks for the PR @caitp!

DanielRosenwasser added a commit that referenced this pull request Mar 16, 2015
Disallow line terminator after arrow function parameters, before =>
@DanielRosenwasser DanielRosenwasser merged commit ad98fad into microsoft:master Mar 16, 2015
@microsoft microsoft locked and limited conversation to collaborators Jun 18, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Arrow functions require no line terminator before arrow => token
5 participants