Disallow await inside async arrow params (#10469)

* Disallow await inside async arrow params

* Use -1 as default for awaitPos/yieldPos
This commit is contained in:
Nicolò Ribaudo 2019-10-02 07:37:40 +02:00 committed by GitHub
parent fa5057f9fb
commit a219b6de7a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 526 additions and 41 deletions

View File

@ -490,11 +490,7 @@ export default class ExpressionParser extends LValParser {
// Parse unary operators, both prefix and postfix. // Parse unary operators, both prefix and postfix.
parseMaybeUnary(refShorthandDefaultPos: ?Pos): N.Expression { parseMaybeUnary(refShorthandDefaultPos: ?Pos): N.Expression {
if ( if (this.isContextual("await") && this.isAwaitAllowed()) {
this.isContextual("await") &&
(this.scope.inAsync ||
(!this.scope.inFunction && this.options.allowAwaitOutsideFunction))
) {
return this.parseAwait(); return this.parseAwait();
} else if (this.state.type.prefix) { } else if (this.state.type.prefix) {
const node = this.startNode(); const node = this.startNode();
@ -676,8 +672,8 @@ export default class ExpressionParser extends LValParser {
const oldYieldPos = this.state.yieldPos; const oldYieldPos = this.state.yieldPos;
const oldAwaitPos = this.state.awaitPos; const oldAwaitPos = this.state.awaitPos;
this.state.maybeInArrowParameters = true; this.state.maybeInArrowParameters = true;
this.state.yieldPos = 0; this.state.yieldPos = -1;
this.state.awaitPos = 0; this.state.awaitPos = -1;
this.next(); this.next();
@ -716,8 +712,34 @@ export default class ExpressionParser extends LValParser {
// We keep the old value if it isn't null, for cases like // We keep the old value if it isn't null, for cases like
// (x = async(yield)) => {} // (x = async(yield)) => {}
this.state.yieldPos = oldYieldPos || this.state.yieldPos; //
this.state.awaitPos = oldAwaitPos || this.state.awaitPos; // Hi developer of the future :) If you are implementing generator
// arrow functions, please read the note below about "await" and
// verify if the same logic is needed for yield.
if (oldYieldPos !== -1) this.state.yieldPos = oldYieldPos;
// Await is trickier than yield. When parsing a possible arrow function
// (e.g. something starting with `async(`) we don't know if its possible
// parameters will actually be inside an async arrow function or if it is
// a normal call expression.
// If it ended up being a call expression, if we are in a context where
// await expression are disallowed (and thus "await" is an identifier)
// we must be careful not to leak this.state.awaitPos to an even outer
// context, where "await" could not be an identifier.
// For example, this code is valid because "await" isn't directly inside
// an async function:
//
// async function a() {
// function b(param = async (await)) {
// }
// }
//
if (
(!this.isAwaitAllowed() && !oldMaybeInArrowParameters) ||
oldAwaitPos !== -1
) {
this.state.awaitPos = oldAwaitPos;
}
} }
this.state.maybeInArrowParameters = oldMaybeInArrowParameters; this.state.maybeInArrowParameters = oldMaybeInArrowParameters;
@ -1231,8 +1253,8 @@ export default class ExpressionParser extends LValParser {
const oldAwaitPos = this.state.awaitPos; const oldAwaitPos = this.state.awaitPos;
const oldInFSharpPipelineDirectBody = this.state.inFSharpPipelineDirectBody; const oldInFSharpPipelineDirectBody = this.state.inFSharpPipelineDirectBody;
this.state.maybeInArrowParameters = true; this.state.maybeInArrowParameters = true;
this.state.yieldPos = 0; this.state.yieldPos = -1;
this.state.awaitPos = 0; this.state.awaitPos = -1;
this.state.inFSharpPipelineDirectBody = false; this.state.inFSharpPipelineDirectBody = false;
const innerStartPos = this.state.start; const innerStartPos = this.state.start;
@ -1310,8 +1332,8 @@ export default class ExpressionParser extends LValParser {
// We keep the old value if it isn't null, for cases like // We keep the old value if it isn't null, for cases like
// (x = (yield)) => {} // (x = (yield)) => {}
this.state.yieldPos = oldYieldPos || this.state.yieldPos; if (oldYieldPos !== -1) this.state.yieldPos = oldYieldPos;
this.state.awaitPos = oldAwaitPos || this.state.awaitPos; if (oldAwaitPos !== -1) this.state.awaitPos = oldAwaitPos;
if (!exprList.length) { if (!exprList.length) {
this.unexpected(this.state.lastTokStart); this.unexpected(this.state.lastTokStart);
@ -1804,8 +1826,8 @@ export default class ExpressionParser extends LValParser {
): T { ): T {
const oldYieldPos = this.state.yieldPos; const oldYieldPos = this.state.yieldPos;
const oldAwaitPos = this.state.awaitPos; const oldAwaitPos = this.state.awaitPos;
this.state.yieldPos = 0; this.state.yieldPos = -1;
this.state.awaitPos = 0; this.state.awaitPos = -1;
this.initFunction(node, isAsync); this.initFunction(node, isAsync);
node.generator = !!isGenerator; node.generator = !!isGenerator;
@ -1842,8 +1864,8 @@ export default class ExpressionParser extends LValParser {
const oldYieldPos = this.state.yieldPos; const oldYieldPos = this.state.yieldPos;
const oldAwaitPos = this.state.awaitPos; const oldAwaitPos = this.state.awaitPos;
this.state.maybeInArrowParameters = false; this.state.maybeInArrowParameters = false;
this.state.yieldPos = 0; this.state.yieldPos = -1;
this.state.awaitPos = 0; this.state.awaitPos = -1;
if (params) this.setArrowFunctionParameters(node, params); if (params) this.setArrowFunctionParameters(node, params);
this.parseFunctionBody(node, true); this.parseFunctionBody(node, true);
@ -2117,11 +2139,18 @@ export default class ExpressionParser extends LValParser {
); );
} }
if (this.scope.inAsync && word === "await") { if (word === "await") {
this.raise( if (this.scope.inAsync) {
startLoc, this.raise(
"Can not use 'await' as identifier inside an async function", startLoc,
); "Can not use 'await' as identifier inside an async function",
);
} else if (
this.state.awaitPos === -1 &&
(this.state.maybeInArrowParameters || this.isAwaitAllowed())
) {
this.state.awaitPos = this.state.start;
}
} }
if (this.state.inClassProperty && word === "arguments") { if (this.state.inClassProperty && word === "arguments") {
@ -2151,10 +2180,16 @@ export default class ExpressionParser extends LValParser {
} }
} }
isAwaitAllowed(): boolean {
if (this.scope.inFunction) return this.scope.inAsync;
if (this.options.allowAwaitOutsideFunction) return true;
return false;
}
// Parses await expression inside async function. // Parses await expression inside async function.
parseAwait(): N.AwaitExpression { parseAwait(): N.AwaitExpression {
if (!this.state.awaitPos) { if (this.state.awaitPos === -1) {
this.state.awaitPos = this.state.start; this.state.awaitPos = this.state.start;
} }
const node = this.startNode(); const node = this.startNode();
@ -2183,7 +2218,7 @@ export default class ExpressionParser extends LValParser {
// Parses yield expression inside generator. // Parses yield expression inside generator.
parseYield(noIn?: ?boolean): N.YieldExpression { parseYield(noIn?: ?boolean): N.YieldExpression {
if (!this.state.yieldPos) { if (this.state.yieldPos === -1) {
this.state.yieldPos = this.state.start; this.state.yieldPos = this.state.start;
} }
const node = this.startNode(); const node = this.startNode();

View File

@ -1050,12 +1050,14 @@ export default class StatementParser extends ExpressionParser {
node.id = this.parseFunctionId(requireId); node.id = this.parseFunctionId(requireId);
} }
const oldMaybeInArrowParameters = this.state.maybeInArrowParameters;
const oldInClassProperty = this.state.inClassProperty; const oldInClassProperty = this.state.inClassProperty;
const oldYieldPos = this.state.yieldPos; const oldYieldPos = this.state.yieldPos;
const oldAwaitPos = this.state.awaitPos; const oldAwaitPos = this.state.awaitPos;
this.state.maybeInArrowParameters = false;
this.state.inClassProperty = false; this.state.inClassProperty = false;
this.state.yieldPos = 0; this.state.yieldPos = -1;
this.state.awaitPos = 0; this.state.awaitPos = -1;
this.scope.enter(functionFlags(node.async, node.generator)); this.scope.enter(functionFlags(node.async, node.generator));
if (!isStatement) { if (!isStatement) {
@ -1084,6 +1086,7 @@ export default class StatementParser extends ExpressionParser {
this.checkFunctionStatementId(node); this.checkFunctionStatementId(node);
} }
this.state.maybeInArrowParameters = oldMaybeInArrowParameters;
this.state.inClassProperty = oldInClassProperty; this.state.inClassProperty = oldInClassProperty;
this.state.yieldPos = oldYieldPos; this.state.yieldPos = oldYieldPos;
this.state.awaitPos = oldAwaitPos; this.state.awaitPos = oldAwaitPos;

View File

@ -159,15 +159,15 @@ export default class UtilParser extends Tokenizer {
checkYieldAwaitInDefaultParams() { checkYieldAwaitInDefaultParams() {
if ( if (
this.state.yieldPos && this.state.yieldPos !== -1 &&
(!this.state.awaitPos || this.state.yieldPos < this.state.awaitPos) (this.state.awaitPos === -1 || this.state.yieldPos < this.state.awaitPos)
) { ) {
this.raise( this.raise(
this.state.yieldPos, this.state.yieldPos,
"Yield cannot be used as name inside a generator function", "Yield cannot be used as name inside a generator function",
); );
} }
if (this.state.awaitPos) { if (this.state.awaitPos !== -1) {
this.raise( this.raise(
this.state.awaitPos, this.state.awaitPos,
"Await cannot be used as name inside an async function", "Await cannot be used as name inside an async function",

View File

@ -97,8 +97,8 @@ export default class State {
decoratorStack: Array<Array<N.Decorator>> = [[]]; decoratorStack: Array<Array<N.Decorator>> = [[]];
// Positions to delayed-check that yield/await does not exist in default parameters. // Positions to delayed-check that yield/await does not exist in default parameters.
yieldPos: number = 0; yieldPos: number = -1;
awaitPos: number = 0; awaitPos: number = -1;
// Token store. // Token store.
tokens: Array<Token | N.Comment> = []; tokens: Array<Token | N.Comment> = [];

View File

@ -0,0 +1 @@
async (a = ({ await }) => {}) => {};

View File

@ -0,0 +1,3 @@
{
"throws": "Await cannot be used as name inside an async function (1:20)"
}

View File

@ -0,0 +1,3 @@
async function f() {
function g(x = async(await)) {}
}

View File

@ -0,0 +1,224 @@
{
"type": "File",
"start": 0,
"end": 56,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 3,
"column": 1
}
},
"program": {
"type": "Program",
"start": 0,
"end": 56,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 3,
"column": 1
}
},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "FunctionDeclaration",
"start": 0,
"end": 56,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 3,
"column": 1
}
},
"id": {
"type": "Identifier",
"start": 15,
"end": 16,
"loc": {
"start": {
"line": 1,
"column": 15
},
"end": {
"line": 1,
"column": 16
},
"identifierName": "f"
},
"name": "f"
},
"generator": false,
"async": true,
"params": [],
"body": {
"type": "BlockStatement",
"start": 19,
"end": 56,
"loc": {
"start": {
"line": 1,
"column": 19
},
"end": {
"line": 3,
"column": 1
}
},
"body": [
{
"type": "FunctionDeclaration",
"start": 23,
"end": 54,
"loc": {
"start": {
"line": 2,
"column": 2
},
"end": {
"line": 2,
"column": 33
}
},
"id": {
"type": "Identifier",
"start": 32,
"end": 33,
"loc": {
"start": {
"line": 2,
"column": 11
},
"end": {
"line": 2,
"column": 12
},
"identifierName": "g"
},
"name": "g"
},
"generator": false,
"async": false,
"params": [
{
"type": "AssignmentPattern",
"start": 34,
"end": 50,
"loc": {
"start": {
"line": 2,
"column": 13
},
"end": {
"line": 2,
"column": 29
}
},
"left": {
"type": "Identifier",
"start": 34,
"end": 35,
"loc": {
"start": {
"line": 2,
"column": 13
},
"end": {
"line": 2,
"column": 14
},
"identifierName": "x"
},
"name": "x"
},
"right": {
"type": "CallExpression",
"start": 38,
"end": 50,
"loc": {
"start": {
"line": 2,
"column": 17
},
"end": {
"line": 2,
"column": 29
}
},
"callee": {
"type": "Identifier",
"start": 38,
"end": 43,
"loc": {
"start": {
"line": 2,
"column": 17
},
"end": {
"line": 2,
"column": 22
},
"identifierName": "async"
},
"name": "async"
},
"arguments": [
{
"type": "Identifier",
"start": 44,
"end": 49,
"loc": {
"start": {
"line": 2,
"column": 23
},
"end": {
"line": 2,
"column": 28
},
"identifierName": "await"
},
"name": "await"
}
]
}
}
],
"body": {
"type": "BlockStatement",
"start": 52,
"end": 54,
"loc": {
"start": {
"line": 2,
"column": 31
},
"end": {
"line": 2,
"column": 33
}
},
"body": [],
"directives": []
}
}
],
"directives": []
}
}
],
"directives": []
}
}

View File

@ -0,0 +1,225 @@
{
"type": "File",
"start": 0,
"end": 39,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 39
}
},
"program": {
"type": "Program",
"start": 0,
"end": 39,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 39
}
},
"sourceType": "script",
"interpreter": null,
"body": [
{
"type": "ExpressionStatement",
"start": 0,
"end": 39,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 39
}
},
"expression": {
"type": "ArrowFunctionExpression",
"start": 0,
"end": 38,
"loc": {
"start": {
"line": 1,
"column": 0
},
"end": {
"line": 1,
"column": 38
}
},
"id": null,
"generator": false,
"async": true,
"params": [
{
"type": "AssignmentPattern",
"start": 7,
"end": 31,
"loc": {
"start": {
"line": 1,
"column": 7
},
"end": {
"line": 1,
"column": 31
}
},
"left": {
"type": "Identifier",
"start": 7,
"end": 8,
"loc": {
"start": {
"line": 1,
"column": 7
},
"end": {
"line": 1,
"column": 8
},
"identifierName": "a"
},
"name": "a"
},
"right": {
"type": "ArrowFunctionExpression",
"start": 11,
"end": 31,
"loc": {
"start": {
"line": 1,
"column": 11
},
"end": {
"line": 1,
"column": 31
}
},
"id": null,
"generator": false,
"async": false,
"params": [
{
"type": "ObjectPattern",
"start": 12,
"end": 24,
"loc": {
"start": {
"line": 1,
"column": 12
},
"end": {
"line": 1,
"column": 24
}
},
"properties": [
{
"type": "ObjectProperty",
"start": 14,
"end": 22,
"loc": {
"start": {
"line": 1,
"column": 14
},
"end": {
"line": 1,
"column": 22
}
},
"method": false,
"key": {
"type": "Identifier",
"start": 14,
"end": 19,
"loc": {
"start": {
"line": 1,
"column": 14
},
"end": {
"line": 1,
"column": 19
},
"identifierName": "await"
},
"name": "await"
},
"computed": false,
"shorthand": false,
"value": {
"type": "Identifier",
"start": 21,
"end": 22,
"loc": {
"start": {
"line": 1,
"column": 21
},
"end": {
"line": 1,
"column": 22
},
"identifierName": "x"
},
"name": "x"
}
}
]
}
],
"body": {
"type": "BlockStatement",
"start": 29,
"end": 31,
"loc": {
"start": {
"line": 1,
"column": 29
},
"end": {
"line": 1,
"column": 31
}
},
"body": [],
"directives": []
}
}
}
],
"body": {
"type": "BlockStatement",
"start": 36,
"end": 38,
"loc": {
"start": {
"line": 1,
"column": 36
},
"end": {
"line": 1,
"column": 38
}
},
"body": [],
"directives": []
}
}
}
],
"directives": []
}
}

View File

@ -1,15 +1,5 @@
language/expressions/assignment/destructuring/obj-prop-__proto__dup.js(default) language/expressions/assignment/destructuring/obj-prop-__proto__dup.js(default)
language/expressions/assignment/destructuring/obj-prop-__proto__dup.js(strict mode) language/expressions/assignment/destructuring/obj-prop-__proto__dup.js(strict mode)
language/expressions/async-arrow-function/await-as-param-ident-nested-arrow-parameter-position.js(default)
language/expressions/async-arrow-function/await-as-param-ident-nested-arrow-parameter-position.js(strict mode)
language/expressions/async-arrow-function/await-as-param-nested-arrow-parameter-position.js(default)
language/expressions/async-arrow-function/await-as-param-nested-arrow-parameter-position.js(strict mode)
language/expressions/async-arrow-function/await-as-param-rest-nested-arrow-parameter-position.js(default)
language/expressions/async-arrow-function/await-as-param-rest-nested-arrow-parameter-position.js(strict mode)
language/expressions/async-arrow-function/early-errors-arrow-await-in-formals-default.js(default)
language/expressions/async-arrow-function/early-errors-arrow-await-in-formals-default.js(strict mode)
language/expressions/async-arrow-function/early-errors-arrow-await-in-formals.js(default)
language/expressions/async-arrow-function/early-errors-arrow-await-in-formals.js(strict mode)
language/expressions/class/elements/fields-duplicate-privatenames.js(default) language/expressions/class/elements/fields-duplicate-privatenames.js(default)
language/expressions/class/elements/fields-duplicate-privatenames.js(strict mode) language/expressions/class/elements/fields-duplicate-privatenames.js(strict mode)
language/expressions/class/elements/syntax/early-errors/grammar-private-environment-on-class-heritage-chained-usage.js(default) language/expressions/class/elements/syntax/early-errors/grammar-private-environment-on-class-heritage-chained-usage.js(default)