Skip to content

fix(parser): parse / after an await identifier as division - #659

Merged
3cp merged 1 commit into
meriyah:mainfrom
MaxFreedomPollard:fix/await-identifier-division
Sep 15, 2026
Merged

3cp merged 1 commit into
meriyah:mainfrom
MaxFreedomPollard:fix/await-identifier-division

Conversation

@MaxFreedomPollard

Copy link
Copy Markdown
Contributor

meriyah.parseScript('await/x') throws [1:5-1:7]: Unterminated regular expression. So do function f(){ await/x }, function* g(){ await/x }, let x = await/2, var await; await/x and label: await/x, while await/x/g and x = await/2/g throw Unexpected token: 'regular expression'. In script code await is an ordinary identifier, so the / after it is a division operator, and V8 and acorn parse every one of those inputs; strict script code is hit the same way, since "use strict"; await/x fails with the same Unterminated regular expression. The same hole reaches a property named await in any mode, so a.await/2/g, function f(){ x.await/2/g } and async function f(){ x.await/2/g } fail too, in script and in module code alike, even though await there is only a property name and can never start an await expression.

The cause is in parseIdentifier in src/parser.ts, which decided by token text alone: const allowRegex = tokenValue === 'await' && (parser.getToken() & Token.IsEscaped) === 0. Every identifier whose text happens to be await therefore made the lexer read the following token with Context.AllowRegExp, no matter what the surrounding context was and no matter whether the identifier was an expression, a binding, a label or a member property. Whether await introduces an AwaitExpression is fully determined by the context, so no token text sniffing and no lookahead is needed for that choice.

The fix moves the decision to the one caller that can start an await expression. parseAwaitExpressionOrIdentifier computes allowRegExp as not escaped and context & (Context.InAwaitContext | Context.Module), which is the same test src/common.ts already uses at lines 308, 340 and 359 to decide whether await is a keyword, and hands it through parseIdentifierOrArrow into parseIdentifier, which no longer decides from tokenValue. Both new parameters default to 0, so property names, object keys and bindings never ask for a regex scan through them; the break and continue label references keep requesting one through Context.AllowRegExp in the context, as before. Await expressions in module code are untouched: await/x at module top level still fails, y = await/x/g in a module and async function f(){ await/x/g } still parse the regular expression, and async function f(){ await/x } still fails. sourceType: 'commonjs' sets neither Context.Module nor Context.InAwaitContext, so it reads / as division, which matches Node. One nearby gap is untouched because it is not this scan: function f(a = await/2/g) {} is accepted by V8 and acorn but still reports Await expression not allowed in formal parameter, from the parameter await tracking (firstAwaitLocation, DestructuringKind.Await).

Seventeen pass cases go into test/parser/expressions/await.ts: fourteen fail on main, one of them under sourceType: 'commonjs' and one in strict script code, and three guard the await expression side that must keep scanning a regular expression. Two new fail cases pin that side so it cannot quietly flip to division later. Seven existing fail inputs change their message, filling eight snapshot entries because one is listed twice, and all still error. Four now point at the right token: async(a = await/r/g) => {} moves from [1:15-1:19]: Expected ')' under /r/g to [1:10-1:15]: Await expression not allowed in formal parameter under await. Two become less pointed but state the correct complaint, since division is the right reading in script code and the real fault is the target on the left. The seventh, a = (b = await/r/g) => {}) => {}, now points at the stray ).

On node v22.23.2 over base 683b03e: npx vitest run is 91979 passed in 139 files with 0 failures, npx tsc --noEmit exits 0, npx prettier --check . is clean and node scripts/lint-snapshots.mjs passes. Every input named above was cross checked against acorn, and the script ones against new vm.Script; after the change the parser agrees with both on all of them, apart from the formal parameter case called out in the third paragraph. I could not run the test262 conformance harness locally, so that pass deserves a look in CI.

`parseIdentifier` used to scan the next token with `Context.AllowRegExp` whenever the identifier text was an unescaped `await`, regardless of context. In sloppy script code, and for any property named `await`, that made a following `/` open a regular expression instead of acting as a division operator, so `await/x` failed with `Unterminated regular expression` and `a.await/2/g` with `Unexpected token: 'regular expression'`.

The regex scan now travels with the one caller that can actually start an await expression. `parseAwaitExpressionOrIdentifier` computes it from the context that decides how `await` reads, and passes it through `parseIdentifierOrArrow` into `parseIdentifier`, which no longer decides from the token text. Property names, object keys, labels and bindings therefore never request a regex scan, while `await /x/` inside an async function or at module top level is unchanged.
@pkg-pr-new

pkg-pr-new Bot commented Sep 15, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/meriyah@659

commit: 95a7520

@3cp 3cp left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thx!

@3cp
3cp merged commit 7424452 into meriyah:main Sep 15, 2026
8 checks passed
@3cp

3cp commented Oct 5, 2026

Copy link
Copy Markdown
Member

v7.4.0 is released.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants