Skip to content

Commit

Permalink
Disallow await inside async arrow params (#10469)
Browse files Browse the repository at this point in the history
* Disallow await inside async arrow params

* Use -1 as default for awaitPos/yieldPos
  • Loading branch information
nicolo-ribaudo committed Oct 2, 2019
1 parent fa5057f commit a219b6d
Show file tree
Hide file tree
Showing 11 changed files with 526 additions and 41 deletions.
83 changes: 59 additions & 24 deletions packages/babel-parser/src/parser/expression.js
Expand Up @@ -490,11 +490,7 @@ export default class ExpressionParser extends LValParser {
// Parse unary operators, both prefix and postfix.

parseMaybeUnary(refShorthandDefaultPos: ?Pos): N.Expression {
if (
this.isContextual("await") &&
(this.scope.inAsync ||
(!this.scope.inFunction && this.options.allowAwaitOutsideFunction))
) {
if (this.isContextual("await") && this.isAwaitAllowed()) {
return this.parseAwait();
} else if (this.state.type.prefix) {
const node = this.startNode();
Expand Down Expand Up @@ -676,8 +672,8 @@ export default class ExpressionParser extends LValParser {
const oldYieldPos = this.state.yieldPos;
const oldAwaitPos = this.state.awaitPos;
this.state.maybeInArrowParameters = true;
this.state.yieldPos = 0;
this.state.awaitPos = 0;
this.state.yieldPos = -1;
this.state.awaitPos = -1;

this.next();

Expand Down Expand Up @@ -716,8 +712,34 @@ export default class ExpressionParser extends LValParser {

// We keep the old value if it isn't null, for cases like
// (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;
Expand Down Expand Up @@ -1231,8 +1253,8 @@ export default class ExpressionParser extends LValParser {
const oldAwaitPos = this.state.awaitPos;
const oldInFSharpPipelineDirectBody = this.state.inFSharpPipelineDirectBody;
this.state.maybeInArrowParameters = true;
this.state.yieldPos = 0;
this.state.awaitPos = 0;
this.state.yieldPos = -1;
this.state.awaitPos = -1;
this.state.inFSharpPipelineDirectBody = false;

const innerStartPos = this.state.start;
Expand Down Expand Up @@ -1310,8 +1332,8 @@ export default class ExpressionParser extends LValParser {

// We keep the old value if it isn't null, for cases like
// (x = (yield)) => {}
this.state.yieldPos = oldYieldPos || this.state.yieldPos;
this.state.awaitPos = oldAwaitPos || this.state.awaitPos;
if (oldYieldPos !== -1) this.state.yieldPos = oldYieldPos;
if (oldAwaitPos !== -1) this.state.awaitPos = oldAwaitPos;

if (!exprList.length) {
this.unexpected(this.state.lastTokStart);
Expand Down Expand Up @@ -1804,8 +1826,8 @@ export default class ExpressionParser extends LValParser {
): T {
const oldYieldPos = this.state.yieldPos;
const oldAwaitPos = this.state.awaitPos;
this.state.yieldPos = 0;
this.state.awaitPos = 0;
this.state.yieldPos = -1;
this.state.awaitPos = -1;

this.initFunction(node, isAsync);
node.generator = !!isGenerator;
Expand Down Expand Up @@ -1842,8 +1864,8 @@ export default class ExpressionParser extends LValParser {
const oldYieldPos = this.state.yieldPos;
const oldAwaitPos = this.state.awaitPos;
this.state.maybeInArrowParameters = false;
this.state.yieldPos = 0;
this.state.awaitPos = 0;
this.state.yieldPos = -1;
this.state.awaitPos = -1;

if (params) this.setArrowFunctionParameters(node, params);
this.parseFunctionBody(node, true);
Expand Down Expand Up @@ -2117,11 +2139,18 @@ export default class ExpressionParser extends LValParser {
);
}

if (this.scope.inAsync && word === "await") {
this.raise(
startLoc,
"Can not use 'await' as identifier inside an async function",
);
if (word === "await") {
if (this.scope.inAsync) {
this.raise(
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") {
Expand Down Expand Up @@ -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.

parseAwait(): N.AwaitExpression {
if (!this.state.awaitPos) {
if (this.state.awaitPos === -1) {
this.state.awaitPos = this.state.start;
}
const node = this.startNode();
Expand Down Expand Up @@ -2183,7 +2218,7 @@ export default class ExpressionParser extends LValParser {
// Parses yield expression inside generator.

parseYield(noIn?: ?boolean): N.YieldExpression {
if (!this.state.yieldPos) {
if (this.state.yieldPos === -1) {
this.state.yieldPos = this.state.start;
}
const node = this.startNode();
Expand Down
7 changes: 5 additions & 2 deletions packages/babel-parser/src/parser/statement.js
Expand Up @@ -1050,12 +1050,14 @@ export default class StatementParser extends ExpressionParser {
node.id = this.parseFunctionId(requireId);
}

const oldMaybeInArrowParameters = this.state.maybeInArrowParameters;
const oldInClassProperty = this.state.inClassProperty;
const oldYieldPos = this.state.yieldPos;
const oldAwaitPos = this.state.awaitPos;
this.state.maybeInArrowParameters = false;
this.state.inClassProperty = false;
this.state.yieldPos = 0;
this.state.awaitPos = 0;
this.state.yieldPos = -1;
this.state.awaitPos = -1;
this.scope.enter(functionFlags(node.async, node.generator));

if (!isStatement) {
Expand Down Expand Up @@ -1084,6 +1086,7 @@ export default class StatementParser extends ExpressionParser {
this.checkFunctionStatementId(node);
}

this.state.maybeInArrowParameters = oldMaybeInArrowParameters;
this.state.inClassProperty = oldInClassProperty;
this.state.yieldPos = oldYieldPos;
this.state.awaitPos = oldAwaitPos;
Expand Down
6 changes: 3 additions & 3 deletions packages/babel-parser/src/parser/util.js
Expand Up @@ -159,15 +159,15 @@ export default class UtilParser extends Tokenizer {

checkYieldAwaitInDefaultParams() {
if (
this.state.yieldPos &&
(!this.state.awaitPos || this.state.yieldPos < this.state.awaitPos)
this.state.yieldPos !== -1 &&
(this.state.awaitPos === -1 || this.state.yieldPos < this.state.awaitPos)
) {
this.raise(
this.state.yieldPos,
"Yield cannot be used as name inside a generator function",
);
}
if (this.state.awaitPos) {
if (this.state.awaitPos !== -1) {
this.raise(
this.state.awaitPos,
"Await cannot be used as name inside an async function",
Expand Down
4 changes: 2 additions & 2 deletions packages/babel-parser/src/tokenizer/state.js
Expand Up @@ -97,8 +97,8 @@ export default class State {
decoratorStack: Array<Array<N.Decorator>> = [[]];

// Positions to delayed-check that yield/await does not exist in default parameters.
yieldPos: number = 0;
awaitPos: number = 0;
yieldPos: number = -1;
awaitPos: number = -1;

// Token store.
tokens: Array<Token | N.Comment> = [];
Expand Down
@@ -0,0 +1 @@
async (a = ({ await }) => {}) => {};
@@ -0,0 +1,3 @@
{
"throws": "Await cannot be used as name inside an async function (1:20)"
}
@@ -0,0 +1,3 @@
async function f() {
function g(x = async(await)) {}
}

0 comments on commit a219b6d

Please sign in to comment.