mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-16 07:13:45 -05:00
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.
This commit is contained in:
@@ -2240,7 +2240,7 @@ namespace ts {
|
||||
isStartOfType(/*inStartOfParameter*/ true);
|
||||
}
|
||||
|
||||
function parseParameter(): ParameterDeclaration {
|
||||
function parseParameter(requireEqualsToken?: boolean): ParameterDeclaration {
|
||||
const node = <ParameterDeclaration>createNode(SyntaxKind.Parameter);
|
||||
if (token() === SyntaxKind.ThisKeyword) {
|
||||
node.name = createIdentifier(/*isIdentifier*/ true);
|
||||
@@ -2269,19 +2269,11 @@ namespace ts {
|
||||
|
||||
node.questionToken = parseOptionalToken(SyntaxKind.QuestionToken);
|
||||
node.type = parseParameterType();
|
||||
node.initializer = parseBindingElementInitializer(/*inParameter*/ true);
|
||||
node.initializer = parseInitializer(/*inParameter*/ true, requireEqualsToken);
|
||||
|
||||
return addJSDocComment(finishNode(node));
|
||||
}
|
||||
|
||||
function parseBindingElementInitializer(inParameter: boolean) {
|
||||
return inParameter ? parseParameterInitializer() : parseNonParameterInitializer();
|
||||
}
|
||||
|
||||
function parseParameterInitializer() {
|
||||
return parseInitializer(/*inParameter*/ true);
|
||||
}
|
||||
|
||||
function fillSignature(
|
||||
returnToken: SyntaxKind.ColonToken | SyntaxKind.EqualsGreaterThanToken,
|
||||
flags: SignatureFlags,
|
||||
@@ -2334,7 +2326,8 @@ namespace ts {
|
||||
setYieldContext(!!(flags & SignatureFlags.Yield));
|
||||
setAwaitContext(!!(flags & SignatureFlags.Await));
|
||||
|
||||
const result = parseDelimitedList(ParsingContext.Parameters, flags & SignatureFlags.JSDoc ? parseJSDocParameter : parseParameter);
|
||||
const result = parseDelimitedList(ParsingContext.Parameters,
|
||||
flags & SignatureFlags.JSDoc ? parseJSDocParameter : () => parseParameter(!!(flags & SignatureFlags.RequireCompleteParameterList)));
|
||||
|
||||
setYieldContext(savedYieldContext);
|
||||
setAwaitContext(savedAwaitContext);
|
||||
@@ -3017,7 +3010,7 @@ namespace ts {
|
||||
return expr;
|
||||
}
|
||||
|
||||
function parseInitializer(inParameter: boolean): Expression {
|
||||
function parseInitializer(inParameter: boolean, requireEqualsToken?: boolean): Expression {
|
||||
if (token() !== SyntaxKind.EqualsToken) {
|
||||
// It's not uncommon during typing for the user to miss writing the '=' token. Check if
|
||||
// there is no newline after the last token and if we're on an expression. If so, parse
|
||||
@@ -3032,11 +3025,18 @@ namespace ts {
|
||||
// do not try to parse initializer
|
||||
return undefined;
|
||||
}
|
||||
if (inParameter && requireEqualsToken) {
|
||||
// this occurs with speculative parsing of lambdas, so try to consume the initializer,
|
||||
// but signal that the parameter was missing the equals sign so it can abort if it wants
|
||||
parseAssignmentExpressionOrHigher();
|
||||
const result = createNode(SyntaxKind.Identifier, scanner.getStartPos()) as Identifier;
|
||||
result.escapedText = "= not found" as __String;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Initializer[In, Yield] :
|
||||
// = AssignmentExpression[?In, ?Yield]
|
||||
|
||||
parseExpected(SyntaxKind.EqualsToken);
|
||||
return parseAssignmentExpressionOrHigher();
|
||||
}
|
||||
@@ -3351,8 +3351,7 @@ namespace ts {
|
||||
function tryParseAsyncSimpleArrowFunctionExpression(): ArrowFunction | undefined {
|
||||
// We do a check here so that we won't be doing unnecessarily call to "lookAhead"
|
||||
if (token() === SyntaxKind.AsyncKeyword) {
|
||||
const isUnParenthesizedAsyncArrowFunction = lookAhead(isUnParenthesizedAsyncArrowFunctionWorker);
|
||||
if (isUnParenthesizedAsyncArrowFunction === Tristate.True) {
|
||||
if (lookAhead(isUnParenthesizedAsyncArrowFunctionWorker) === Tristate.True) {
|
||||
const asyncModifier = parseModifiersForArrowFunction();
|
||||
const expr = parseBinaryExpressionOrHigher(/*precedence*/ 0);
|
||||
return parseSimpleArrowFunctionExpression(<Identifier>expr, asyncModifier);
|
||||
@@ -3386,7 +3385,6 @@ namespace ts {
|
||||
const node = <ArrowFunction>createNode(SyntaxKind.ArrowFunction);
|
||||
node.modifiers = parseModifiersForArrowFunction();
|
||||
const isAsync = hasModifier(node, ModifierFlags.Async) ? SignatureFlags.Await : SignatureFlags.None;
|
||||
|
||||
// Arrow functions are never generators.
|
||||
//
|
||||
// If we're speculatively parsing a signature for a parenthesized arrow function, then
|
||||
@@ -3409,7 +3407,8 @@ namespace ts {
|
||||
// - "a ? (b): c" will have "(b):" parsed as a signature with a return type annotation.
|
||||
//
|
||||
// So we need just a bit of lookahead to ensure that it can only be a signature.
|
||||
if (!allowAmbiguity && token() !== SyntaxKind.EqualsGreaterThanToken && token() !== SyntaxKind.OpenBraceToken) {
|
||||
if (!allowAmbiguity && ((token() !== SyntaxKind.EqualsGreaterThanToken && token() !== SyntaxKind.OpenBraceToken) ||
|
||||
find(node.parameters, p => p.initializer && ts.isIdentifier(p.initializer) && p.initializer.escapedText === "= not found"))) {
|
||||
// Returning undefined here will cause our caller to rewind to where we started from.
|
||||
return undefined;
|
||||
}
|
||||
@@ -5158,7 +5157,7 @@ namespace ts {
|
||||
const node = <BindingElement>createNode(SyntaxKind.BindingElement);
|
||||
node.dotDotDotToken = parseOptionalToken(SyntaxKind.DotDotDotToken);
|
||||
node.name = parseIdentifierOrPattern();
|
||||
node.initializer = parseBindingElementInitializer(/*inParameter*/ false);
|
||||
node.initializer = parseInitializer(/*inParameter*/ false);
|
||||
return finishNode(node);
|
||||
}
|
||||
|
||||
@@ -5175,7 +5174,7 @@ namespace ts {
|
||||
node.propertyName = propertyName;
|
||||
node.name = parseIdentifierOrPattern();
|
||||
}
|
||||
node.initializer = parseBindingElementInitializer(/*inParameter*/ false);
|
||||
node.initializer = parseInitializer(/*inParameter*/ false);
|
||||
return finishNode(node);
|
||||
}
|
||||
|
||||
@@ -5214,7 +5213,7 @@ namespace ts {
|
||||
node.name = parseIdentifierOrPattern();
|
||||
node.type = parseTypeAnnotation();
|
||||
if (!isInOrOfKeyword(token())) {
|
||||
node.initializer = parseInitializer(/*inParameter*/ false);
|
||||
node.initializer = parseNonParameterInitializer();
|
||||
}
|
||||
return finishNode(node);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
=== tests/cases/conformance/parser/ecmascript5/ArrowFunctionExpressions/parserArrowFunctionExpression5.ts ===
|
||||
function foo(q: string, b: number) {
|
||||
>foo : Symbol(foo, Decl(parserArrowFunctionExpression5.ts, 0, 0))
|
||||
>q : Symbol(q, Decl(parserArrowFunctionExpression5.ts, 0, 13))
|
||||
>b : Symbol(b, Decl(parserArrowFunctionExpression5.ts, 0, 23))
|
||||
|
||||
return true ? (q ? true : false) : (b = q.length, function() { });
|
||||
>q : Symbol(q, Decl(parserArrowFunctionExpression5.ts, 0, 13))
|
||||
>b : Symbol(b, Decl(parserArrowFunctionExpression5.ts, 0, 23))
|
||||
>q.length : Symbol(String.length, Decl(lib.d.ts, --, --))
|
||||
>q : Symbol(q, Decl(parserArrowFunctionExpression5.ts, 0, 13))
|
||||
>length : Symbol(String.length, Decl(lib.d.ts, --, --))
|
||||
|
||||
};
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
=== tests/cases/conformance/parser/ecmascript5/ArrowFunctionExpressions/parserArrowFunctionExpression5.ts ===
|
||||
function foo(q: string, b: number) {
|
||||
>foo : (q: string, b: number) => boolean | (() => void)
|
||||
>q : string
|
||||
>b : number
|
||||
|
||||
return true ? (q ? true : false) : (b = q.length, function() { });
|
||||
>true ? (q ? true : false) : (b = q.length, function() { }) : boolean | (() => void)
|
||||
>true : true
|
||||
>(q ? true : false) : boolean
|
||||
>q ? true : false : boolean
|
||||
>q : string
|
||||
>true : true
|
||||
>false : false
|
||||
>(b = q.length, function() { }) : () => void
|
||||
>b = q.length, function() { } : () => void
|
||||
>b = q.length : number
|
||||
>b : number
|
||||
>q.length : number
|
||||
>q : string
|
||||
>length : number
|
||||
>function() { } : () => void
|
||||
|
||||
};
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
tests/cases/conformance/parser/ecmascript5/RegularExpressions/parserRegularExpressionDivideAmbiguity7.ts(1,2): error TS2304: Cannot find name 'a'.
|
||||
tests/cases/conformance/parser/ecmascript5/RegularExpressions/parserRegularExpressionDivideAmbiguity7.ts(2,3): error TS1005: ';' expected.
|
||||
|
||||
|
||||
==== tests/cases/conformance/parser/ecmascript5/RegularExpressions/parserRegularExpressionDivideAmbiguity7.ts (2 errors) ====
|
||||
(a/8
|
||||
~
|
||||
!!! error TS2304: Cannot find name 'a'.
|
||||
){}
|
||||
~
|
||||
!!! error TS1005: ';' expected.
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
//// [parserRegularExpressionDivideAmbiguity7.ts]
|
||||
(a/8
|
||||
){}
|
||||
|
||||
|
||||
//// [parserRegularExpressionDivideAmbiguity7.js]
|
||||
(a / 8);
|
||||
{ }
|
||||
@@ -0,0 +1,3 @@
|
||||
function foo(q: string, b: number) {
|
||||
return true ? (q ? true : false) : (b = q.length, function() { });
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
(a/8
|
||||
){}
|
||||
Reference in New Issue
Block a user