Skip to content

Commit 3a25650

Browse files
committed
Fail spec parsing lambdas on parameter missing a =
Fail speculative parsing of arrow function expressions whenever it has a parameter with an initialiser that is missing '='. Ordinarily this is allowed for better error recovery in the language service, but for speculative parsing, the errors can compound. When the initialiser is an error, and when the '=>' is missing (which is also allowed), what is putatively an arrow function may actually be something else. For example, `(a / 8) + function () { }` is currently parsed as if someone had intended to write `(a = /8)+function()/) => { }` but they forgot the `=` of the initialiser, the `=>` of the lambda, forgot to close the regular expression, and mistakenly inserted a newline right after the regular expression.
1 parent 39ecfc0 commit 3a25650

File tree

7 files changed

+84
-20
lines changed

7 files changed

+84
-20
lines changed

src/compiler/parser.ts

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -2243,7 +2243,7 @@ namespace ts {
22432243
isStartOfType(/*inStartOfParameter*/ true);
22442244
}
22452245

2246-
function parseParameter(): ParameterDeclaration {
2246+
function parseParameter(requireEqualsToken?: boolean): ParameterDeclaration {
22472247
const node = <ParameterDeclaration>createNode(SyntaxKind.Parameter);
22482248
if (token() === SyntaxKind.ThisKeyword) {
22492249
node.name = createIdentifier(/*isIdentifier*/ true);
@@ -2272,19 +2272,11 @@ namespace ts {
22722272

22732273
node.questionToken = parseOptionalToken(SyntaxKind.QuestionToken);
22742274
node.type = parseParameterType();
2275-
node.initializer = parseBindingElementInitializer(/*inParameter*/ true);
2275+
node.initializer = parseInitializer(/*inParameter*/ true, requireEqualsToken);
22762276

22772277
return addJSDocComment(finishNode(node));
22782278
}
22792279

2280-
function parseBindingElementInitializer(inParameter: boolean) {
2281-
return inParameter ? parseParameterInitializer() : parseNonParameterInitializer();
2282-
}
2283-
2284-
function parseParameterInitializer() {
2285-
return parseInitializer(/*inParameter*/ true);
2286-
}
2287-
22882280
function fillSignature(
22892281
returnToken: SyntaxKind.ColonToken | SyntaxKind.EqualsGreaterThanToken,
22902282
flags: SignatureFlags,
@@ -2335,7 +2327,8 @@ namespace ts {
23352327
setYieldContext(!!(flags & SignatureFlags.Yield));
23362328
setAwaitContext(!!(flags & SignatureFlags.Await));
23372329

2338-
const result = parseDelimitedList(ParsingContext.Parameters, flags & SignatureFlags.JSDoc ? parseJSDocParameter : parseParameter);
2330+
const result = parseDelimitedList(ParsingContext.Parameters,
2331+
flags & SignatureFlags.JSDoc ? parseJSDocParameter : () => parseParameter(!!(flags & SignatureFlags.RequireCompleteParameterList)));
23392332

23402333
setYieldContext(savedYieldContext);
23412334
setAwaitContext(savedAwaitContext);
@@ -3016,7 +3009,7 @@ namespace ts {
30163009
return expr;
30173010
}
30183011

3019-
function parseInitializer(inParameter: boolean): Expression {
3012+
function parseInitializer(inParameter: boolean, requireEqualsToken?: boolean): Expression {
30203013
if (token() !== SyntaxKind.EqualsToken) {
30213014
// It's not uncommon during typing for the user to miss writing the '=' token. Check if
30223015
// there is no newline after the last token and if we're on an expression. If so, parse
@@ -3031,11 +3024,18 @@ namespace ts {
30313024
// do not try to parse initializer
30323025
return undefined;
30333026
}
3027+
if (inParameter && requireEqualsToken) {
3028+
// this occurs with speculative parsing of lambdas, so try to consume the initializer,
3029+
// but signal that the parameter was missing the equals sign so it can abort if it wants
3030+
parseAssignmentExpressionOrHigher();
3031+
const result = createNode(SyntaxKind.Identifier, scanner.getStartPos()) as Identifier;
3032+
result.escapedText = "= not found" as __String;
3033+
return result;
3034+
}
30343035
}
30353036

30363037
// Initializer[In, Yield] :
30373038
// = AssignmentExpression[?In, ?Yield]
3038-
30393039
parseExpected(SyntaxKind.EqualsToken);
30403040
return parseAssignmentExpressionOrHigher();
30413041
}
@@ -3351,8 +3351,7 @@ namespace ts {
33513351
function tryParseAsyncSimpleArrowFunctionExpression(): ArrowFunction | undefined {
33523352
// We do a check here so that we won't be doing unnecessarily call to "lookAhead"
33533353
if (token() === SyntaxKind.AsyncKeyword) {
3354-
const isUnParenthesizedAsyncArrowFunction = lookAhead(isUnParenthesizedAsyncArrowFunctionWorker);
3355-
if (isUnParenthesizedAsyncArrowFunction === Tristate.True) {
3354+
if (lookAhead(isUnParenthesizedAsyncArrowFunctionWorker) === Tristate.True) {
33563355
const asyncModifier = parseModifiersForArrowFunction();
33573356
const expr = parseBinaryExpressionOrHigher(/*precedence*/ 0);
33583357
return parseSimpleArrowFunctionExpression(<Identifier>expr, asyncModifier);
@@ -3386,7 +3385,6 @@ namespace ts {
33863385
const node = <ArrowFunction>createNode(SyntaxKind.ArrowFunction);
33873386
node.modifiers = parseModifiersForArrowFunction();
33883387
const isAsync = hasModifier(node, ModifierFlags.Async) ? SignatureFlags.Await : SignatureFlags.None;
3389-
33903388
// Arrow functions are never generators.
33913389
//
33923390
// If we're speculatively parsing a signature for a parenthesized arrow function, then
@@ -3409,7 +3407,8 @@ namespace ts {
34093407
// - "a ? (b): c" will have "(b):" parsed as a signature with a return type annotation.
34103408
//
34113409
// So we need just a bit of lookahead to ensure that it can only be a signature.
3412-
if (!allowAmbiguity && token() !== SyntaxKind.EqualsGreaterThanToken && token() !== SyntaxKind.OpenBraceToken) {
3410+
if (!allowAmbiguity && ((token() !== SyntaxKind.EqualsGreaterThanToken && token() !== SyntaxKind.OpenBraceToken) ||
3411+
find(node.parameters, p => p.initializer && ts.isIdentifier(p.initializer) && p.initializer.escapedText === "= not found"))) {
34133412
// Returning undefined here will cause our caller to rewind to where we started from.
34143413
return undefined;
34153414
}
@@ -5159,7 +5158,7 @@ namespace ts {
51595158
const node = <BindingElement>createNode(SyntaxKind.BindingElement);
51605159
node.dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken);
51615160
node.name = parseIdentifierOrPattern();
5162-
node.initializer = parseBindingElementInitializer(/*inParameter*/ false);
5161+
node.initializer = parseInitializer(/*inParameter*/ false);
51635162
return finishNode(node);
51645163
}
51655164

@@ -5176,7 +5175,7 @@ namespace ts {
51765175
node.propertyName = propertyName;
51775176
node.name = parseIdentifierOrPattern();
51785177
}
5179-
node.initializer = parseBindingElementInitializer(/*inParameter*/ false);
5178+
node.initializer = parseInitializer(/*inParameter*/ false);
51805179
return finishNode(node);
51815180
}
51825181

@@ -5215,7 +5214,7 @@ namespace ts {
52155214
node.name = parseIdentifierOrPattern();
52165215
node.type = parseTypeAnnotation();
52175216
if (!isInOrOfKeyword(token())) {
5218-
node.initializer = parseInitializer(/*inParameter*/ false);
5217+
node.initializer = parseNonParameterInitializer();
52195218
}
52205219
return finishNode(node);
52215220
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
=== tests/cases/conformance/parser/ecmascript5/ArrowFunctionExpressions/parserArrowFunctionExpression5.ts ===
2+
function foo(q: string, b: number) {
3+
>foo : Symbol(foo, Decl(parserArrowFunctionExpression5.ts, 0, 0))
4+
>q : Symbol(q, Decl(parserArrowFunctionExpression5.ts, 0, 13))
5+
>b : Symbol(b, Decl(parserArrowFunctionExpression5.ts, 0, 23))
6+
7+
return true ? (q ? true : false) : (b = q.length, function() { });
8+
>q : Symbol(q, Decl(parserArrowFunctionExpression5.ts, 0, 13))
9+
>b : Symbol(b, Decl(parserArrowFunctionExpression5.ts, 0, 23))
10+
>q.length : Symbol(String.length, Decl(lib.d.ts, --, --))
11+
>q : Symbol(q, Decl(parserArrowFunctionExpression5.ts, 0, 13))
12+
>length : Symbol(String.length, Decl(lib.d.ts, --, --))
13+
14+
};
15+
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
=== tests/cases/conformance/parser/ecmascript5/ArrowFunctionExpressions/parserArrowFunctionExpression5.ts ===
2+
function foo(q: string, b: number) {
3+
>foo : (q: string, b: number) => boolean | (() => void)
4+
>q : string
5+
>b : number
6+
7+
return true ? (q ? true : false) : (b = q.length, function() { });
8+
>true ? (q ? true : false) : (b = q.length, function() { }) : boolean | (() => void)
9+
>true : true
10+
>(q ? true : false) : boolean
11+
>q ? true : false : boolean
12+
>q : string
13+
>true : true
14+
>false : false
15+
>(b = q.length, function() { }) : () => void
16+
>b = q.length, function() { } : () => void
17+
>b = q.length : number
18+
>b : number
19+
>q.length : number
20+
>q : string
21+
>length : number
22+
>function() { } : () => void
23+
24+
};
25+
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
tests/cases/conformance/parser/ecmascript5/RegularExpressions/parserRegularExpressionDivideAmbiguity7.ts(1,2): error TS2304: Cannot find name 'a'.
2+
tests/cases/conformance/parser/ecmascript5/RegularExpressions/parserRegularExpressionDivideAmbiguity7.ts(2,3): error TS1005: ';' expected.
3+
4+
5+
==== tests/cases/conformance/parser/ecmascript5/RegularExpressions/parserRegularExpressionDivideAmbiguity7.ts (2 errors) ====
6+
(a/8
7+
~
8+
!!! error TS2304: Cannot find name 'a'.
9+
){}
10+
~
11+
!!! error TS1005: ';' expected.
12+
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
//// [parserRegularExpressionDivideAmbiguity7.ts]
2+
(a/8
3+
){}
4+
5+
6+
//// [parserRegularExpressionDivideAmbiguity7.js]
7+
(a / 8);
8+
{ }
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
function foo(q: string, b: number) {
2+
return true ? (q ? true : false) : (b = q.length, function() { });
3+
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
(a/8
2+
){}

0 commit comments

Comments
 (0)