-
Notifications
You must be signed in to change notification settings - Fork 12.8k
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
Conversation
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; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
Certainly
Maybe it would be fine to flag whether a newline occured before the |
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. |
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. |
530f8f9
to
447ed96
Compare
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! |
@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 |
it doesn't necessarily make it easier. But it's much more consistent with how we do the rest of our nodes.
That sounds right to me. |
@@ -584,6 +584,9 @@ declare module "typescript" { | |||
name?: Identifier; | |||
body: Block | Expression; | |||
} | |||
interface ArrowFunctionExpression extends FunctionExpression { |
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
Sorry. That was a misreading on my part. The change looks great. Feel free to merge at any time. |
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, "=>"); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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();
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 |
👍 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; |
There was a problem hiding this comment.
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 end
s of arrowFunction.equalsGreaterThanToken,
since the full start and end are on the same line.
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 |
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. |
44766b4
to
25e3a95
Compare
Yes, @DanielRosenwasser is correct, those latter 3 should be valid. |
function checkGrammarArrowFunction(node: FunctionLikeDeclaration): boolean { | ||
if (node.kind === SyntaxKind.ArrowFunction) { | ||
var arrowFunction = <ArrowFunction>node; | ||
var sourceFile = getSourceFileOfNode(node); |
There was a problem hiding this comment.
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?
I've made one last suggestion. After dealing with that and a merge conflict, I think this should be good to go. |
Restores functionality broken in previous commit
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) || |
There was a problem hiding this comment.
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.
This looks great, thanks for the PR @caitp! |
Disallow line terminator after arrow function parameters, before =>
Closes #2282